ThinkPHP 3.2.2变量覆盖漏洞主因是assign()参数可控、I()来源混用及extract()滥用,导致$filename等关键变量被覆盖引发LFI/RCE;修复需限制assign首参合法性、强制I()显式来源与过滤、禁用extract()及${}、入口拦截高危参数并关闭php.ini危险配置。

ThinkPHP 3.2.2 的变量覆盖漏洞主要源于框架设计中对用户输入的宽松处理,尤其是 assign() 方法参数可控、I() 函数来源混用、extract() 随意调用等行为,导致攻击者可覆盖关键变量(如 $filename、$cacheFile、$template),进而触发任意文件包含或远程代码执行。
修补需聚焦源头过滤、调用约束、上下文隔离三个层面,不依赖版本升级(因 3.2.x 已停止维护),而是通过代码层加固阻断利用链。
关键修复点:限制 assign() 第一个参数的合法性
ThinkPHP 3.2.x 中 assign() 若接收数组且第一个参数可控(如 $this->assign(I('data'))),会直接合并到模板变量数组 $this->tVar。后续在模板解析时经 extract($this->tVar),可能覆盖内部变量(如 $filename),造成 LFI 或 RCE。
- 禁止将用户输入直接作为
assign()的第一个参数
❌ 错误写法:$this->assign(I('data'));✅ 正确做法:
- 显式指定键名和值,拒绝动态数组注入
$data = I('data/a', '', 'htmlspecialchars'); // 白名单字段 + 过滤 $this->assign('data', $data); - 或重写
assign()方法,在类中加校验(如CommonController基类):public function assign($name, $value = null) { if (is_array($name)) { // 拒绝非预设键名的数组赋值 $allowed_keys = ['title', 'content', 'user_info']; foreach ($name as $k => $v) { if (!in_array($k, $allowed_keys)) { unset($name[$k]); } } } parent::assign($name, $value); }
- 显式指定键名和值,拒绝动态数组注入
严格约束 I() 和 input() 的使用方式
I() 默认按 GET→POST→COOKIE→SESSION→SERVER 顺序取值,极易被低优先级来源污染;若未指定过滤器或来源,就是变量污染入口。
-
所有
I()调用必须显式声明来源和过滤器
❌ 危险写法:$id = I('id'); // 来源不确定,无过滤✅ 强制规范:
$id = I('get.id', 0, 'intval'); // 仅从 GET 取,强制转整型 $name = I('post.name', '', 'htmlspecialchars'); // POST 专属,XSS 过滤 $type = I('request.type', 'list', '/^[a-z]+$/i'); // 正则白名单 -
禁用
$_REQUEST全局混用,统一改用$_GET/$_POST显式获取
在入口文件(如index.php)顶部加:unset($_REQUEST); // 彻底禁用,避免审计盲区
消除 extract() 和可变变量风险
3.2.2 中多处存在 extract($_POST) 或 ${$key} 类型代码(如日志、缓存模块),这是变量覆盖最直接的通道。
-
全局搜索项目中所有
extract(、${、$$,逐个替换-
extract($_POST)→ 改为白名单赋值:$allowed = ['title', 'status', 'sort']; foreach ($allowed as $field) { if (isset($_POST[$field])) { $$field = htmlspecialchars($_POST[$field]); } } -
${$key}→ 确保$key是硬编码或白名单内字符串,禁止来自用户输入
-
-
检查模板引擎调用逻辑(如
fetch()),确认$template、$cacheFile等路径变量不参与用户拼接
若需动态模板名,限定范围:$tpl = basename(I('get.t', 'index')); // 去路径、去扩展名 $this->display($tpl . '.html'); // 仅允许当前目录下 .html 文件
补充防护:入口拦截与运行时加固
即使代码层已收敛,仍需在请求入口设“最后一道闸”。
-
在
index.php或App/Common/Conf/config.php加全局过滤:// 拦截高危参数 if (isset($_GET['s']) || isset($_POST['s'])) { $s = $_GET['s'] ?? $_POST['s']; if (preg_match('/(think|call_user_func|eval|assert|system|exec|shell_exec)/i', $s)) { die('Access Denied'); } } // 禁用危险配置(确保 config.php 中) 'show_error_msg' => false, 'app_debug' => false, 'log_record' => false, -
关闭 PHP 低版本遗留风险(检查 php.ini):
register_globals = Off allow_url_include = Off magic_quotes_gpc = Off
不复杂但容易忽略。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











