HTMLPurifier不是开箱即用的XSS防护黑盒,必须白名单配置、禁用createDefault()、启用Core.RemoveScriptContents等关键选项,并在输出时按上下文二次转义。

HTMLPurifier 不是“引入就能防 XSS”的黑盒工具,配置错误或误用反而会制造假安全感。它只对明确允许富文本的字段有效,且必须配合上下文输出转义才真正安全。
为什么 htmlspecialchars 不够用?
它只转义 <、>、"、'、& 五个字符,对以下攻击完全无效:
-
<img src=x onerror=alert(1)>—— 事件属性不被转义 -
<a href="javascript:alert(1)">click</a>—— 危险协议未过滤 -
<span style="background:url(javascript:alert(1))"></span>—— CSS 注入绕过 -
<div data-onclick="alert(1)"></div>——data-*属性默认可能放行(老版本)
这些都属于富文本输入场景,htmlspecialchars 会把所有标签变成纯文本,破坏排版 —— 这不是你想要的。
HTMLPurifier 必须用白名单配置,禁用 createDefault()
HTMLPurifier_Config::createDefault() 默认允许太多危险行为:比如 style 标签、on* 事件、javascript: 协议、data-* 属性等。生产环境绝不能直接用。
立即学习“前端免费学习笔记(深入)”;
正确做法是显式声明白名单:
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'p,b,i,u,em,strong,a[href|title],ul,ol,li,blockquote');
$config->set('CSS.AllowedProperties', 'font,font-size,font-weight,color');
$config->set('HTML.Nofollow', true);
$config->set('Core.RemoveScriptContents', true); // 关键:移除 script 标签内容
$config->set('HTML.SafeIframe', true); // 如需嵌入视频,必须显式开启并配 AllowIframeHosts
$purifier = new HTMLPurifier($config);
注意:Core.RemoveScriptContents 和 HTML.SafeIframe 是两个常被忽略但影响安全的关键开关。
Laravel 或 ThinkPHP 中别每次 new 实例
每次 new HTMLPurifier($config) 都会重复解析配置、重建内部缓存,QPS 高时性能明显下降,且无法共享配置状态。
推荐方案:
- Laravel:用
mews/purifier包,它已注册为单例,Purifier::clean()复用同一实例 - ThinkPHP:在
app\common\provider\AppServiceProvider.php中通过app()->bind()绑定单例 - 原生 PHP:封装为静态服务类,
PurifyService::instance()返回复用实例
避免在 validate 规则或中间件里临时 new,那等于放弃缓存和配置一致性。
最易被忽略的一环:输出时仍要 htmlspecialchars
即使 $purifier->purify() 返回的是“干净 HTML”,它仍是用户可控内容。若你把它拼进 JavaScript 字符串、URL 参数或 HTML 属性值中,依然会触发 XSS。
例如:
// ❌ 危险:直接插入 HTML 属性 echo '<div data-content="' . $clean_html . '"></div>'; // ✅ 正确:按上下文转义 echo '<div data-content="' . htmlspecialchars($clean_html, ENT_QUOTES, 'UTF-8') . '"></div>';
记住:HTMLPurifier 解决的是「富文本输入」的安全问题;而输出位置的转义,解决的是「上下文注入」问题 —— 两者不是二选一,是必须共存的两道防线。



















