thinkphp xss防御核心是按上下文显式转义:html内容用htmlspecialchars($var, ent_quotes, 'utf-8'),属性值用htmlentities($var, ent_quotes, 'utf-8'),js内嵌用json_encode($var, json_unescaped_unicode | json_hex_tag),富文本必须用htmlpurifier白名单过滤,禁用default_filter和控制器层预过滤。

ThinkPHP 的 XSS 防御不是靠“开个开关”就能搞定的,default_filter 配置看似省事,但绕过它太容易——直接读 $_GET、从数据库查完就 echo、JSON 输出里拼字符串,全都会漏防。真正有效的做法是:所有用户数据在**最终输出到 HTML/JS/属性/URL 前**,按上下文显式转义或净化。
模板变量不自动转义,{$var} 是高危操作点
ThinkPHP 模板引擎默认不转义 {$var},这是多数 XSS 漏洞的起点。别信“框架已处理”的错觉,尤其当变量来自数据库、缓存或第三方 API 时。
-
{$user_input}直接渲染,攻击者提交<script>alert(1)</script>就执行 -
{$var|htmlspecialchars}看似安全,但缺ENT_QUOTES参数 → 单引号不转义,onerror="alert(1)"可绕过 - 富文本内容(如后台编辑器)不能用
htmlspecialchars(),否则<p></p>变成<p>,页面显示源码 - 正确写法:
{:htmlspecialchars($var, ENT_QUOTES, 'UTF-8')}或{:$var|htmlentities=ENT_QUOTES}
htmlspecialchars() 必须带全参数,且只用于 HTML 内容上下文
漏传参数或错用场景,等于没防。比如把 htmlspecialchars() 用在 JS 字符串里," 被转成 ",JS 解析直接报错;用在 URL 参数里,空格变 + 或 %20,链接失效。
- HTML 文本内容:用
htmlspecialchars($s, ENT_QUOTES, 'UTF-8') - HTML 属性值(如
title="{$title}"):必须用htmlentities($s, ENT_QUOTES, 'UTF-8'),它比htmlspecialchars()多转单引号和部分 Unicode 控制字符 - JS 字符串内嵌:用
json_encode($s, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG),别自己拼" + {$var} + " - URL 参数值:用
urlencode($s),不是htmlspecialchars()
富文本必须用 HTMLPurifier 白名单过滤,不是 strip_tags() 或 remove_xss()
strip_tags() 只删标签不校验结构,remove_xss() 是 ThinkPHP 旧版扩展,规则简陋且已废弃。攻击者用 <img src="x" onerror="alert(1)"> 或 <div style="max-width:90%"> 仍可触发。
<ul>
<li>不要 <code>composer global require ezyang/htmlpurifier,要 composer require ezyang/htmlpurifier 到项目本地
HTMLPurifier_Config::createDefault(),它默认放行危险属性(如 style、data-*)Core.RemoveScriptContents(删 script 标签及内容)、HTML.SafeIframe(若需嵌 YouTube)app\common\provider\AppServiceProvider.php 中用 app()->bind('purify', function () { ... })
控制器层提前过滤是反模式,input() 不等于已安全
有人习惯在 Controller 里对所有 input() 结果调 htmlspecialchars() 再 assign(),这会让变量无法再用于 JS 输出或生成 URL,反而制造新漏洞。
-
input('post.content')返回的是原始字符串,框架不做任何过滤 ——default_filter只影响该函数返回值,不影响后续处理 - 同一变量可能同时用于
<h1>{$title}</h1>和data-title="{$title}",两种上下文需不同转义方式 - 数据库存原始 HTML 没问题,但读出来后绝不能裸 echo;也别用
html_entity_decode()“还原”,它不防 XSS,只解码 - 真正干净的数据流是:输入 → 验证(如
Validate规则)→ 存储 → 读取 → 按输出位置决定怎么转义/净化
最常被忽略的一点:XSS 防御成败不在“有没有过滤”,而在“是否在正确的时机、用正确的函数、传正确的参数”。哪怕只漏掉一个 ENT_QUOTES,或在一个 iframe src 属性里用了 htmlspecialchars(),攻击链就成立了。











