教程
🛡️

网络安全

Web 安全、渗透测试、密码学与安全合规的系统化教程。

PHP 代码审计基础:先记住这拨"一用就危险"的函数

PHP 因为历史包袱重、弱类型、函数众多,是代码审计的高产战场。这篇系统梳理 PHP 里的命令执行、代码执行、文件包含、文件操作、SQL 注入、变量覆盖、反序列化等危险函数与典型漏洞模式,并讲清弱类型比较、魔术方法这些最容易让人栽跟头的语言特性。

· 更新于 2026-07-19 阅读量 --

PHP 是代码审计里最”出活”的语言之一:它历史长、函数杂、又有弱类型这种反直觉的特性,开发者一不小心就写出可被利用的代码。要把 PHP 审计做好,第一步不是去读业务,而是把”哪些函数危险、哪些写法坑人”刻进肌肉记忆。这一篇就把 PHP 审计的基本功打牢。

本文所有手法仅用于你对自有或已授权的 PHP 代码做安全评估。

命令执行与代码执行

这类漏洞最致命,因为一旦可控,攻击者就能在服务器上跑任意命令。PHP 里要盯紧这些:

  • 命令执行systemexecshell_execpassthrupopenproc_open,以及反引号 ` 包裹的表达式。只要它们的参数拼接了用户输入又没过滤,就是命令注入。
  • 代码执行eval(直接执行字符串代码)、assert(老版本可当 eval 用)、preg_replace/e 修饰符(已废弃但老代码还有)、create_function(已移除但老系统残留)。
$name = $_GET['name'];
eval("echo 'hello $name';");   // 传入 name=;phpinfo(); 即可执行任意代码

审计时全局搜这些函数名,对每个调用点追问:参数来源是否用户可控?中间有没有 escapeshellarg / escapeshellcmd 过滤?没有就高危。

文件包含

includerequireinclude_oncerequire_once 用来引入文件。当被包含的路径拼了用户输入,就可能出现 LFI(本地文件包含) 甚至 RFI(远程文件包含,需 allow_url_include=On

$page = $_GET['page'];
include("pages/$page.php");   // 传入 page=../../../../etc/passwd%00 可读取系统文件

配合 PHP 的 php://filter(如 php://filter/read=convert.base64-encode/resource=)还能读源码、配合 data:// 或日志投毒可升级为代码执行。审计文件包含,重点看路径变量是否过滤了 ../ 和协议封装器。

文件操作类

文件上传、读写、删除同样高危:file_get_contentsfile_put_contentsfopenunlinkmove_uploaded_filecopy。典型问题:

  • 上传没校验后缀/MIME,导致传 .php 马。
  • 删除/读取路径拼接用户参数,造成任意文件删除/读取(比如删除安装锁文件重新安装)。
  • file_get_contents($url)$url 可控,触发 SSRF

SQL 注入

虽然现在多用 PDO 预处理,但老代码和手写拼接依然普遍:mysql_query(已废弃)、mysqli_query 直接拼 SQL、PDO 用了 query 而非 prepare,或者 prepare 里把表名/字段名拼进去(预处理管不了标识符)。

$sql = "SELECT * FROM users WHERE name = '$name'";  // $name 来自 $_GET,未转义

审计 SQL 注入,先找所有 SQL 语句拼接点,再看参数是否经过 addslashes、PDO 预处理或框架的查询构造器。注意 order bylimit 后的参数预处理也管不到,常被忽略。

变量覆盖

PHP 的 $$(可变变量)、extractparse_str(无第二参数时)、import_request_variables(已移除)都会把外部输入变成内部变量,造成变量覆盖。

foreach ($_GET as $k => $v) { $$k = $v; }  // 用户可覆盖任意已存在变量,比如覆盖 $isAdmin

这类漏洞常导致越权、认证绕过:攻击者传入 isAdmin=1 之类的参数,直接覆盖掉权限判断用的变量。审计时看到 $$extractparse_str 要格外警惕。

弱类型比较的坑

PHP 的 == 是弱比较,会偷偷做类型转换,这是无数漏洞的根源:

  • '0e123' == '0e456' 都为 0,在 md5($a) == md5($b) 的校验里可构造”哈希碰撞”绕过(如两个 md5 都以 0e 开头的字符串)。
  • '123abc' == 123truenull == ''true
  • in_array('abc', [0,1,2]) 在默认非严格模式下,'abc' 转数值为 0,命中 0

所以审计里看到 ==in_array 无第三参数、switch 比较、数组键比较,都要怀疑是否可借类型转换绕过。正确做法是用 === 严格比较。

反序列化

unserialize() 会把字符串还原成对象,并在过程中自动调用魔术方法__wakeup__destruct__toString__call 等。如果反序列化的输入用户可控,攻击者可以构造恶意对象,触发这些魔术方法里的危险操作(比如 __destruct 里写了文件、调了数据库)。这就是 PHP 反序列化漏洞(POP 链)的基础。审计时看到 unserialize($_GET/$_POST/...) 必须追魔术方法的调用链。

全局搜索的实战节奏

新手审计 PHP 项目,建议这个节奏:

  1. 先全局搜危险函数(evalsystemincludeunserialize$$extract),把可疑点列成清单。
  2. 对每个点逆推参数来源,看是否用户可控、有无过滤。
  3. 再通读入口文件(index.php、路由、配置文件),理解全局过滤和鉴权是怎么做的,判断有没有被绕过的口子。
  4. 最后挑高危点动态验证(用 Xdebug 或本地起环境复现)。

PHP 配置项里的安全信号

除了代码本身,PHP 的运行时配置(php.ini)也是审计的重要信号源。很多漏洞能不能被打穿,取决于配置怎么开:

  • register_globals:开启后会把请求变量自动注册成全局变量,极易造成变量覆盖,现代 PHP 已移除,但老系统升级不彻底时可能残留等效写法。
  • magic_quotes_gpc:旧版用来自动给引号加斜杠,常被开发者误当成”万能防注入”,实际上对数字型注入、二次注入完全无效,还会污染数据。看到代码依赖它做防护,要警惕。
  • allow_url_include:开启后 include 能加载远程地址,直接把文件包含升级为远程代码执行,生产环境必须关。
  • allow_url_fopen:控制能否用 file_get_contents 访问远程地址,开着就给服务端请求伪造留了口子。
  • display_errors:生产环境应关,开着会把路径、SQL、框架版本直接暴露给攻击者,大幅降低对方信息收集的成本。
  • open_basedir:限制 PHP 能访问的目录,配好能挡掉一部分任意文件读取与删除。
  • disable_functions:禁掉 execsystemeval 等危险函数,是重要的纵深防御手段。
  • session.cookie_httponlysession.cookie_secure:前者防脚本读 Cookie,后者要求 HTTPS 传输,都该开。
  • expose_php:开着会在响应头暴露”X-Powered-By: PHP/版本”,给攻击者版本情报,应关。

审计时先读目标环境的 php.ini 和框架配置,判断这些开关是开是关,再结合代码评估”这个洞在当前配置下到底能不能利用”。同样的代码,配置不同,危害天差地别。把配置和代码放在一起看,结论才靠谱。

还有一个容易忽略的点:不要只盯着”明显危险”的函数。很多高危问题藏在看似普通的写法里,比如用 intval 转类型后仍然可被数组绕过、用 htmlspecialchars 却只转了双引号没转单引号导致属性内跨站脚本。审计要带着”这个防护是不是真的完整”的怀疑,而不是看到有过滤函数就放行。

审计中的几个实用小技巧

最后给几个能立刻用上的小技巧。第一,善用 IDE 的全局搜索和正则,比如搜 (\$_GET|\$_POST|\$_REQUEST)\[ 能快速列出所有外部输入点,再逐个追流向。第二,遇到看不懂的加密或混淆,先确认它是不是真的保护逻辑,很多时候只是障眼法。第三,把”用户可控”四个字贴在显示器上,每次看到变量来源都先问一句能不能被用户改。第四,动态验证优先于静态猜测,能在本地复现的就别只在脑子里推演,复现一次胜过猜十次。这些习惯看着小,积累起来能让你审计又快又准。

这一篇你该记住的

  • PHP 审计先把危险函数清单背熟:命令/代码执行、文件包含、文件操作、SQL 拼接、变量覆盖、反序列化。
  • 弱类型 == 会偷偷转型,是 md5 碰撞、类型混淆绕过的根源,能用 === 就别用 ==
  • $$extractparse_str 会造成变量覆盖,常导致越权与认证绕过。
  • unserialize 可控输入 + 魔术方法 = 反序列化漏洞,要追 __wakeup/__destruct 等调用链。
  • 实战节奏:全局搜危险函数 → 逆推参数来源 → 通读入口与鉴权 → 动态验证

下一篇我们拿最流行的 PHP 框架 ThinkPHP 开刀,看框架的路由、控制器、模板引擎里,哪些写法会埋雷,又该怎么审。