PHP trick(代码审计关注点)
字数 1268 2025-08-29 08:32:02
PHP代码审计关键点详解
文件操作函数路径处理差异
问题描述
PHP中文件读写操作(file_put_contents, copy, file_get_contents等)与文件删除/判断操作(unlink, file_exists等)对路径处理存在差异,可能导致安全绕过。
技术原理
- 读写操作:调用
php_stream_open_wrapper_ex,最终使用tsrm_realpath标准化路径 - 删除/判断操作:不进行路径标准化处理
绕过方法
- Linux系统:
xxxxx/../test.phptest.php/.
- Windows系统:
test.php:testtest.ph<
- 伪协议:
php://filter/resource=1.php
示例代码
$filename = __DIR__ . '/tmp/' . $user['name'];
$data = $user['info'];
file_put_contents($filename, $data);
if(file_exists($filename)) {
unlink($filename); // 可被绕过
}
变量覆盖漏洞
extract()函数
extract($_GET); // 危险:直接覆盖现有变量
extract($_GET, EXTR_SKIP); // 安全:跳过已有变量
extract($_GET, EXTR_PREFIX_SAME, "prefix"); // 安全:添加前缀
parse_str()函数
parse_str("name=Bill&age=60"); // 危险:直接创建变量
parse_str("name=Bill&age=60", $output); // 安全:使用数组接收
数值处理问题
intval()函数特性
- 32位系统最大带符号范围:-2147483648到2147483647
- 64位系统最大带符号范围:9223372036854775807
- 截断取整(非四舍五入):
intval(10.99999)→ 10 - 转换规则:遇到数字或正负号开始,遇到非数字或
\0结束
浮点数精度问题
var_dump(1.000000000000000 == 1); // TRUE
var_dump(1.0000000000000001 == 1); // TRUE
is_numeric()与intval()差异
is_numeric("\r\n\t 0.1e2"); // TRUE
intval("\r\n\t 12"); // 12
函数比较绕过
strcmp()数组绕过
if(strcmp($_GET['a'], $_GET['b']) == 0) // 传入数组可绕过
sha1()/md5()数组绕过
if(sha1($_GET['a']) === sha1($_GET['b'])) // 传入数组可绕过
弱类型比较问题
常见绕过示例
md5('240610708') == md5('QNKCDZO'); // TRUE (0e...)
sha1('aaroZmOk') == sha1('aaK1STfY'); // TRUE
'0010e2' == '1e3'; // TRUE
'0x1234Ab' == ' 1193131 '; // TRUE
布尔转换规则
FALSE情况:FALSE, 0, 0.0, "", "0", array(), NULL
字符串转数值规则
- 开头有数字:转为数字并省略后面非数字字符
- 开头无数字:转为0
1 + "bob-1.3e3" // 1
1 + "10 Small Pigs" // 11
其他关键点
eregi()匹配绕过
- 传入数组可绕过
- 使用
%00截断
变量名转换规则
点号和空格会被转为下划线:
parse_str("na.me=admin&pass wd=123", $test);
// 结果:["na_me"]=>"admin", ["pass_wd"]=>"123"
in_array()松散比较
in_array("1asd", array(1,2,3,4)); // TRUE
in_array("1asd", array(1,2,3,4), TRUE); // FALSE
htmlspecialchars()转义问题
默认不转义单引号,需使用ENT_QUOTES参数:
htmlspecialchars($input); // 仅转义双引号
htmlspecialchars($input, ENT_QUOTES); // 转义双引号和单引号
sprintf()格式化漏洞
可以吃掉转义后的单引号:
$sql = "select * from user where username = '%\' and 1=1#'";
sprintf($sql, "admin"); // 结果:username = '' and 1=1#'
赋值运算符优先级
=优先级高于and:
$c = is_numeric($a) and is_numeric($b); // 可能绕过$b检查
URL解析差异
parse_url与libcurl差异
user@eval.com:80@baidu.com:- PHP解析host:
baidu.com - libcurl解析host:
eval.com
- PHP解析host:
filter_var()绕过
filter_var("0://evil.com:80;google.com:80/", FILTER_VALIDATE_URL); // TRUE
通过file_get_contents的XSS
$url = "data://baidu.com/plain;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pgo=";
file_get_contents($url); // 返回<script>alert(1)</script>
防御建议
- 对所有文件操作使用绝对路径并进行标准化处理
- 使用
EXTR_SKIP或EXTR_PREFIX_SAME参数处理extract() - 对数值比较使用严格比较
=== - 对
strcmp()等函数添加类型检查 - 使用
htmlspecialchars($input, ENT_QUOTES)完全转义 - 对URL验证使用多重检查机制
- 避免直接使用用户输入拼接SQL语句
- 对重要比较使用严格模式(如
in_array的第三个参数)