XSS 的三种类型与防御
XSS 的本质是:攻击者的输入被浏览器当成了代码。按注入路径分三类,防御思路是相通的。
三种类型
存储型:恶意脚本存进数据库(如评论),每个访问者都会执行。危害最大,影响面最广。
反射型:脚本在 URL 参数里,服务端原样输出到页面。需要诱导用户点特定链接,常见于搜索结果页。
DOM 型:纯前端问题,服务端完全无辜:
// 反例:location.hash 直接进 innerHTML
document.getElementById('tip').innerHTML = '欢迎 ' + location.hash.slice(1);
防御的层次
第一层(最有效):输出编码。 数据回显到什么上下文,就用什么编码——这是 XSS 防御的核心原则:
- HTML 正文:
<>&"'实体化; - 属性值:加引号 + HTML 编码;
<script>内 / JSON:防</script>逃逸,用 JSON 库的转义而非手拼;- URL:
encodeURIComponent,而不是encodeURI。
第二层:CSP。 内容安全策略限制脚本来源,即使注入成功也难执行:
Content-Security-Policy: default-src 'self'; script-src 'self' cdn.example.com; object-src 'none'
注意 unsafe-inline 会大幅削弱 CSP,能不用就不用。CSP 是兜底不是替代,输出编码依然是第一优先。
第三层:框架默认安全。 React/Vue 默认对插值转义。危险操作集中在 dangerouslySetInnerHTML、v-html,评审时重点看这几处,并明确「为什么这里必须富文本」。
富文本怎么办
需要用户发 HTML(评论、文章)时,用白名单净化库(服务端 HTMLPurifier,前端 DOMPurify),绝不用正则过滤——绕过手段(属性拆分、大小写、编码嵌套)远超正则的表达能力。同时封禁 javascript: 伪协议、on* 事件属性、<iframe>。
Cookie 的最后一道防线
setcookie('sid', $v, ['httponly' => true, 'secure' => true, 'samesite' => 'Lax']);
HttpOnly 让脚本读不到会话 Cookie,XSS 偷不走登录态。它不解决 XSS,但把损失从「接管账号」降到「污染页面」。
自测方法:在所有输入点提交 '"()<script>alert(1)</script>,逐个输出位置检查是否被编码。这个土办法比扫描器先发现问题。