PHP代码审计核心是建立“输入→处理→输出→执行”链路敏感度,需重点追踪用户可控参数如何流入eval、system、call_user_func、unserialize等危险函数,识别宽字节绕过、反序列化利用链及filter_input仅校验不防护等典型误区。

PHP代码审计不是学一堆函数名,而是建立「输入→处理→输出→执行」的链路敏感度。真正卡住初学者的,从来不是看不懂eval(),而是看到call_user_func($_GET['func'], $_GET['arg'])时,没意识到$_GET['func']能传system、file_put_contents甚至自定义类方法。
怎么快速定位危险函数调用点
别从头读代码。直接 grep 或用 Seay/Rips 扫描以下关键词,再人工确认参数是否可控:
-
eval(、assert(:检查参数是否来自$_GET、$_POST、$_REQUEST、$_COOKIE,尤其注意被base64_decode()或urldecode()二次解码后的结果 -
preg_replace(.*?/e:带/e修饰符的写法已废弃但存量极多,replacement参数若含用户输入即高危 -
create_function(:第二个参数$code若拼接了$_GET,等价于eval() -
call_user_func(、call_user_func_array(、array_map(:第一个参数是回调名,若由用户控制(如$_GET['callback']),就等于把函数选择权交给了攻击者 -
system(、exec(、shell_exec(、passthru(、反引号:重点看参数是否经过escapeshellarg()或escapeshellcmd()——前者只保护单个参数,后者只过滤命令字符,二者混用或漏用都可能绕过
为什么 filter_input() 不能当安全过滤用
filter_input()只是校验+基础清洗,不改变变量后续使用方式。它不阻止你把校验过的$id直接拼进 SQL 或丢进system()。
- 用
FILTER_VALIDATE_INT拿到$id,不代表system("ls -l /var/log/{$id}")就安全——攻击者可传1;cat%20/etc/passwd,整条命令仍会被执行 -
FILTER_SANITIZE_STRING在 PHP 8.1+ 已弃用,且它删标签但不清理属性,<img src="1" onerror="alert(1)" alt="PHP代码审计怎么学_常见危险函数与漏洞挖掘【指南】" >中onerror照样保留 - 它不防重赋值:
$a = filter_input(INPUT_GET, 'x', FILTER_SANITIZE_SPECIAL_CHARS); $b = $_GET['x']; system($b);——$a安全,$b完全不受控
反序列化漏洞最常出在哪几行代码
盯死所有对unserialize()的调用,尤其是参数来自$_GET、$_POST、$_COOKIE或文件内容的地方。常见藏匿位置:
立即学习“PHP免费学习笔记(深入)”;
-
unserialize($_COOKIE['user_data']):Cookie 看似“自己写的”,但攻击者可伪造 -
unserialize(file_get_contents($cache_file)):缓存文件若被污染(如日志写入、上传覆盖),反序列化即触发 -
$data = base64_decode($_GET['payload']); unserialize($data);:Base64 只是编码,不是加密,解码后仍是原始字节流 - 用
__wakeup()或__destruct()做资源清理的类,若内部调用了file_get_contents()、include()或system(),就构成利用链起点
宽字节注入为什么现在还频繁出现
因为开发者只记住了「用mysqli_real_escape_string()就行」,却忽略了字符集配置才是关键。
- 数据库连接设为
gbk,但 PHP 连接层没显式指定set_charset('gbk'),导致mysqli_real_escape_string()按默认utf8处理,转义失效 -
mysql_query("SET NAMES gbk")之后再执行mysql_real_escape_string(),但 MySQLi/PDO 中set_charset()和real_escape_string()必须配对使用,跨连接无效 - 看到
addslashes()就以为安全——它根本不识别字符集,%df%27在 GBK 下会被解析为汉字+单引号,逃逸成功 - 修复不是换函数,而是统一字符集:PHP 连接层、MySQL 服务端、表字段、HTML meta 标签,全部强制
utf8mb4,并用预处理语句替代拼接
真正难的不是认出exec(),而是看出$func = $_GET['f']; $func($_GET['p']);这种动态调用;不是背熟魔术方法,而是理解__toString()在echo $obj时触发、__invoke()在$obj()时触发——这些细节决定一条利用链能不能闭合。



















