XSS 是什么
XSS(跨站脚本)的成因是把不可信数据当代码输出到了浏览器:攻击者输入的片段被页面当作 HTML 或 JavaScript 执行,脚本于是在受害者的会话里运行。它能读取页面可见数据、代替用户发起操作、盗取未受保护的凭据,配合蠕虫式传播还能一次性影响大量用户。防护的根本手段是输出编码——同一份数据在 HTML 文本、属性、URL、JavaScript 四种上下文里需要不同的编码方式,因此安全边界在渲染层而不是输入层。
基本信息
| 风险类别 | OWASP Top 10 2021 A03 注入类;CWE-79 |
|---|---|
| 成因 | 用户数据未按输出上下文编码,被当作 HTML/JS 解析 |
| 危害 | 会话劫持、伪造操作、钓鱼与页面篡改、蠕虫式扩散 |
| 基础防护 | 按上下文做输出编码,默认转义为常态 |
| 纵深防护 | HttpOnly 与 SameSite、CSP、富文本白名单净化 |
| 框架差异 | 主流前端框架默认转义插值,风险集中在显式绕过点 |
典型使用场景
- 用户昵称、评论、工单标题等回显位置:最常见的存储型注入点
- 搜索关键词回显:搜索页把关键词原样写回页面,形成反射型
- URL 参数拼接进页面内联脚本:前端用参数拼 JS 变量时最易出事
- 富文本编辑器内容与第三方 Markdown 渲染:需要白名单净化而不是全转义
- 后台运营查看用户提交内容:管理员会话权限高,被利用后影响面更大
- 移动端内嵌 WebView:可进一步触达本地能力,需要独立评估
四种上下文的编码方式
四种上下文的编码方式
HTML 文本:< 转 <,> 转 >,& 转 &
属性值:引号包裹,编码双引号与单引号,避免未加引号的属性
URL 参数:URL 编码,并对协议做白名单,只允许 http/https
JS 上下文:用 JSON 序列化后嵌入,禁止字符串拼接构造脚本
CSS 与 style 属性:只允许固定枚举值,不接受用户直接输入
# 关键:编码必须发生在最终输出位置,而不是统一在入库前处理三类 XSS 的差别
| 类型 | 触发方式 | 主要检测位置 |
|---|---|---|
| 反射型 | 单次请求的数据被回显执行 | 搜索、跳转、错误页的回显参数 |
| 存储型 | 恶意内容落库,被其他用户访问时执行 | 留言、昵称、工单、导入的模板 |
| DOM 型 | 浏览器端脚本把不可信数据写入 DOM | 前端代码里的 innerHTML、eval、location 赋值 |
CSP 与 Cookie 加固
CSP 与 Cookie 加固
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-<每请求随机值>'; object-src 'none'
# CSP 是兜底:即使存在注入点,也能阻止内联脚本执行
Set-Cookie: sid=<值>; HttpOnly; Secure; SameSite=Lax
# HttpOnly 阻断脚本读取 Cookie,SameSite 降低跨站请求被自动携带的风险
# 两者都不能替代输出编码,只是把单点问题的影响范围压小开 CSP 时先跑报告模式(Content-Security-Policy-Report-Only)收集违规来源,再切强制模式,避免直接上线拦断业务脚本。
只做输入过滤很难覆盖全部场景:同一份数据可能出现在多个输出位置,同一位置的编码规则还随浏览器与上下文变化,所以以输出编码为主、输入校验为辅。
验证与巡检要点
- 用无害标记字符验证回显位置:先看数据落在 HTML、属性还是脚本里,再判定该位置需要的编码
- 覆盖多出口:同一个字段常同时出现在页面、JSON 接口、导出文件与邮件模板中
- 检查前端拼接点:搜索 innerHTML、document.write、eval 与模板字符串拼接等写法
- 验证净化库配置:导出富文本白名单,确认不含事件属性与危险协议
- 回归正常展示:编码改造后确认中文、表情、特殊符号显示正常,避免修一处坏一处
JSON 接口也有上下文问题:如果前端把接口返回值直接插入 DOM,接口层就算做了转义也可能在浏览器里被还原后执行,安全边界要看数据最终落在哪里。