XSS 攻击是什么?前端如何防范?
简化版
XSS 是攻击者把恶意脚本注入页面并在用户浏览器中执行。防范核心是不要让不可信内容变成可执行脚本,常见手段包括输出转义、富文本白名单过滤、避免危险 HTML、CSP、HttpOnly Cookie 和严格的输入校验。
详细版
XSS 常见类型:
- 存储型:恶意内容存入数据库,用户访问时执行。
- 反射型:恶意脚本通过 URL 参数反射到页面。
- DOM 型:前端脚本把不可信数据写入 DOM 导致执行。
危害:
- 窃取 token 或用户信息。
- 伪造用户操作。
- 篡改页面内容。
- 发起恶意请求。
防护方式:
- 模板插值默认转义。
- 不随意使用
innerHTML。 - 富文本使用白名单过滤。
- 设置 CSP 限制脚本来源。
- Cookie 使用 HttpOnly 降低 token 被读风险。
完整版教学
一、XSS 的本质
XSS 的关键不是用户输入了危险字符,而是这些字符最终被浏览器当作脚本执行。
例如评论内容如果直接插入 HTML:
container.innerHTML = comment
攻击者提交 <script> 或带事件属性的标签,就可能执行恶意代码。
二、三类 XSS
存储型最危险,因为恶意内容会被保存,影响所有访问该内容的用户。
反射型通常依赖诱导用户点击恶意链接,服务端把参数原样输出到页面。
DOM 型发生在前端代码中,服务端可能完全没有参与。比如从 location.hash 读取内容后写入 innerHTML。
三、防护思路
最重要的是输出编码。普通文本应该按文本渲染,不要按 HTML 解释。
富文本不能简单转义所有标签,因为业务需要保留部分标签。这时要用白名单,只允许安全标签和安全属性。
CSP 是第二道防线。它可以限制脚本来源,禁止内联脚本,降低 XSS 成功后的破坏范围。
四、面试追问与工程落地
面试官可能问:“React/Vue 默认能防 XSS 吗?”
模板插值默认会转义文本,能防很多 XSS。但如果使用 dangerouslySetInnerHTML、v-html 或手动操作 innerHTML,仍然可能引入风险。
工程中安全不能只靠框架默认能力。富文本、Markdown、第三方脚本、用户生成内容都要重点审查。
五、输出上下文决定防护方式
同一个字符串放在不同位置,需要不同处理;不存在“统一替换尖括号就安全”的万能函数。
| 输出位置 | 推荐方式 | 典型危险点 |
|---|---|---|
| HTML 文本 | 使用 textContent/模板自动转义 | innerHTML |
| HTML 属性 | 框架属性绑定并限制属性名 | 事件属性、未加引号属性 |
| URL | 解析协议并建立 allowlist | javascript:、危险 data URL |
| JavaScript 数据 | 安全序列化,避免拼源码 | 打断字符串或 script 标签 |
| 富文本 | 成熟 sanitizer 白名单清洗 | 标签、属性、URL 协议组合 |
比如评论有 10000 条,哪怕 9999 条正常,只要 1 条存储型 payload 被保存,就可能影响之后查看页面的所有用户。输入过滤可减少垃圾数据,但最终必须在每个输出 sink 按上下文处理。
心法:先把不可信数据当“数据”而不是 HTML;确需 HTML 时再进入经过审计的单一清洗通道。
六、DOM sink、CSP 与验证方法
危险 sink 不只 innerHTML,还包括 outerHTML、insertAdjacentHTML、document.write、字符串形式的 setTimeout 等。代码审计应从这些 sink 反向追踪数据源,如 URL、postMessage、服务端响应、localStorage 和第三方 SDK。
// 安全展示普通文本
node.textContent = userComment
// 富文本必须先经过成熟且配置正确的 sanitizer
node.innerHTML = sanitize(userHtml)
CSP nonce/hash、Trusted Types、HttpOnly Cookie 都是纵深措施:CSP 限制执行来源,Trusted Types 收紧 DOM XSS sink,HttpOnly 减少 Cookie 被直接读走;它们都不修复原始注入点。
测试要覆盖存储型、反射型和 DOM 路径,并尝试标签、事件属性、危险 URL、SVG/MathML 等不同解析上下文。修复后既要证明 payload 不执行,也要回归合法富文本没有被破坏。
七、常见误区与追问
- 误区:过滤
<script>标签就能防 XSS。 事件属性、SVG、危险 URL 和大量解析上下文都可能执行代码。 - 误区:只在输入时统一转义最安全。 数据未来输出上下文未知,过早编码会双重转义且仍可能用错编码。
- 误区:React/Vue 项目不会出现 XSS。 普通插值较安全,但危险 HTML API、URL、第三方组件和手动 DOM 操作仍会引入漏洞。
- 追问:存储型、反射型、DOM 型如何区分? 看恶意数据是否持久化、是否由响应反射、以及漏洞 sink 是否主要在客户端代码。
- 追问:富文本为什么不能只做 HTML 转义? 业务需要保留部分标签,应按标签、属性和 URL 协议白名单清洗。
- 追问:HttpOnly 能阻止 XSS 做什么? 它阻止脚本直接读取 Cookie,但不能阻止脚本代用户发请求。
- 追问:CSP 为什么只是第二道防线? 配置可能有兼容放宽,且注入仍可能篡改页面或利用允许的能力。
八、加强记忆
- 本质:不可信数据进入可执行上下文,被浏览器解释成代码。
- 主防线:普通文本用安全 API,输出时按 HTML、属性、URL、JS 上下文编码。
- 富文本:集中使用成熟 sanitizer 的 allowlist,禁止自行拼过滤器。
- 审计方法:从危险 DOM sink 反查 URL、消息、存储和接口等数据源。
- 纵深措施:严格 CSP、Trusted Types、HttpOnly 降低漏网后的损失。
- 全面测试:覆盖存储、反射、DOM 三条路径及多种 HTML 解析上下文。