falco 敏感信息检测

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:应用于规则的标签列表,可以有多个tag。falco tags可以参考Falco tags 官网

我们的规则中的tags中的filesystem表示The rule relates to reading/writing files,即该规则与读/写文件有关。

整个规则可以参考Falco规则官网

规则对应数据结构为:

	/*!
		\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函数的最后几行。

假如我们想让cat /etc/shadow 不产生告警日志,则将规则修改成如下方式即可:

- 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_,
     cat
     )
    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]

即在condition中的"and not proc.name in"增加cat即可。

我看一下规则文件中的敏感文件sensitive_files有哪些。

sensitive_files宏定义:

- macro: sensitive_files
  condition: >
    fd.name startswith /etc and
    (fd.name in (sensitive_file_names)
     or fd.directory in (/etc/sudoers.d, /etc/pam.d))

条件定义如下,文件路径的起始为/etc,并且文件名属于sensitive_file_names或者文件目录为/etc/sudoers.d或者/etc/pam.d。显然我们的例子,触发的条件应该在sensitive_file_names。

继续往下看:

- list: sensitive_file_names
  items: [/etc/shadow, /etc/sudoers, /etc/pam.conf, /etc/security/pwquality.conf]

一目了然,就在这个定义中。也就是说我们 cat /etc/pam.conf 也会告警。我们测试一下。

符合预期。可见yaml的可读性很强。 

对敏感文件的读取操作是open_read这个宏定义表示。我们看其定义:

- macro: open_read
  condition: evt.type in (open,openat,openat2) and evt.is_open_read=true and fd.typechar='f' and fd.num>=0

首先evt.type= 指定为open、openat、openat2这三个系统调用之一,并且is_open_read有读操作,并且Falco希望我们在写规则时在条件的开头至少使用一个evt.type=选项,否则会出现告警信息, 这个选项可以将规则分组,提升性能。

。。。。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值