不能用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;又比如 <a href="javascript:alert(1)">click</a>,链接照样可执行。
更麻烦的是国际化场景常含动态插值(如 "<b>{name}</b> logged in"),若插值前未净化,恶意内容直接注入 DOM;若插值后净化,又可能破坏已转义的实体(如把 © 变成 ©)。
<SCRIPT> 逃逸)data:、vbscript:、javascript: 全部放行<img src=x onerror=alert(1)> 在 <div> 内仍生效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,并确保配置与前端白名单完全一致——不是“类似”,而是字段级对齐。
关键配置项(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()(后端)<span data-i18n="welcome"></span> + JS 注入安全节点)绝对不要做:先插值再净化整个字符串——因为 t() 返回的已是带标签的 HTML,再跑一遍 DOMPurify 可能破坏原有结构(比如把 <b>{name}</b> 中的 {name} 当作文本节点误删)。
多语言 JSON 文件本身也要扫描:CI 流程中加入正则检测(如 /onw+s*=/i、/javascript:/i),防止翻译人员误填恶意内容。