本文目录导读:

- 第一步:绘制数据流图
- 第二步:应用 STRIDE 模型分析威胁
- 第三步:针对 PHP 特有弱点的专项威胁分析
- 第四步:落地缓解措施(以威胁为驱动)
- 实战案例:一个简单的 PHP 登录系统威胁建模
- 推荐的威胁建模工具与工作流
- 总结:PHP 威胁建模的“三步走”
在 PHP 开发中进行威胁建模,通常不是指用某种特定的工具扫描代码,而是指在系统设计阶段,系统性地识别、分类并缓解潜在的安全风险。
针对 PHP 项目,威胁建模的核心思路通常遵循 STRIDE 模型,并结合 PHP 特有的“坑”(如类型松散、超全局变量滥用、反序列化等)。
以下是针对 PHP 项目的系统性威胁建模指南,分为四个步骤:
第一步:绘制数据流图
这是最重要的一步,你需要明确:
- 数据入口: 用户输入(
$_GET、$_POST、$_COOKIE、$_FILES)、API 请求、Webhook、队列消息、第三方服务回调。 - 数据出口: SQL 查询、文件写入、日志记录、API 响应、邮件发送、命令行执行(
exec)。 - 信任边界: 浏览器与服务器之间、服务器与数据库之间、服务器与文件系统之间、不同微服务之间。
PHP 特有场景: 不要让数据直接进入以下危险函数而不经过过滤:
include $user_input(本地文件包含)eval($user_input)/preg_replace的/e修饰符 /assert($user_input)unserialize($user_input)(对象注入)
第二步:应用 STRIDE 模型分析威胁
针对每个数据流,使用以下六类威胁进行头脑风暴:
| 威胁类别 | PHP 典型攻击实例 | 涉及的关键函数/特性 |
|---|---|---|
| Spoofing(伪装) | 会话固定、会话劫持、CSRF | session_id()、$_COOKIE、缺少 CSRF Token |
| Tampering(篡改) | SQL 注入、XSS、文件上传绕过 | 字符串拼接 SQL、htmlspecialchars() 使用不当、MIME 类型校验缺失 |
| Repudiation(否认) | 缺少审计日志,或日志记录敏感信息(如原密码) | error_log()、file_put_contents() |
| Information Disclosure(信息泄露) | 目录遍历、路径泄露、异常堆栈暴露 | file_get_contents($path)、ini_set('display_errors', '1')(生产环境) |
| Denial of Service(拒绝服务) | 正则表达式灾难(ReDoS)、大文件上传、无限制的循环/递归 | preg_match() 嵌套量词、move_uploaded_file() 未限制大小 |
| Elevation of Privilege(提权) | 反序列化漏洞(POP链)、弱口令、未授权访问后台 | unserialize()、__destruct()、__wakeup() 魔术方法 |
第三步:针对 PHP 特有弱点的专项威胁分析
PHP 不像 Java 那么强类型,很多安全问题源于语言特性,威胁建模时必须加入以下专项检查:
魔术方法与反序列化(最危险)
- 威胁: 用户控制
$_COOKIE或$_POST传入unserialize(),攻击者构造恶意对象,利用__destruct()或__wakeup()执行任意代码。 - 缓解措施:
- 永远不要反序列化用户输入。
- 若必须,使用
allowed_classes参数限制类白名单。 - 考虑使用 JSON 替代 PHP 序列化。
类型松散比较( 与 )
- 威胁:
0e开头的字符串(如"0e123")与0比较时结果为true,这常用于绕过密码校验(MD5 哈希以0e开头)。 - 缓解措施: PHP 8.0+ 已修复字符串与数字比较的弱点,但所有身份验证逻辑必须使用 (严格比较)。
文件包含与路径解析
- 威胁: 远程文件包含(RFI)和本地文件包含(LFI)。
include变量未过滤,读取/etc/passwd或执行远程脚本。 - 缓解措施: 使用
realpath()解析路径,并严格校验白名单(switch($page) { case 'home': case 'about': }而非include $page)。
extract() / parse_str() 变量覆盖
- 威胁: 未加第二个参数的
extract($_GET)会覆盖$is_admin变量。 - 缓解措施: 如果必须使用,使用前缀(
extract($arr, EXTR_PREFIX_ALL, 'GET')),或者干脆不使用。
第四步:落地缓解措施(以威胁为驱动)
根据威胁清单,为每个高风险威胁制定具体的防御策略,并落实到代码中:
| 风险等级 | 威胁描述 | PHP 具体防御策略 |
|---|---|---|
| 严重 | SQL 注入 | 强制使用 PDO 预处理语句(参数化查询),禁用字符串拼接。 |
| 严重 | 反序列化 RCE | 封禁 unserialize(),若必须使用,使用 allowed_classes 并校验 HMAC 签名。 |
| 高 | XSS 存储型/反射型 | 输出时统一使用 htmlspecialchars($var, ENT_QUOTES, 'UTF-8');前端配合 CSP 头(Content-Security-Policy)。 |
| 高 | 文件上传 Webshell | 使用 finfo_file() 检测 MIME 类型,不让用户控制文件名(重命名),文件存储目录禁止执行 PHP(Nginx 配置 location ~ \.php$ { deny all; })。 |
| 中等 | CSRF | 使用 hash_equals() 定时比较 Token(防时序攻击),配合 SameSite Cookie 属性。 |
| 中等 | 信息泄露 | 生产环境设置 display_errors=Off,日志写入 error_log 而非浏览器。 |
实战案例:一个简单的 PHP 登录系统威胁建模
数据流: 用户提交 POST → login.php → 验证 Session → 数据库查询 → 返回 JSON。
| 威胁 | 攻击点 | 缓解措施(代码级) |
|---|---|---|
| 伪装 | 攻击者篡改 Cookie 中的 Session ID | 设置 session_regenerate_id(true)(登录成功后),并设置 Secure; HttpOnly; SameSite=Lax |
| 篡改 | 在用户名输入 ' OR 1=1 -- |
使用 PDO::prepare() |
| 信息泄露 | 数据库连接错误直接输出 | try/catch 中只输出 “系统错误”,详细日志写入 $logger |
| 提权 | 直接访问 admin.php 绕过登录 |
在控制器最顶部加中间件或 if ($_SESSION['role'] !== 'admin') { exit; } |
| 拒绝服务 | 登录接口无频率限制,被爆破 | 引入 rate-limiter,且密码哈希使用 password_hash() 和 password_verify()(不可逆)。 |
推荐的威胁建模工具与工作流
-
自动化扫描(结合建模):
- 静态分析: 使用 PHPStan(Level Max)+ Psalm 检查类型问题,使用 PHP-CS-Fixer 检查敏感函数。
- 依赖检查: 使用 Composer 的
composer audit检查已知 CVE(漏洞库)。
-
动态验证(针对建模痛点):
使用 OWASP ZAP 或 Burp Suite 测试 SQL 注入/XSS 是否被堵住。
-
团队协作文档:
- 使用 OWASP Threat Dragon(免费开源)绘制 DF D图并标记 STRIDE 威胁。
- 在代码仓库的
SECURITY.md中明确“不允许使用的函数”清单(如eval、unserialize、extract)。
PHP 威胁建模的“三步走”
- 画图: 找到所有 超全局数组的出入口。
- 套模型: 用 STRIDE 对照,特别关注 PHP 的“魔法特性”(反序列化、松散比较、文件包含)。
- 设防御: 核心逻辑里禁止拼接 SQL、禁止 Unserialize 用户数据、输出必须转义。
威胁建模不是一次性的,而是在功能迭代时每次都过一遍的思维习惯,对 PHP 而言,“永远不信任 ” 就是最好的威胁建模基础。