Skip to main content
我对 Web 安全的理解是一次次踩坑补起来的:先记住 XSS 和 CSRF 怎么防,后来才明白防御要分层,单点挡住并不够。只会说”转义、加 CSP”解释不了 SameSite 为什么能挡 CSRF。

Q1: XSS 是什么?怎么防御?

1

Web 安全的原罪: 信任用户输入

早期 Web 应用直接把用户输入拼接到 HTML 中返回。攻击者在输入中嵌入 <script> 标签,服务器原样返回,浏览器执行了恶意脚本——这就是 XSS(Cross-Site Scripting)的本质。
2

三种 XSS 类型

存储型:攻击者在评论区输入恶意脚本,存入数据库,所有用户浏览时执行。反射型:攻击者构造含恶意脚本的 URL,服务器把参数拼到 HTML 中返回。DOM 型:前端直接用 innerHTML 或 location.hash 渲染用户输入,不经过服务器。
3

防御核心: 输出转义

在将用户输入插入 HTML 之前,对特殊字符进行转义:< 转为 &lt;,> 转为 &gt;," 转为 &quot;。React 的 JSX 和 Vue 的模板默认自动转义,dangerouslySetInnerHTML / v-html 会绕过转义。
4

纵深防御: CSP 和 Trusted Types

CSP(Content Security Policy)限制页面只能加载指定来源的脚本,即使 XSS 注入成功也无法执行外部恶意脚本。Trusted Types 是 CSP 的补充——要求 innerHTML 等 DOM sink 只能接受经过安全检查的类型,不能接受原始字符串。
5

关键权衡: 安全性与开发体验

严格的 CSP 会禁用 eval 和内联脚本,影响许多第三方库的使用。strict-dynamic + nonce 策略是平衡安全和兼容性的最佳方案——只信任带 nonce 标记的脚本,由 strict-dynamic 自动传播信任。
XSS 防御解决的核心问题:
一句话总结:XSS 的本质是信任了用户输入——防御的核心是输出转义(不让输入变成代码),CSP 是纵深防御(即使转义失败也限制脚本来源)。

没有输出转义

就像一个公司让访客自己填写名牌,访客在名牌上写”我是 CEO”然后大摇大摆走进会议室。如果前台不检查(输出转义),所有人都会相信他的身份。

引入输出转义 + CSP

输出转义相当于前台检查名牌上的文字,把尖括号转义后按纯文本显示。CSP 相当于会议室门口还有人脸识别——即使混进了大楼,没有权限也进不了会议室。
回答时先说 XSS 的本质(信任用户输入),再说三种类型(存储/反射/DOM),然后说防御层次(输出转义 → CSP → Trusted Types)。想听的是你对攻击本质的理解,而不是背诵”转义特殊字符”。

Q2: CSRF 是什么?怎么防御?

CSRF(Cross-Site Request Forgery)的本质是浏览器自动携带 Cookie——用户访问恶意页面,页面自动发请求到目标站点,浏览器自动带上目标站点的 Cookie,服务器无法区分是用户主动发起还是恶意页面自动发起。
SameSite 是防御 CSRF 最有效的手段,Chrome 从 v80 开始默认值改为 Lax:
  • Strict:跨站请求一律不发送 Cookie。最安全但影响体验——从外部链接点击进入网站时没有登录状态。
  • Lax:同站请求始终发送;跨站时只在顶层导航且使用安全方法(如 GET)时发送,POST 不发送。平衡安全和体验,Chrome 默认值。
  • None:始终发送,必须配合 Secure。用于跨站场景(如第三方嵌入)。
CSRF 就像有人冒充你去快递站签收包裹。快递站(服务器)只认签名(Cookie),不核实你是不是本人(无法区分请求来源)。SameSite=Strict 相当于快递站规定”只有本人亲自来才能签收”——跨站请求就像别人代签,直接拒绝。CSRF Token 相当于每次签收都要输入一个动态验证码——攻击者不知道验证码,即使冒充你也签不了。

