必须对用户数据做上下文敏感转义:HTML文本内容用HTML实体转义,属性值同理且须加引号,JS字符串需额外JS转义,不可仅靠HTML转义。

HTML 输出时必须对用户数据做上下文敏感转义
直接把用户输入拼进 HTML 页面,等于给攻击者送弹药。关键不是“要不要转义”,而是“在哪个环节、按什么规则转义”。innerHTML、document.write、服务端模板(如 EJS、Jinja)中插值,都必须根据输出位置选择对应转义方式:
- 插入到 HTML 文本内容(如
<div>${userInput}</div>)→ 用 HTML 实体转义:&→&,→ <code>,<code>>→>,"→",'→' - 插入到 HTML 属性值(如
<input value="${userInput}">)→ 同样需 HTML 实体转义,且属性必须用引号包裹(单/双均可,但不加引号会绕过部分过滤) - 插入到 JavaScript 字符串(如
<script>var name = "${userInput}";</script>)→ 不能只靠 HTML 转义,必须额外做 JS 字符串转义(如\u003c代替),或改用 JSON 序列化:<code>JSON.stringify(userInput) - 插入到 URL 参数(如
<a href="/search?q=${userInput}">)→ 必须用encodeURIComponent(),而非encodeURI()或手动替换
CSP 的 default-src 和 script-src 必须显式收紧
默认不设 CSP 等于没设;只设 default-src 'self' 也不够——它不控制 script-src,而后者才是 XSS 的主战场。常见错误是以为 “开了 CSP 就安全了”,结果配置留了后门:
-
script-src 'self' 'unsafe-inline' 'unsafe-eval'≈ 没开 CSP:内联脚本和eval直接复活 XSS - 允许
https:或通配符*→ 外部任意 JS 可被加载执行 - 遗漏
unsafe-hashes却写了内联onclick→ 浏览器仍会拦截,但开发者误以为“已授权” - 正确姿势:从最小集起步,例如
script-src 'self' 'nonce-27f844a5',配合服务端为每个响应生成唯一nonce值,并写入<script nonce="27f844a5">...</script>
前后端都要校验,但校验 ≠ 过滤
前端正则过滤 <script></script> 标签、后端用 strip_tags() 删除 HTML —— 这类操作看似防御,实则极易被绕过(比如大小写混淆、注释绕过、Unicode 编码)。真正有效的校验是白名单 + 上下文感知:
- 前端仅作体验层辅助(防误触),不可信;所有校验逻辑必须在服务端重复执行
- 对富文本需求,不用自研过滤器,用成熟库如
DOMPurify.sanitize()(并开启{USE_PROFILES: {html: true}}) - 对纯文本字段(如用户名、评论),服务端应拒绝含控制字符、不可见 Unicode、或 HTML 实体编码的输入(如
<img alt="如何防止XSS攻击_转义与CSP双重防护【方法】" >),而不是自动解码再“清理” - 数据库存储前不做转义,渲染时才按上下文转义——存编码后的数据反而增加二次解码风险
HTTP-only + Secure Cookie 是防窃取的底线
即使 XSS 成功,如果敏感 Cookie(如 sessionid)没设 HttpOnly,攻击者就能用 document.cookie 直接盗走;若没设 Secure,HTTPS 页面里也可能被 HTTP 中间人劫持。这不是 CSP 或转义能覆盖的维度:
- 后端设 Cookie 时必须包含
HttpOnly(禁 JS 访问)和Secure(仅 HTTPS 传输)标志 - 现代应用还应加上
SameSite=Strict或Lax,防 CSRF 同时也限制跨源 Cookie 发送场景 - 前端避免用
fetch或XMLHttpRequest显式读写敏感 Cookie;JWT 存 localStorage 是高危做法,应优先用 HttpOnly Cookie
report-uri 或 report-to 很容易被忽略,但它能让你第一时间发现策略是否被绕过、哪些页面漏了 nonce、有没有旧代码偷偷用了 eval —— 不配监控的 CSP,就像没装刹车片的车。

















