一行注释骗过25年模板引擎:Smarty缓存投毒RCE深度复现(CVE-2026-82531,附PoC)
原创
赤霄攻防实验室
赤霄攻防实验室
赤霄攻防实验室
2026年10月9日 09:16
河北
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
CVE-2026-82531 · Smarty 模板引擎 nocache 标记伪造缓存投毒 RCE
TL;DR 四行速览
- 是什么:
PHP 最老牌模板引擎 Smarty 的代码注入漏洞(CVE-2026-82531),继承模板编译路径漏掉一个哈希复位语句,攻击者可用一行注释伪造引擎内部标记,把原始 PHP 逐字写进缓存文件
* 影响:
Smarty 5.x 全系(<5.8.5)与 4.x(<4.5.8),CVSS 4.0 高达 9.2;PrestaShop 等大量 CMS 与自建站点底层在用
* 修复:
升级到 2026-10-04 发布的 5.8.5 / 4.5.8,官方同时合入回归测试锁死利用链
* 看点:
我们在本地 5.8.4 环境完整复现了「编译落盘→缓存投毒→include 执行」全链路,注入代码真实运行;5.8.5 上同一载荷被中和——一个 null 引爆的审计盲区,值得所有写模板引擎的人看三遍
漏洞概要信息卡:CVE-2026-82531 关键事实一览
01 一行注释,骗过了二十年的模板引擎
Smarty 是 PHP 世界最老牌的编译型模板引擎,2001 年立项至今二十五年,PrestaShop 等知名电商系统与无数自建站点的页面渲染都跑在它上面。它的核心机制是「编译 + 缓存」:模板先被编译成 PHP 文件,渲染结果再整体缓存,下一个访客直接 include 缓存文件——性能是快了,但缓存文件本身就成了攻击者的目标。
这次出问题的,是引擎用来区分「可缓存内容」和「不可缓存内容」的内部标记。Smarty 用一对形如注释的标记(SmartyNocache 标记对,中间夹一串随机哈希)包住每次请求都要重新计算的部分,写缓存时靠这段哈希识别哪些内容需要单独抽取。问题在于:通过 extends: 继承语法编译的模板,顶层编译产物里恰恰漏掉了哈希复位语句。
于是出现了一个诡异的连锁反应:新进程从磁盘重载编译文件时,nocache\_hash 属性保持 null;写缓存时这个 null 被折进正则表达式,变成一个匹配「空哈希标记」的分支。也就是说,攻击者只要在能回显到页面的数据里,塞进一对带空哈希的伪造标记——不需要知道真实哈希是什么——引擎就会把标记里的内容当成「受保护的 nocache 段」,逐字拷进缓存文件,完全绕过本应执行的 PHP 标签中和。
攻击链全貌:从一条用户输入到服务器代码执行
02 根因:编译器少发了一条语句
把镜头拉近到源码。Smarty 编译模板时会生成一个统一的入口函数(unifunc),正常模板的入口函数开头有一条复位语句,把本次编译的 nocache\_hash 写回 Compiled 对象。但 extends:/多组件继承路径构建顶层入口时,只从 include 标签出发拼装,这条复位语句永远不会出现(src/Compiler/Template.php 411-430 行与 1255-1256 行的差异就在这里)。
更巧的是,从磁盘重载编译文件的那段代码(src/Template/Compiled.php 290-293 行)只恢复了 unifunc、has\_nocache\_code、file\_dependency 三个属性——nocache\_hash 不在名单里。单进程内这个问题被掩盖了:编译和渲染在同一个对象上,哈希还在内存里。一旦换成 PHP-FPM 的真实世界,每个 worker 都是新进程,哈希永远回不来。
最后一步在缓存写入处(src/Template/Cached.php 303-315 行):null 哈希被当成数组下标,产生空字符串键,implode 之后正则里多出一个空分支 (真实哈希|)。正则里出现空分支意味着什么?意味着它可以匹配一对完全不带哈希的裸标记——而这对标记,任何人都能写出来。
根因对照:漏洞版与修复版在缓存切分逻辑上的差异
为什么二十多年没被发现
回头看,这个漏洞藏在三个环节的耦合处:编译器按路径生成不同的入口结构、重载逻辑只挑了三个属性恢复、正则拼接对 null 零防御。单看每一处都「没什么问题」,组合起来才是完整的利用链。这也是它值得所有基础组件维护者警惕的地方:状态在对象生命周期里的传递,比任何单点代码都更容易漏。
03 官方响应:一次教科书式的修复
2026 年 10 月 4 日,维护者 Simon Wisselink 在 4.x/5.x 两条分支同时发布 4.5.8 与 5.8.5,修复 commit 直白地命名为「防止 nocache 标记伪造导致的缓存投毒 RCE」。修复没有打补丁式的缝缝补补,而是三层防御:空哈希不再折入正则;array\_filter 强制过滤空键,空分支在结构上不可能再出现;哈希表为空时整段输出按普通文本全量中和。
更值得称赞的是官方同时合入了回归测试 NocacheHashInjectionTest:完整重放「良性编译→缓存过期→恶意数据重生成→良性请求触发」三阶段利用链,断言哨兵文件永不被创建。安全修复配测试锁死,这才是基础组件该有的姿势。
官方修复 commit:c0fdd48,双分支同日修复
04 本地复现:注入代码真实执行
我们在本地搭了对照环境:同一台机器、同一个 PHP 8.1,分别跑 Smarty 5.8.4(漏洞版)与 5.8.5(修复版)。利用条件完全对齐官方公告——开启缓存(CACHING\_LIFETIME\_CURRENT)、extends: 继承模板、攻击者可控数据未转义回显。
5.8.4 上的结果:第二阶段用携带伪造标记的赋值数据重新生成缓存时,注入的 PHP 代码立即执行——哨兵文件当场生成;缓存文件里躺着未中和的原始 PHP,下一个「良性」请求 include 缓存时它还会再执行一次。判定:VULNERABLE。
5.8.5 上跑同一载荷:伪造标记还在数据里,但引擎不再把它当作 nocache 段,PHP 标签被正常中和成纯文本输出,哨兵文件自始至终不存在。判定:PATCHED。完整验证脚本(三态结论输出:VULNERABLE / PATCHED / UNKNOWN)已开源,见文末参考链接。
05 谁该立即行动
三类站点风险最高:一是 PrestaShop 等 Smarty 内核的电商系统——商品评论、搜索回填正是「用户数据未转义回显」的高发区;二是任何开启 Smarty 缓存且模板使用 extends: 继承的自建站点;三是模板文件或数据来源部分受用户影响的 SaaS 平台。自查方法很简单:composer 看锁定版本,低于 5.8.5 / 4.5.8 即在影响范围。
处置优先级:能升级的立刻升级,5.x 内是平滑的补丁版本;暂不能升级的,对回显变量开启 escape\_html 转义、清空全部缓存目录(存量缓存可能已被投毒)、临时开启 compile\_check 增加重编译校验。注意:只清缓存不升级是无效的,下一个攻击载荷会重新投毒。
06 情报源
NVD 收录页:CVE-2026-82531,CVSS 4.0 9.2 / 3.1 8.1
GitHub Security Advisory:GHSA-3w63-v7pm-cq9x,含完整根因路径
本次分析的信息源与验证材料:
· NVD:https://nvd.nist.gov/vuln/detail/CVE-2026-82531
· GitHub Advisory:https://github.com/smarty-php/smarty/security/advisories/GHSA-3w63-v7pm-cq9x
· 修复 commit:https://github.com/smarty-php/smarty/commit/c0fdd4824aca4f67e814b50703cfee1dfde41414
· 验证脚本:https://github.com/Rubby2001/CVE-POCS/tree/main/CVE-2026-82531
· 官方发布:Smarty 5.8.5 / 4.5.8(2026-10-04)
本文仅用于防御研究与安全教学,分析基于公开披露信息与本地受控环境验证,请勿对未授权目标进行测试。
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。











评论