falco 检测不受信任程序读取敏感文件。
?
运行方式:
./userspace/falco/falco -c ../falco.yaml -r ../rules/falco_rules.yaml
测试命令: sudo cat /etc/shadow
日志
06:07:23.678922444: Warning Sensitive file opened for reading by non-trusted program (user=<NA> user_loginuid=1000 program=cat command=cat /etc/shadow file=/etc/shadow parent=sudo gparent=bash ggparent=sshd gggparent=sshd container_id=host image=<NA>)
对应的规则在 ../rules/falco_rules.yaml
- rule: Read sensitive file untrusted
desc: >
an attempt to read any sensitive file (e.g. files containing user/password/authentication
information). Exceptions are made for known trusted programs.
condition: >
sensitive_files and open_read
and proc_name_exists
and not proc.name in (user_mgmt_binaries, userexec_binaries, package_mgmt_binaries,
cron_binaries, read_sensitive_file_binaries, shell_binaries, hids_binaries,
vpn_binaries, mail_config_binaries, nomachine_binaries, sshkit_script_binaries,
in.proftpd, mandb, salt-minion, postgres_mgmt_binaries,
google_oslogin_
)
and not cmp_cp_by_passwd
and not ansible_running_python
and not run_by_qualys
and not run_by_chef
and not run_by_google_accounts_daemon
and not user_read_sensitive_file_conditions
and not mandb_postinst
and not perl_running_plesk
and not perl_running_updmap
and not veritas_driver_script
and not perl_running_centrifydc
and not runuser_reading_pam
and not linux_bench_reading_etc_shadow
and not user_known_read_sensitive_files_activities
and not user_read_sensitive_file_containers
output: >
Sensitive file opened for reading by non-trusted program (user=%user.name user_loginuid=%user.loginuid program=%proc.name
command=%proc.cmdline file=%fd.name parent=%proc.pname gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4] container_id=%container.id image=%container.image.repository)
priority: WARNING
tags: [filesystem, mitre_credential_access, mitre_discovery]
这个对应上面的日志:
首先是:
priority: WARNING
然后是
output: >
Sensitive file opened for reading by non-trusted program (user=%user.name user_loginuid=%user.loginuid program=%proc.name
command=%proc.cmdline file=%fd.name parent=%proc.pname gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4] container_id=%
规则比较长。首先是rule : 规则名,desc:规则描述,condition:匹配条件,output:满足条件时的输出信息,priority:优先级,tags:具体含义还没搞懂。
规则对应数据结构为:
/*!
\brief Represents infos about a rule
*/
struct rule_info
{
context ctx;
size_t index;
size_t visibility;
std::string name;
std::string cond;
std::string source;
std::string desc;
std::string output;
std::set<std::string> tags;
std::vector<rule_exception_info> exceptions;
falco_common::priority_type priority;
bool enabled;
bool warn_evttypes;
bool skip_if_unknown_filter;
};
如果在../falco.yaml中的priority配置比WARNING小,则这条日志不会打印出来。因为,其表示系统的最低优先级,比如Error。
具体定义如下:
static vector<string> priority_names = {
"Emergency",
"Alert",
"Critical",
"Error",
"Warning",
"Notice",
"Informational",
"Debug"
};
优先级配置代码分析:
void falco_configuration::init(string conf_filename, const vector<string> &cmdline_options)
{
string m_config_file = conf_filename;
m_config = new yaml_configuration();
try
{
m_config->load_from_file(m_config_file);//解析../falco.yaml文件
}
catch(const std::exception& e)
{
std::cerr << "Cannot read config file (" + m_config_file + "): " + e.what() + "\n";
throw e;
}
......
string priority = m_config->get_scalar<string>("priority", "debug");//优先级默认值
if (!falco_common::parse_priority(priority, m_min_priority))//优先级配置解析,结果保存在m_min_priority属性中
{
throw logic_error("Unknown priority \"" + priority + "\"--must be one of emergency, alert, critical, error, warning, notice, informational, debug");
}
......
}
我们可以做个实验,比如在配置文件中写入错误优先级:
priority: debug1
运行报错:
?可以看到提示错误与上面的配置吻合(部分日志是我加的调试)。
接着加载规则,
application::load_rules_files=>falco_engine::load_rules_file=>falco_engine::load_rules=>rule_reader::load=>read_item
static void read_item(
rule_loader::configuration& cfg,
rule_loader& loader,
const YAML::Node& item,
const rule_loader::context& ctx)
{
......
else if(item["rule"].IsDefined())//是否是规则
{
rule_loader::rule_info v;
v.ctx = ctx;
bool append = false;
v.enabled = true;
v.warn_evttypes = true;
v.skip_if_unknown_filter = false;
THROW(!decode_val(item["rule"], v.name) || v.name.empty(),
"Rule name is empty");
if(decode_val(item["append"], append) && append)
{
decode_val(item["condition"], v.cond);
if (item["exceptions"].IsDefined())
{
read_rule_exceptions(item["exceptions"], v);
}
loader.append(cfg, v);
}
else
{
string priority;//优先级处理
bool has_enabled = decode_val(item["enabled"], v.enabled);
bool has_defs = decode_val(item["condition"], v.cond)
&& decode_val(item["output"], v.output)
&& decode_val(item["desc"], v.desc)
&& decode_val(item["priority"], priority);
if (!has_defs)//检查规则的合法性,规则必须存在'condition', 'output', 'desc', and 'priority'
{
THROW(!has_enabled, "Rule must have properties 'condition', 'output', 'desc', and 'priority'");
loader.enable(cfg, v);
}
else//规则合法
{
v.output = trim(v.output);
v.source = falco_common::syscall_source;
THROW(!falco_common::parse_priority(priority, v.priority),
"Invalid priority");//和之前一样,解析规则的优先级
decode_val(item["source"], v.source);
decode_val(item["warn_evttypes"], v.warn_evttypes);
decode_val(item["skip-if-unknown-filter"], v.skip_if_unknown_filter);
decode_seq(item["tags"], v.tags);
if (item["exceptions"].IsDefined())
{
read_rule_exceptions(item["exceptions"], v);
}
loader.define(cfg, v);//处理规则,下面接着看
}
}
}
else
{
cfg.warnings.push_back("Unknown top level object");
}
}
application::load_rules_files=>falco_engine::load_rules_file=>falco_engine::load_rules=>rule_reader::load=>read_item=>rule_loader::define
void rule_loader::define(configuration& cfg, rule_info& info)
{
if (!cfg.engine->is_source_valid(info.source))
{
cfg.warnings.push_back("Rule " + info.name
+ ": warning (unknown-source): unknown source "
+ info.source + ", skipping");
return;
}
auto prev = m_macro_infos.at(info.name);
THROW(prev && prev->source != info.source,
"Rule " + info.name + " has been re-defined with a different source");
for (auto &ex : info.exceptions)
{
THROW(!ex.fields.is_valid(), "Rule exception item "
+ ex.name + ": must have fields property with a list of fields");
validate_exception_info(cfg, ex, info.source);
}
define_info(m_rule_infos, info, m_cur_index++);//将规则对象保存在m_rule_infos数组中
}
解析完之后,我们要去编译规则。规则的优先级检查就在此处进行。继续看代码:
application::load_rules_files=>falco_engine::load_rules_file=>falco_engine::load_rules=>rule_loader::compile
bool rule_loader::compile(configuration& cfg, indexed_vector<falco_rule>& out)
{
indexed_vector<list_info> lists;
indexed_vector<macro_info> macros;
// expand all lists, macros, and rules
try
{
compile_list_infos(cfg, lists);
compile_macros_infos(cfg, lists, macros);
compile_rule_infos(cfg, lists, macros, out);
}
catch (exception& e)
{
cfg.errors.push_back(e.what());
return false;
}
// print info on any dangling lists or macros that were not used anywhere
for (auto &m : macros)
{
if (!m.used)
{
cfg.warnings.push_back("macro " + m.name
+ " not referred to by any rule/macro");
}
}
for (auto &l : lists)
{
if (!l.used)
{
cfg.warnings.push_back("list " + l.name
+ " not referred to by any rule/macro/list");
}
}
return true;
}
application::load_rules_files=>falco_engine::load_rules_file=>falco_engine::load_rules=>rule_loader::compile=>?rule_loader::compile_rule_infos
void rule_loader::compile_rule_infos(
configuration& cfg,
indexed_vector<list_info>& lists,
indexed_vector<macro_info>& macros,
indexed_vector<falco_rule>& out)
{
string err, condition;
set<string> warn_codes;
filter_warning_resolver warn_resolver;
for (auto &r : m_rule_infos)//遍历之前加载的规则数组
{
try
{
// skip the rule if below the minimum priority
if (r.priority > cfg.min_priority)//优先级检查
{
continue;
}
......
}
}
很明显cfg.min_priority不是之前解析的配置文件的那个那个字段,那是在哪里传递过来的呢?继续往下看:
application::load_rules_files=>falco_engine::load_rules_file=>falco_engine::load_rules
void falco_engine::load_rules(const string &rules_content, bool verbose, bool all_events, uint64_t &required_engine_version)
{
rule_loader::configuration cfg(rules_content);
cfg.engine = this;
cfg.min_priority = m_min_priority;//此处赋值的
cfg.output_extra = m_extra;
cfg.replace_output_container_info = m_replace_container_info;
......
}
还是对不上之前的那个地方是falco_configuration::m_min_priority,这里是falco_engine::m_min_priority。这是怎么关联的呢?在application::init_falco_engine函数的最后将该值传递给了falco_engine::m_min_priority
application::run_result application::init_falco_engine()
{
......
m_state->engine->set_min_priority(m_state->config->m_min_priority);
return ret;
application::init_falco_engine=>falco_engine::set_min_priority
void falco_engine::set_min_priority(falco_common::priority_type priority)
{
m_min_priority = priority;
}
这样就对应上了。通过了优先级检查的规则会保存在falco_engine::m_rules中。
如果我们想看是否真的符合前面的预期,可以做如下操作。
运行时加上如下参数?-l 'Read sensitive file untrusted',表示打印某条规则的描述信息。
不存在,并且程序挂了。Falco查询某条规则时,未做判空。
void falco_engine::describe_rule(string *rule)
{
static const char* rule_fmt = "%-50s %s\n";
fprintf(stdout, rule_fmt, "Rule", "Description");
fprintf(stdout, rule_fmt, "----", "-----------");
if (!rule)
{
for (auto &r : m_rules)
{
auto str = falco::utils::wrap_text(r.description, 51, 110) + "\n";
fprintf(stdout, rule_fmt, r.name.c_str(), str.c_str());
}
}
else
{
auto r = m_rules.at(*rule);//此处可能会返回为空,
auto str = falco::utils::wrap_text(r->description, 51, 110) + "\n";//这里没有检查r是否为空直接操作。导致程序挂了
fprintf(stdout, rule_fmt, r->name.c_str(), str.c_str());
}
}
?我们改成
priority: debug
继续执行:
?查询到了该规则的信息。
具体代码在application::load_rules_files函数的最后几行。
。。。。。。
|