不能用strip_tags()做国际化html过滤,因为它不校验属性、协议、嵌套结构和大小写,会放行onclick、javascript:等危险内容,且无法处理动态插值、实体编码冲突及多语言语义属性(如lang/dir),导致xss漏洞。

为什么不能用 strip_tags() 做国际化 HTML 过滤
很多团队在做多语言富文本时,误以为 strip_tags($html, '<p><strong><em>') </em></strong></p> 就够了——它确实能留下标签,但完全不管属性、事件、协议和嵌套结构。比如用户提交:<p onclick="alert(1)">Hello</p>,strip_tags() 会原样保留 onclick;又比如 @#@#@#@#@#@#@#@#@#@0,链接照样可执行。
更麻烦的是国际化场景常含动态插值(如 "<b>{name}</b> logged in"),若插值前未净化,恶意内容直接注入 DOM;若插值后净化,又可能破坏已转义的实体(如把 © 变成 ©)。
- 它不解析 DOM,无法识别注释、CDATA、自闭合标签中的陷阱
- 对大小写不敏感(
<script></script>逃逸) - 不校验 URI 协议,
data:、vbscript:、javascript:全部放行 - 无法处理嵌套 XSS,例如
<img src="x" onerror="alert(1)">在<div> 内仍生效 <h3> <code>DOMPurify白名单配置必须显式声明协议与属性前端做国际化渲染时,
DOMPurify是唯一靠谱选择,但默认配置不安全。必须显式限制协议、禁止危险属性,并适配多语言常见需求(如支持lang、dir、title等语义属性)。示例配置(支持中/英/日/阿语等多语言排版):
const config = { ALLOWED_TAGS: ['p', 'br', 'b', 'i', 'u', 'em', 'strong', 'span', 'a', 'code', 'sub', 'sup'], ALLOWED_ATTR: ['href', 'title', 'lang', 'dir', 'class'], // 显式放开 dir="rtl"、lang="ar" 等 FORBID_ATTR: ['onerror', 'onclick', 'onload', 'javascript:', 'data-*'], FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'style', 'noscript'], ALLOWED_URI_REGEXP: /^https?:\/\//i, // 强制 http(s)://,拒绝 javascript: 或 mailto:(除非明确需要) KEEP_CONTENT: true };注意:
ALLOWED_URI_REGEXP比ALLOWED_PROTOCOLS更可靠,后者在某些版本里有绕过漏洞;lang和dir必须进ALLOWED_ATTR,否则多语言文本会丢失语义。后端净化不可省略,且必须与前端配置严格对齐
国际化内容常由 CMS 或翻译平台批量导入,前端 JS 可被绕过,所以服务端必须做同等强度净化。PHP 项目应使用
HTMLPurifier,并确保配置与前端白名单完全一致——不是“类似”,而是字段级对齐。
Html To Pdf下载使用 Puppeteer + Chrome 将 HTML 渲染为中文 PDF,自动处理图表等待、Tab 展开、动画、测高、白边消除、防分页,适用于看板、报表、网页和交互图表转 PDF。
关键配置项(PHP):
$config = HTMLPurifier_Config::createDefault(); $config->set('HTML.Allowed', 'p,b,i,u,em,strong,a[href|title|lang|dir],br,span[lang|dir|class]'); $config->set('URI.AllowedSchemes', ['http', 'https']); // 不要漏掉 https $config->set('Attr.AllowedFrameTargets', []); // 禁用 target="_blank"(除非加 rel="noopener") $config->set('HTML.Trusted', false); // 关键!禁用可信模式,否则 script 可能复活 $purifier = new HTMLPurifier($config);常见坑:
- 没设
HTML.Trusted = false→style标签可能被放行 - 前端允许
lang,后端没放开 → 多语言<span lang="ja">こんにちは</span>被删 - 前后端
ALLOWED_URI_REGEXP/URI.AllowedSchemes不一致 → 某些链接在预览时正常,上线后 403 或被截断 - 入库前净化一次,渲染前又调一次 → 实体重复编码(
→)
插值环节最容易被忽略的安全断裂点
国际化框架(如 i18next、vue-i18n)常提供 HTML 插值语法:
t('welcome', { name: '<b>admin</b>' })。这里name若来自用户输入,且未提前净化,就等于把 XSS 直接塞进模板。正确做法只有两种:
- 所有变量在传入
t()前,必须经DOMPurify.sanitize()处理(前端)或$purifier->purify()(后端) - 禁用 HTML 插值,改用纯文本占位 + 客户端 DOM 构建(如
<span data-i18n="welcome"></span>+ JS 注入安全节点)
绝对不要做:先插值再净化整个字符串——因为
t()返回的已是带标签的 HTML,再跑一遍DOMPurify可能破坏原有结构(比如把<b>{name}</b>中的{name}当作文本节点误删)。多语言 JSON 文件本身也要扫描:CI 流程中加入正则检测(如
/on\w+\s*=/i、/javascript:/i),防止翻译人员误填恶意内容。 - 没设