Q3: 除了 XSS 和 CSRF,还有哪些常见的 Web 安全威胁?

基础梳理

专家视角深度剖析

很多人只知道 XSS 和 CSRF,但实际更在意的是:你是否理解安全防御的纵深策略? 安全防御不是单一手段能解决的,而是需要多层防御(Defense in Depth): 第一层,输入验证:在数据进入系统时进行严格的格式校验和长度限制。但这不能替代输出转义——你不能信任任何客户端输入。 第二层,输出转义:在将数据插入 HTML/CSS/JS 时进行上下文相关的转义。HTML 上下文转义 < 和 >,JS 上下文转义引号和反斜杠,URL 上下文编码特殊字符。 第三层,CSP:限制脚本来源、禁止内联脚本和 eval,即使 XSS 注入成功也无法执行。 第四层,Cookie 安全:HttpOnly 阻止 JS 读取 Cookie、Secure 只在 HTTPS 下发送、SameSite 防御 CSRF。

Q4: CSP 和 Trusted Types 是什么?怎么配置?

CSP(Content Security Policy)的工作原理: CSP 通过 HTTP 响应头或 <meta> 标签声明页面允许加载哪些来源的资源。浏览器在执行脚本、加载样式、发起请求前检查是否符合策略。 CSP 指令的处理顺序:
CSP Level 3 的最佳策略是 strict-dynamic + nonce:只信任带 nonce 标记的脚本,strict-dynamic 自动传播信任给脚本动态创建的子脚本。这比白名单更安全(白名单容易被绕过),比 unsafe-inline 更严格。Content-Security-Policy: script-src 'strict-dynamic' 'nonce-xxx'; base-uri 'self'; object-src 'none'

Blink 的 CSP 实现在 ContentSecurityPolicy 类中(详见 Blink 源码:content_security_policy.cc):
CSP 检查器在脚本执行前调用 AllowFromSource,遍历所有 CSP 策略(页面可能有多条 CSP 头),任何一条策略拒绝就阻止执行。这确保了即使一条 CSP 配置有漏洞,其他策略仍然提供保护。 详见 Blink 源码:trusted_types_util.cc:
Trusted Types 通过类型系统在运行时阻止 DOM XSS——所有 DOM sink(如 innerHTML、outerHTML、insertAdjacentHTML)只能接受 TrustedHTML 等安全类型,不能接受原始字符串。开发者必须通过创建 Policy 来”清洗”用户输入,Policy 中定义了安全检查逻辑。这把安全检查从”开发者记得转义”变成了”类型系统强制检查”。

及格回答 vs 满分回答

问题:“说说 XSS 和 CSRF 的区别和防御”

及格回答(60-70分):
“XSS 是注入脚本,CSRF 是伪造请求。XSS 用转义和 CSP 防御,CSRF 用 SameSite Cookie 和 CSRF Token 防御。”
问题出在:知道规则但不理解设计。 满分回答(90-100分):
XSS 的本质是信任了用户输入——服务器或前端把用户输入当作 HTML/JS 执行。三种类型:存储型(存入数据库,所有用户受害)、反射型(URL 参数拼接到页面)、DOM 型(前端直接用 innerHTML 渲染)。防御的核心是输出转义(HTML 上下文转义 < 和 >,JS 上下文转义引号),React/Vue 默认自动转义,dangerouslySetInnerHTML / v-html 会绕过转义。[1]
CSRF 的本质是浏览器自动携带 Cookie——用户访问恶意页面,页面自动发请求到目标站点,浏览器自动带上目标站点的 Cookie。SameSite Cookie 是最有效的防御——Chrome 默认 Lax(同站发送;跨站仅顶层导航的安全方法发送,POST 不发),Strict 最安全但影响体验(外部链接进入网站时没有登录状态)。CSRF Token 让服务器能区分”用户主动发起”和”恶意页面自动发起”的请求。[2]
CSP(Content Security Policy)是纵深防御的关键——限制脚本来源、禁止内联脚本和 eval,即使 XSS 注入成功也无法执行。CSP Level 3 的 strict-dynamic + nonce 策略是最佳实践。Trusted Types 是 CSP 的补充——通过类型系统在运行时阻止 DOM XSS,要求 innerHTML 等 DOM sink 只能接受安全类型。[3]
实际项目中,安全防御需要多层策略:输入验证(第一道防线)、输出转义(核心防御)、CSP(纵深防御)、Cookie 安全(HttpOnly + Secure + SameSite)。任何单一手段都不能完全防御所有攻击。

