php -S 启动内置服务器运行 PHP 代码,识别 eval/system/include 等危险函数的利用条件,从一段 PHP 源码中定位 SQL 注入点和文件上传绕过路径——这是模块 2 Web 渗透之前,从代码层面理解漏洞成因的核心准备。上一篇你用 Bash 在终端里解析日志。本篇视角切换到 Web 后端——在所有后端语言中,PHP 是安全审计绕不开的语言。不是因为 PHP 本身不安全,而是因为它的部署量最大、历史遗留代码最多、入门门槛最低导致大量未经安全审查的代码上线。OWASP Top 10 中的注入、文件上传、反序列化漏洞,在 PHP 项目中出现频率远高于其他语言。
PHP 当前最新稳定版为 8.5.9(2026 年 7 月发布),Kali Linux 通过 apt install php 默认安装 PHP 8.4。但互联网上仍有大量 PHP 5.x 和 7.x 的历史站点——这些老版本的默认配置(magic_quotes、register_globals 等已废弃特性)正是漏洞高发区。代码审计时,先确认目标 PHP 版本再判断危险函数行为。
| 版本 | 发布时间 | 危险默认特性 | 安全状态 |
|---|---|---|---|
| PHP 5.x | 2004-2019 | register_globals、magic_quotes | 已停止安全更新 |
| PHP 7.x | 2015-2023 | 默认开启危险函数 | 已停止安全更新 |
| PHP 8.x | 2020-至今 | 默认 stricter type、移除安全模式 | 8.3/8.5 安全支持中 |
安全工程师看 PHP 源码时,关注点不是"这段代码实现了什么功能",而是"这段代码的输入从哪来、有没有经过过滤、最终流向哪个危险函数"。这种阅读方式叫做"污点追踪"(taint tracking)——从用户可控输入出发,追踪数据流直到它到达危险函数。本篇从 PHP 基础语法讲起,最终实现一个完整的 SQL 注入漏洞代码审计。
你已经掌握了 Python 和 Bash 的变量概念。PHP 变量多一个 $ 符号前缀——$name、$age。但 PHP 对安全最重要的不是变量本身,而是"超全局数组"——这些数组直接承载用户输入,是所有 Web 漏洞的源头。
先在 Kali 上启动 PHP 内置服务器,写第一段代码。内置服务器不需要 Apache/Nginx,适合安全学习和漏洞复现:
执行 php -v 输出示例:
服务器启动后,在同级目录创建 index.php,浏览器访问 http://127.0.0.1:8000/index.php 即可运行。现在看 PHP 中最关键的"超全局数组"——这是用户输入进入 PHP 代码的入口:
这六个超全局数组是 PHP 代码审计的"入口标记"——任何一次漏洞挖掘都从找 $_GET、$_POST 等数组开始。写一个接收用户输入并直接输出的页面——这是最基础的"反射"场景:
$_REQUEST 合并了 GET、POST、COOKIE 三种输入。代码审计时如果看到 $_REQUEST,意味着攻击者可以通过三种方式传递 payload——不仅 URL 参数能触发漏洞,POST 表单和 Cookie 也能触发,攻击面比单独 $_GET 大三倍。上面这段代码看起来人畜无害——用户输入名字,页面回显问候语。但如果把 URL 改成 input.php?name=<script>alert(1)</script>,浏览器会把这段 JavaScript 当成代码执行——这就是最原始的反射型 XSS。PHP 直接把用户输入拼到 HTML 输出中,没有任何转义。这是模块 2 要深入讲的 XSS 漏洞,本篇先建立"输入即不可信"的认知。
上一章你理解了"输入从哪来"——超全局数组。本章关注"输入流向哪去"——危险函数。PHP 的危险函数分为三大类:代码执行类、命令执行类、文件包含类。每一类对应不同的漏洞类型,但共同特征是:如果用户输入能到达这些函数且未经严格过滤,漏洞就成立。
先看代码执行类——eval() 是最危险的函数,它把字符串当作 PHP 代码执行:
访问 eval_vuln.php?code=system("id"); 时,eval() 把 system("id") 当作 PHP 代码执行,system() 又执行了系统命令 id——两层调用,直接 RCE(Remote Code Execution,远程代码执行)。这就是为什么 eval() 被称为"PHP 危险函数之王"。
命令执行类函数有四个:system()、exec()、passthru()、shell_exec()。它们都能执行系统命令,区别在于输出处理方式不同:
| 函数 | 输出方式 | 返回值 | 利用难度 |
|---|---|---|---|
system() |
直接输出到页面 | 最后一行 | 最易利用 |
exec() |
返回数组需手动输出 | 最后一行 | 需构造回显 |
passthru() |
原始输出二进制数据 | 无 | 最易利用 |
shell_exec() |
返回字符串需echo | 完整输出 | 需构造回显 |
第三类是文件包含类——include、require、include_once、require_once。这些函数把另一个文件的内容插入当前 PHP 文件执行。如果文件路径可控,攻击者可以包含任意文件——这叫"文件包含漏洞"(LFI/RFI):
include(),路径完全可控,攻击者可包含任意文件$whitelist = ['about','contact','home']; if(in_array($page,$whitelist)){include("pages/$page.php");}PHP 5.3.4 起默认关闭了 allow_url_include,RFI(远程文件包含)在现代 PHP 中已基本不可用。但 LFI(本地文件包含)仍然常见——攻击者通过 ../ 目录跳转读取 /etc/passwd、/var/log/auth.log 等敏感文件。PHP 8.x 移除了 %00 截断特性,但 pearcmd.php 包含等新利用链仍在涌现。
你已经理解了危险函数的利用条件——用户输入到达危险函数且未过滤。现在把这个分析框架应用到 Web 漏洞中最经典的 SQL 注入。SQL 注入的本质是:PHP 把用户输入拼接到 SQL 语句字符串中,用户输入中的 SQL 特殊字符(单引号、分号、注释符)改变了 SQL 语句的语义。
先搭建一个有漏洞的 PHP 页面。用 PHP 内置服务器 + SQLite 数据库(无需安装 MySQL,Kali 自带 SQLite 扩展):
这段代码的污点追踪链:$_GET['id'] → $id → 字符串拼接进 $sql → query() 执行。整条链路没有任何过滤。看正常请求和注入请求的区别:
正常请求 ?id=1 生成 SQL SELECT * FROM users WHERE id=1——只返回 id=1 的用户。注入请求 ?id=1 OR 1=1 生成 SQL SELECT * FROM users WHERE id=1 OR 1=1——1=1 恒为真,WHERE 条件失效,返回所有用户。这就是 SQL 注入的本质——用户输入改变了 SQL 语义。
更危险的 payload 是联合查询注入(UNION-based)——用 UNION SELECT 拼接额外的查询结果:
第三步 id=-1 让原查询返回空(id=-1 不存在),UNION SELECT 的结果占满输出位置——这是 UNION 注入的标准技巧。修复方案是使用 PDO 预处理语句(prepared statement),把数据和 SQL 结构分离:
? 是占位符,execute([$id]) 把 $id 作为数据绑定——数据库引擎知道 1 OR 1=1 是一个字符串值而非 SQL 语法,不会改变 WHERE id=? 的结构。预处理是防御 SQL 注入的根本方案——不是过滤输入,而是让输入永远不可能成为 SQL 语法的一部分。
$_GET、$_POST、$_REQUEST,追踪变量是否进入了 query()、exec() 等执行函数。如果中间出现了 prepare(),说明用了预处理——基本安全。如果看到字符串拼接(. 连接),说明有注入风险。上一章你理解了 SQL 注入的代码层面成因——字符串拼接。本章看另一种高危漏洞:文件上传。文件上传漏洞的核心是:PHP 服务器允许用户上传文件,但如果对文件类型、内容、存储路径检查不严,攻击者可以上传一个 .php 文件(webshell),然后通过 URL 访问它执行任意命令。
先看一个有漏洞的文件上传代码——它只检查了文件扩展名,但检查方式有缺陷:
这段代码用黑名单过滤——只禁止 php 和 phtml 扩展名。但 PHP 能执行的扩展名远不止这两个。以下是常见的绕过方式:
| 绕过方式 | 扩展名 | 原理 |
|---|---|---|
| 大小写绕过 | .PHP / .Php |
Windows 不区分大小写 |
| Apache解析 | .php5 / .pht |
httpd.conf 配置可执行 |
| 双扩展名 | shell.php.jpg |
Apache 从右向左解析 |
| 空字节截断 | shell.php%00.jpg |
PHP<5.3.4 空字节截断 |
| .htaccess | .htaccess |
上传配置文件改解析规则 |
一个典型的 webshell——上传后通过 URL 访问就能执行任意命令:
这行代码就是著名的"一句话木马"——只有一行 PHP 代码,@ 抑制错误输出,eval() 把 POST 参数 cmd 的值当作 PHP 代码执行。攻击者用浏览器或中国菜刀/蚁剑等工具连接,向 cmd 参数发送 system("whoami"),服务器就执行命令并返回结果。这是模块 4 后渗透阶段维持权限的常用手段。
安全修复方案是用白名单代替黑名单——只允许明确安全的文件类型,同时重命名上传文件、禁止上传目录执行 PHP:
修复版做了三件事:白名单只允许图片格式、strtolower() 统一小写防大小写绕过、random_bytes(16) 生成随机文件名防猜测路径。此外还应在 Apache/Nginx 配置中禁止 uploads 目录执行 PHP——这是纵深防御,即使绕过了应用层检查,Web 服务器也不会执行 .php 文件。
Content-Type,用 Burp Suite 一秒就能改成 image/jpeg 伪装。客户端传来的任何数据都不可信pathinfo() 从文件名提取扩展名 + 白名单过滤 + 服务端禁止执行PHP本篇所有漏洞复现必须在本地环境进行——Kali 上的 PHP 内置服务器、自己的虚拟机、或 DVWA 靶场。PHP 的危险函数 eval()、system()、include 在真实 Web 服务器上意味着 RCE——远程代码执行。如果在他人网站上测试 SQL 注入或上传 webshell,即使只是为了"验证漏洞是否存在",也已触犯《刑法》第 285 条非法侵入计算机信息系统罪。合法练习方式:本机 127.0.0.1、自己的云服务器、DVWA/Vulhub 靶场、PortSwigger Web Academy。
phpinfo.php 文件,内容为 <?php phpinfo(); ?>,浏览器访问确认 PHP 环境正常。然后修改第四章的 sqli_vuln.php,用浏览器访问 ?id=1 和 ?id=1 OR 1=1,对比两次输出的差异。upload_vuln.php 添加 MIME 类型检查($_FILES['file']['type']),验证这个检查能否被 Burp Suite 修改 Content-Type 绕过。提示:用 curl -F "file=@shell.php;type=image/jpeg" 模拟绕过。这是模块 2 Burp Suite 的预热练习。' OR 1=1-- 绕过认证),登录后有文件上传功能(黑名单过滤不完整)。然后审计你自己写的代码,标注每个漏洞的污点追踪链和修复方案。这是模块 2 DVWA 靶场练习的代码层面准备。