一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

HTML国际化如何安全?过滤机制工程化规范

时间:2026-07-10 12:08:52 编辑:袖梨 来源:一聚教程网

不能用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;若插值后净化,又可能破坏已转义的实体(如把 © 变成 ©)。

  • 它不解析 DOM,无法识别注释、CDATA、自闭合标签中的陷阱
  • 对大小写不敏感(<SCRIPT> 逃逸)
  • 不校验 URI 协议,data:vbscript:javascript: 全部放行
  • 无法处理嵌套 XSS,例如 <img src=x onerror=alert(1)><div> 内仍生效

DOMPurify 白名单配置必须显式声明协议与属性

前端做国际化渲染时,DOMPurify 是唯一靠谱选择,但默认配置不安全。必须显式限制协议、禁止危险属性,并适配多语言常见需求(如支持 langdirtitle 等语义属性)。

示例配置(支持中/英/日/阿语等多语言排版):

立即学习“前端免费学习笔记(深入)”;

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_REGEXPALLOWED_PROTOCOLS 更可靠,后者在某些版本里有绕过漏洞;langdir 必须进 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 = falsestyle 标签可能被放行
  • 前端允许 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 流程中加入正则检测(如 /onw+s*=/i/javascript:/i),防止翻译人员误填恶意内容。

热门栏目