常见追问

题目来源于真实面经(字节/腾讯/阿里前端面试、牛客网、haizlin/fe-interview),按难度递进。
答题要点: 指出它绕过自动转义的机制,以及安全使用的前提条件。高分回答: “dangerouslySetInnerHTML 会绕过 React 的自动转义,直接把字符串作为 HTML 插入 DOM。如果字符串包含用户输入的 <script> 标签或事件处理器(如 <img onerror=alert(1) />),就会执行恶意脚本。安全使用的前提:必须对用户输入进行严格的 HTML 清理。推荐使用 DOMPurify 库:
DOMPurify 会移除所有危险的标签和属性(<script>、onerror、javascript: URL 等),只保留安全的 HTML。但即使使用了 DOMPurify,也要配合 CSP 做纵深防御——因为清理逻辑可能有漏洞。”
答题要点: 结合 CSP 的局限性和绕过场景解释。高分回答: “CSP 不能完全阻止 XSS,主要有几个绕过场景:1)unsafe-inline 和 unsafe-eval:如果 CSP 配置了这两个关键字,XSS 仍然可以执行内联脚本和 eval。很多旧项目为了兼容第三方库不得不开启这些。 2)JSONP 端点:如果白名单中包含支持 JSONP 的域名,攻击者可以通过 JSONP 注入脚本。 3)base-uri 攻击:如果 CSP 没有限制 base-uri,攻击者可以通过注入 <base> 标签改变相对路径的解析基准。 4)CSS 注入:style-src 如果允许 unsafe-inline,攻击者可以通过 CSS 泄露数据(CSS exfiltration)。最佳实践:用 strict-dynamic + nonce 替代白名单,base-uri 'self',object-src 'none'。”
答题要点: 对比常见攻击,点出点击劫持的隐蔽性和防御方式。高分回答: “点击劫持是攻击者用透明的 <iframe> 嵌入目标网站,覆盖在一个诱骗页面上。用户以为点击的是诱骗页面上的按钮(如’领取奖品’),实际上点击的是透明 iframe 中的目标网站按钮(如’确认转账’)。防御方式: 1)X-Frame-Options:HTTP 响应头,控制页面是否可以被 iframe 嵌入。DENY 完全禁止嵌入,SAMEORIGIN 只允许同源嵌入。 2)CSP frame-ancestors:CSP Level 2 引入,比 X-Frame-Options 更灵活。可以指定允许嵌入的来源白名单。 3)JavaScript frame-busting:在页面中检测自己是否被嵌入,如果是就跳出 iframe。但这种方法可以被 sandbox 属性绕过。最佳实践:同时配置 X-Frame-Options: SAMEORIGIN 和 Content-Security-Policy: frame-ancestors 'self'。对于不需要被嵌入的页面(如登录页、支付页),用 DENY 完全禁止。“

延伸阅读与引用参考

  • OWASP Top Ten:Web 应用最关键的十大安全风险,定期更新的安全威胁清单。[1]
  • MDN: Content Security Policy:Mozilla 对 CSP 指令的完整参考,含配置示例和最佳实践。[2]
  • Trusted Types API 规范:W3C 对 Trusted Types 的权威定义,理解如何通过类型系统阻止 DOM XSS。[3]