ThinkPHP的{$name}语法默认不自动转义HTML,是框架设计而非漏洞;应使用{:$data.content}跳过转义或htmlspecialchars_decode解码,富文本须用HtmlPurifier白名单过滤。

模板变量输出不自动转义的默认行为
ThinkPHP 的 {$name} 语法默认不做 HTML 转义,这不是 bug,是框架明确的设计选择:把上下文安全责任交还给开发者。所以你看到 <p>Hello</p> 被原样渲染成文本,不是因为“被转义了”,而是因为你没告诉模板引擎“这段内容要当 HTML 解析”。
关闭转义的两种可靠写法
必须用冒号前缀或显式解码,不能依赖配置项“关掉”整个转义逻辑:
-
{:$data.content}—— 最直接,跳过所有默认过滤,原始 HTML 直出 -
{:htmlspecialchars_decode($data.content)}—— 仅当内容已被错误地 double-encoded(比如存库时多套了一层htmlspecialchars())才需要 - 别碰
template.default_filter配置去“全局关闭”,它会破坏 JSON 字段、URL 参数等非 HTML 上下文的输出
为什么 htmlspecialchars() 不该在入库前调用
这是开发中最常踩的坑:把用户提交的富文本先 htmlspecialchars() 再存进数据库,结果页面上永远显示 <p>。
将 Markdown 文章转换为微信公众号兼容的纯 HTML 格式。当用户要求"转成微信格式"、"生成公众号文章 HTML"、"排版到微信草稿箱"、"微信粘贴格式"时触发。输出带内联样式的纯 HTML,微信编辑器可直接渲染,无需额外适配。支持标题、段落、列表、粗体、行内代码、表格、引用块、代码块、配图的完整转换。
- HTML 标签应以原始形态入库(字段类型设为
TEXT或MEDIUMTEXT) - 只在输出到 HTML 上下文时做一次转义或白名单过滤(富文本用
HtmlPurifier) - 用
Db::execute()拼 SQL 或create()前手动过滤,都会导致数据失真
富文本字段不能用 htmlspecialchars() 替代过滤
想靠 htmlspecialchars() 防 XSS?它会把合法的 <p><strong>Hi</strong></p>
<p><span>立即学习</span>“<a href="https://pan.quark.cn/s/7fc7563c4182" style="text-decoration: underline !important; color: blue; font-weight: bolder;" rel="nofollow" target="_blank">PHP免费学习笔记(深入)</a>”;</p> 变成纯文本,彻底破坏格式。
- 必须引入
HtmlPurifier,配白名单:$config->set('HTML.Allowed', 'p,strong,ul,ol,li,a[href|title],img[src|alt]') - 禁用正则删
<script></script>——绕过方式太多,<scr>ipt></scr>就能逃逸 - 前端编辑器(如 Tiptap)的 XSS 过滤只是辅助,服务端校验才是最后一道防线
{:$data.content} 却仍显示源码,大概率是因为你用了 display() 但控制器提前输出了 HTTP header,或者中间件已拦截并返回了 JSON;这种情况下 layout 和变量输出机制都已失效,得先检查响应头和返回路径。


















