想象你住在一栋「云端智能公寓」里,每套房子里都住着一个网站(比如 bank.com),你每天的转账、发帖、购物都在这里进行。公寓的物业装了一套强大的门禁系统:只有同属一栋楼(同源)的住户才能互相探访。然而,这世上总有两类人让你头疼:第一类是「贴纸条的骗子」——他会偷偷溜进你的房间,在你家玄关贴满假的取款公告,每一位来访的客人都信以为真;第二类是「隔空指挥的黑客」——他根本不进屋,而是站在窗外用激光笔遥控你家的智能音箱,让它替你给别人转账。前者叫 XSS(跨站脚本攻击),后者叫 CSRF(跨站请求伪造)。好消息是,物业有能力防范它们:访客白名单(CSP)、保险箱(HttpOnly)、动态验证码(CSRF Token)、钥匙投递模式(SameSite)——把这些招数组合起来,就能把这两类骗子统统挡在门外。这篇文章,就用「公寓安全手册」的方式,带你从零看懂 Web 前端安全的两大必修课。

岗位关联

对 AI 从业者而言,XSS 与 CSRF 防护是构建安全 Web 应用的基本功——无论你在做模型服务接口、AI 生成内容的渲染页面还是数据标注平台,安全防线都决定了产品能否真正交付。

目录
  1. 可视化演示
  2. 同源策略与CORS
  3. XSS攻击原理
  4. XSS防护体系
  5. CSRF攻击原理
  6. CSRF防护方案
  7. Cookie安全属性
  8. 安全开发实践
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

同源策略与CORS

想象你住在「云端公寓」,每套房子里都住着一个网站(比如 bank.com),你每天在这里生活。公寓的物业装了一道严格的门禁系统:只有同属一栋楼(同源)的住户才能互相探访。住在 b.com 的访客,绝对不允许直接进屋翻看你在 a.com 的存款记录。这道门禁,就是浏览器安全体系中最重要的基石——同源策略。

从技术上说,「源」由三个要素共同定义:协议(https 还是 http)、域名(bank.com 还是 evil.com)、端口(443 还是 8080)。三者完全一致才算同源,任何一个不一致,浏览器就会把这次访问视为「跨源」,默认不允许读取对方的数据。这就像门禁卡同时校验「哪栋楼、哪个单元、哪个门牌号」。

但现实中,你有时确实需要跨源合作——比如你的前端页面要调用第三方支付接口。这时,服务器可以主动签发一张「访客通行证」,也就是 CORS 机制:服务器在响应头里写上 Access-Control-Allow-Origin: https://trusted.com,浏览器核对后才会放行这张跨源请求。

理解了门禁规则,你才会真正明白后面两类骗子的狡猾之处:XSS 是在门禁「放行」的场景里趁虚而入,CSRF 则是利用门禁「自动放行」的机制狐假虎威。接下来,我们看第一类骗子——贴纸条的 XSS 攻击者。

sameOrigin(A,B)=(schemeA=schemeB)∧(hostA=hostB)∧(portA=portB)\displaystyle sameOrigin(A,B) = (scheme_A = scheme_B) \wedge (host_A = host_B) \wedge (port_A = port_B)
同源判定是严格的三重「且」关系:协议、域名、端口任一项不同,访问即被判为跨源。
  1. 协议比对检查两个地址是否使用相同协议,https 与 http 互不相同。
    schemeA=schemeB\displaystyle scheme_A = scheme_B
  2. 域名比对检查主机名是否完全一致,www.example.com 与 example.com 在浏览器眼中是不同源。
    hostA=hostB\displaystyle host_A = host_B
  3. 端口比对检查端口号是否一致,例如 443 与 8080 不同源。
    portA=portB\displaystyle port_A = port_B
  4. 合取判定三个条件必须同时满足,∧ 表示逻辑「与」。
    sameOrigin(A,B)=(schemeA=schemeB)∧(hostA=hostB)∧(portA=portB)\displaystyle sameOrigin(A,B) = (scheme_A = scheme_B) \wedge (host_A = host_B) \wedge (port_A = port_B)

XSS攻击原理

你的房间里时常有客人来访。XSS 攻击者(贴纸条的骗子)有三种办法让自己「混成合法访客」。第一种,他在楼下大喊一声「你家水管漏了!」,诱导你亲手打开一扇门——一个嵌着恶意脚本的链接。链接里的参数未经检查就被网页原样回显,脚本顺势溜进屋里执行,这叫反射型 XSS。第二种,他把假公告直接贴在你家楼道的公告栏上,任何路过的客人都会读到并信以为真——恶意脚本被存进数据库,等别人打开页面时再被渲染出来,这叫存储型 XSS。

第三种更隐蔽:他没有贴任何新公告,而是利用了屋内智能音箱「过度听话」的毛病。你家前端代码里有一些「口无遮拦」的接口(比如 innerHTML、document.write),直接拼接了不可信数据。攻击者只需要让一段看似正常的操作触发这个接口,恶意脚本就在 DOM 层面被执行了,这叫 DOM 型 XSS。

从本质上看,XSS 的成因可以概括为「不可信的数据 + 可执行的上下文」:反射型和存储型的区别在于 payload 藏在 URL 里还是数据库里,DOM 型则绕过了服务器,直接在前端 DOM API 上执行。三者的危害是相通的——脚本能窃取会话、篡改页面、模拟用户操作,就像贴纸条的骗子能偷看你抽屉里的存折。

无论哪种潜入术,受害者的浏览器都会把一张「不该执行的纸条」当作自家命令乖乖执行。物业自然不能坐视不管,于是推出了三重反制措施——CSP 访客名单、HttpOnly 保险箱、以及输入输出的双向安检。

XSS∈{Reflected:payload→URL, Stored:payload→DB, DOM:payload→DOM}\displaystyle XSS \in \{Reflected: payload \to URL,\ Stored: payload \to DB,\ DOM: payload \to DOM\}
三种 XSS 的差别只在「恶意脚本从哪条路进来」,最终都是浏览器执行了不该执行的脚本。
  1. 反射型注入payload 藏于 URL 参数中,服务端未过滤即回显到页面,一次性执行。
    Reflected:payload→URL→HTML\displaystyle Reflected: payload \to URL \to HTML
  2. 存储型注入payload 经由表单等入口写入数据库,其他用户访问页面时被服务器读出并渲染。
    Stored:payload→DB→HTML\displaystyle Stored: payload \to DB \to HTML
  3. DOM 型注入payload 不经过服务器,直接进入前端 document.write、innerHTML 等 DOM 操作 API。
    DOM:payload→DOM API→Browser\displaystyle DOM: payload \to DOM\ API \to Browser

XSS防护体系

聪明的物业对贴纸条的骗子准备了三重反制。第一重,给每户发放一本「访客名单」——CSP(内容安全策略)。名单规定了「哪些地方的脚本可以进你家执行」,凡是名单外的脚本一律在门口拦下。即使攻击者已经成功注入了 <script>,只要域名不在白名单里,浏览器也会拒绝执行。这是对 XSS 最关键的一道后端纵深防线。

第二重,给每户发一个「保险箱」——HttpOnly 标志。只要 Cookie 被标记为 HttpOnly,页面里的 JavaScript 就完全读不到它。这样一来,即使攻击者的脚本成功执行,它也拿不到会话凭证,等于小偷翻遍了屋子却找不到存折。

第三重则是大门口的「安检仪」:所有进楼的物品(用户输入)必须过检,非法内容(如 <script>)直接没收——这叫输入过滤;所有带出屋的物品(输出到页面)必须重新包装,HTML 特殊字符被转义成普通文本——这叫输出编码。过滤与编码分别从「入口」和「出口」两个方向切断 XSS,任何一侧失效,另一侧还能兜底。

三道保险层层叠加,效果相当于「脚本进不来、Cookie 读不到、敏感东西带不走」。但物业知道,还有另一种风格的骗子——他根本不需要进屋。

CSP={directive:source-list},directive∈{default-src, script-src, style-src, img-src}\displaystyle CSP = \{directive: source\text{-}list\},\quad directive \in \{default\text{-}src,\ script\text{-}src,\ style\text{-}src,\ img\text{-}src\}
CSP 的每条指令规定「某类资源可以从哪些源加载」,白名单越严,脚本钻空子的空间越小。
  1. 指令定义每条 CSP 策略由「指令名:来源白名单」构成,script-src 专门管脚本来源。
    directive:source-list\displaystyle directive: source\text{-}list
  2. 来源约束'self' 表示只信任当前站点自身,外部域名脚本一律拒绝。
    script-src=′self′\displaystyle script\text{-}src = 'self'
  3. 危险选项'unsafe-inline' 等于放行所有内联脚本,绝大多数 XSS payload 正是以此形式注入,应严格禁用它。
    ′unsafe−inline′⇒CSP ineffective\displaystyle 'unsafe-inline' \Rightarrow CSP\ ineffective

CSRF攻击原理

如果说 XSS 是「进屋贴纸条」,那 CSRF 攻击者就是「隔空指挥的遥控者」。他站在你家窗外,手里拿着一支激光笔,对着屋里的智能音箱命令:「给隔壁转 1000 元!」智能音箱只听指令,却不分辨这条指令是谁发出的——因为你的钥匙(会话 Cookie)就插在门锁上,音箱理所当然地认为这是你本人在下命令。

技术流程是这样的:你登录过银行系统,浏览器里存着有效的会话 Cookie;这时你又访问了攻击者精心构造的恶意页面,页面里藏着一个自动提交的表单或加载图片的标签;浏览器带着你的 Cookie 向银行发出转账请求;银行服务器看到凭证有效,便执行了转账。整个过程中,攻击者始终没有碰到你的 Cookie,他只需要「诱导你的浏览器替他去发请求」。

CSRF 能成功的条件有三个,必须同时成立:第一,受害者已在目标网站完成登录认证;第二,浏览器发出了一个伪造的跨站请求;第三,服务器没有校验请求的来源身份(比如缺失 Token 或来源检查)。CSRF 攻击的矛盾根源在于:Cookie 是浏览器自动携带的,服务器仅凭 Cookie 无法判断「这条请求是否出于用户的本意」。

如果说 XSS 是「冒名进你家贴公告」,那 CSRF 就是「隔空遥控你家电器」。对付遥控者的思路也很自然:给每一条遥控指令加上只有你俩才知道的暗号。

CSRFsuccess=authenticated(victim)∧forged_request∧¬(token_check)\displaystyle CSRF_{success} = authenticated(victim) \wedge forged\_request \wedge \neg(token\_check)
三个条件缺一不可:已登录、已伪造、且服务器没做身份来源校验,CSRF 才能得手。
  1. 受害者已认证受害者浏览器中持有目标站的有效会话凭证,这是攻击的前提。
    authenticated(victim)=true\displaystyle authenticated(victim) = true
  2. 伪造请求发出恶意页面通过表单、图片或 AJAX 诱导浏览器自动发出跨站请求。
    forged_request=true\displaystyle forged\_request = true
  3. 缺少防护校验服务器没有校验 Token 或来源信息,¬ 表示「取反」,即没有做检查。
    ¬(token_check)\displaystyle \neg(token\_check)
  4. 合取生效三者同时满足,CSRF 攻击才成功;堵住任何一环,攻击即告失效。
    CSRFsuccess=A∧B∧C\displaystyle CSRF_{success} = A \wedge B \wedge C

CSRF防护方案

物业升级了规定:从今往后,凡涉及转账、改密码这类敏感操作,除了插在门上的钥匙(Cookie),智能音箱还会要求你报出一串「当天动态验证码」。激光笔可以模仿按键,却算不出这条只有你和音箱共享的暗号,伪造指令当场作废。这串动态验证码在 Web 安全里的名字叫作 CSRF Token。

标准的同步 Token 模式是这样的:服务器为每个用户会话生成一个随机 Token,存入会话存储,并在渲染表单时将其一并下发;用户提交请求时,Token 作为隐藏字段或请求头一并发送;服务器收到请求后,比对请求里的 Token 与会话里的 Token 是否一致,不一致就拒收。由于攻击者无法预知 Token 值,他的伪造请求自然过不了关。

除了同步 Token,还有两种辅助手段:一是校验 Origin / Referer 请求头,确认请求来源与本站同源;二是「双重提交 Cookie」模式——请求体带一份 Token、Cookie 里也放一份,服务器比对两者是否一致。需要特别提醒的是:Token 绝不能作为唯一校验手段放在 Cookie 里,因为 Cookie 会被浏览器自动携带,攻击者的跨站请求同样会带上它,等于把暗号直接写在钥匙上一起交了出去。

验证码在手,隔空指挥彻底失灵。到这里,两大骗术都有了核心对策。但还有最后一块拼图——把「钥匙」本身的投递规则定得更严。

accept(token)=(token=session.token)∧(session.token≠∅)\displaystyle accept(token) = (token = session.token) \wedge (session.token \neq \emptyset)
Token 校验的核心是「请求里的暗号」必须与「服务器会话里的暗号」完全一致,且会话确实存在暗号。
  1. 请求携带 Token前端在表单或请求头中附带本次会话的 Token 值。
    tokenrequest\displaystyle token_{request}
  2. 会话存储比对服务器取出当前会话中保存的 Token,与请求值做恒定时间比对。
    tokenrequest=tokensession\displaystyle token_{request} = token_{session}
  3. 存在性检查会话中必须确实有 Token,不能为空,空会话直接拒绝。
    tokensession≠∅\displaystyle token_{session} \neq \emptyset
  4. 合取判定两个条件同时成立才接受请求。
    accept(token)=(token=session.token)∧(session.token≠∅)\displaystyle accept(token) = (token = session.token) \wedge (session.token \neq \emptyset)

Cookie安全属性

物业给每户的钥匙(Cookie)设计了三种「投递模式」,这就是 SameSite 属性。Strict 模式:只有你亲自到大堂插钥匙(第一方页面请求),锁才响应;Lax 模式:你本人走正规访客通道(顶层导航跳转)时锁才响应,其余跨站请求一律不理;None 模式:只要有地址就送达——但必须走加密邮路(HTTPS),防止钥匙在途中被复制。三种模式决定了「钥匙在什么场景下会被自动掏出来」。

除了投递模式,钥匙还有两条「规格」。第一条是 Secure 标志:带这个标志的 Cookie 只愿意通过 HTTPS 加密通道传输,即使页面被劫持成 HTTP,钥匙也不会裸奔出门。第二条是 Domain 与 Path:通过限定 Cookie 所属的域名和路径,让钥匙只能开特定楼栋、特定房间的门,即使被偷,损失也能控制在最小范围。别忘了上一节说的 HttpOnly——它相当于把钥匙锁进保险箱,任何脚本都摸不到。

回到 CSRF 的语境里,SameSite=Strict 可以非常彻底地防住跨站伪造:攻击者页面发出的请求一律不携带 Cookie,服务器收不到凭证自然无法「被转账」。但 Strict 也有代价——用户从站外链接进入时,会话可能无法恢复(比如从邮件点进网银首页是未登录态),所以实践中常用 Lax 来平衡安全与体验。

钥匙规则严格了,假钥匙自然无处可配。但物业明白:再好的门禁,如果管理手册一团糟,迟早会出纰漏。安全不是装修,而是一套持续运转的日常流程。

SameSite∈{Strict, Lax, None},Secure⇒HTTPS only\displaystyle SameSite \in \{Strict,\ Lax,\ None\},\quad Secure \Rightarrow HTTPS\ only
SameSite 决定 Cookie 何时自动携带,Secure 决定 Cookie 只能走 HTTPS,二者从「频率」和「通道」两个维度约束钥匙的使用。
  1. Strict 模式Cookie 只在第一方上下文中发送,任何跨站请求都不携带。
    SameSite=Strict⇒first-party only\displaystyle SameSite = Strict \Rightarrow first\text{-}party\ only
  2. Lax 模式允许顶层导航的 GET 请求携带 Cookie,其余子资源与跨站 POST 不带。
    SameSite=Lax⇒top-level GET\displaystyle SameSite = Lax \Rightarrow top\text{-}level\ GET
  3. None 模式所有跨站请求都会携带,但必须与 Secure 搭配,否则浏览器拒绝发送。
    SameSite=None⇒HTTPS required\displaystyle SameSite = None \Rightarrow HTTPS\ required
  4. Secure 约束该标志强制 Cookie 仅通过 HTTPS 传输,防止明文网络中被窃听。
    Secure⇒Cookie over TLS\displaystyle Secure \Rightarrow Cookie\ over\ TLS

安全开发实践

最好的物业不是等出了事才叫锁匠,而是把安全检查写进日常运维手册。全球业主委员会公认的安全手册是 OWASP(开放 Web 应用安全项目)发布的 OWASP Top 10——它就像「安全隐患红黑榜」,定期告诉你哪些漏洞最常被攻击者利用,XSS 与 CSRF 常年榜上有名,而 CSP、SameSite 等防护手段正是针对榜单开出的「整改单」。

安全开发实践覆盖软件的整个生命周期,可以分成三个阶段。开发前:团队做安全培训与威胁建模,提前想清楚「哪些功能可能被谁攻击」;开发中:代码审查、SAST 静态扫描自动化检查危险写法(如直接拼接 innerHTML)、单元测试覆盖输入边界;上线后:DAST 动态扫描、渗透测试模拟真实攻击、建立漏洞响应机制。每个阶段独立运作,谁都不依赖别的关卡替自己把门。

这套思路的核心叫「纵深防御」:即使某一层被绕过,下一层仍能兜底。例如:输入过滤漏掉了 <script>,输出编码能把它转义成无害文本;编码也失效了,CSP 还能拒绝执行;CSP 配错了,HttpOnly 依然锁着 Cookie。把「输入校验、输出编码、CSP、HttpOnly、CSRF Token、SameSite」层层叠加,攻击者需要同时击穿 6 道防线才能得手,这在现实中几乎不可能。

门禁、名单、保险箱、验证码、钥匙规则,再加上一本持续更新的安全手册,你的「云端公寓」才真正有了体系化防护。学完理论,我们看看在 AI 时代,这些安全原则如何落地到实际工作场景中。

D=∑idi,di∈{InputFilter, OutputEncode, CSP, HttpOnly, CSRF_Token, SameSite}\displaystyle D = \sum_{i} d_i,\quad d_i \in \{InputFilter,\ OutputEncode,\ CSP,\ HttpOnly,\ CSRF\_Token,\ SameSite\}
纵深防御不是单点防护的堆叠,而是让每一层独立失效、互为兜底,使总体安全性远大于任何一层。
  1. 逐层防线每一项安全措施都是一条独立的防线 dᵢ,各自拦截特定类型的攻击向量。
    di∈{InputFilter, OutputEncode, CSP, HttpOnly, CSRF_Token, SameSite}\displaystyle d_i \in \{InputFilter,\ OutputEncode,\ CSP,\ HttpOnly,\ CSRF\_Token,\ SameSite\}
  2. 求和叠加Σ 表示将各层防线组合起来,而不是只依赖其中某一项。
    ∑idi\displaystyle \sum_{i} d_i
  3. 整体防御各层独立失效、互为补充,共同构成系统的全局防御强度 D。
    D=d1+d2+⋯+dn\displaystyle D = d_1 + d_2 + \cdots + d_n

AI 应用场景

AI 驱动的 Web 应用防火墙(WAF)

阶段:安全运维与威胁检测

解决的问题:传统 WAF 依赖固定规则库匹配 XSS payload,攻击者用编码绕过、大小写混淆等手段就能轻松突破规则。AI 模型可以学习正常请求与恶意请求的流量特征,识别语义层面的脚本注入意图,即使 payload 被各种编码包装也能被识破,就像智能保安能认出化了妆的小偷。

具体案例:某电商平台在评论区接口接入基于机器学习的 WAF 模型,训练数据包含上百万条历史请求和已知 XSS 攻击样本。上线后,模型检测出传统规则漏掉的 37% 变种 payload,同时将误报率控制在 0.5% 以下,值班安全工程师再也不用半夜爬起来看告警。

LLM 辅助安全代码审计

阶段:开发阶段的静态安全审查

解决的问题:人工代码审计成本高、效率低,尤其是在大型前端项目中定位 innerHTML 滥用、缺失 CSRF Token 校验等隐患非常耗时。LLM 可以结合上下文理解代码逻辑,自动标记候选漏洞并给出修复建议,相当于给开发团队配了一位 24 小时在线的安全顾问。

具体案例:一家金融科技公司的前端团队用 LLM 工具扫描 20 万行 React 代码,重点排查 DOM 型 XSS(dangerouslySetInnerHTML)和表单缺失 Token 的问题。扫描结果经人工复核后确认了 14 个真实漏洞,团队按风险等级依次修复,整个审计周期从两周缩短到两天。

AI 红队自动化渗透测试

阶段:上线前的安全测试

解决的问题:传统渗透测试依赖安全专家的经验和手工构造攻击向量,覆盖场景有限。AI 可以根据被测应用的页面结构自动生成大量 XSS 与 CSRF 攻击变体,并评估防护策略(CSP 配置、SameSite 设置、Token 校验)的有效性,就像用 AI 驱动的「红队」反复演练攻防。

具体案例:某云服务商在发布新管理后台前,使用 AI 渗透测试工具自动遍历了全部 120 个表单和 60 个接口,生成并执行了超过 5000 条攻击用例,最终发现一个管理员接口缺少 CSRF 防护,及时修复后才正式上线。

Python 演示

python50 行
import hmacimport htmlimport secrets  class WebSecurityDemo:    """模拟 XSS 与 CSRF 防护的演示类。"""     def __init__(self):        # 服务器端存储每个用户的 CSRF Token        self.token_store = {}     def sanitize_input(self, raw_text):        # 输出编码:把 HTML 特殊字符转义,阻断 XSS payload        return html.escape(raw_text, quote=True)     def build_csp_header(self, script_src='"self"'):        # 构造 CSP 响应头,只允许同源脚本与样式        return "default-src 'self'; script-src {0}; object-src 'none'".format(script_src)     def generate_csrf_token(self, user):        # 为每个用户会话生成随机 Token        token = secrets.token_hex(16)        self.token_store[user] = token        return token     def verify_csrf(self, user, token):        # 恒定时间比对 Token,防止时序攻击        expected = self.token_store.get(user, '')        return hmac.compare_digest(expected, token)     def demo(self):        user = 'alice'        evil_comment = "<script>alert(1)</script><p>你好</p>"        print('[1] XSS 防护演示')        print('原始评论:', evil_comment)        print('转义输出:', self.sanitize_input(evil_comment))        print()        print('[2] CSRF 防护演示')        token = self.generate_csrf_token(user)        print('服务器生成 Token:', token[:8] + '...')        print('正确 Token 校验:', self.verify_csrf(user, token))        print('伪造 Token 校验:', self.verify_csrf(user, 'forged-token'))        print()        print('[3] CSP 响应头示例')        print(self.build_csp_header())  if __name__ == '__main__':    WebSecurityDemo().demo()
目的:演示 Web 安全中的三大基础防护——对用户输入做 HTML 转义防 XSS、生成并校验 CSRF Token、构造 CSP 响应头。
  1. import html调用标准库完成 HTML 实体转义,输出编码是防 XSS 的关键一环。
  2. html.escape(raw_text, quote=True)把 <script> 转成 &lt;script&gt;,浏览器只会把它当作普通文本显示。
  3. secrets.token_hex(16)用密码学安全随机数生成 Token,攻击者无法预测。
  4. hmac.compare_digest(expected, token)恒定时间比较字符串,避免因比较耗时差异泄露信息。
  5. self.token_store[user] = token服务器端会话存储,请求来时比对的是「这边存的值」,而不是 Cookie 里自动携带的值。
  6. "default-src 'self'; script-src {0}; object-src 'none'"CSP 白名单骨架,后续可追加 style-src、img-src 等指令。

常见误区

误区我给网站配好了 HTTPS 证书,地址栏也有小锁图标了,XSS 和 CSRF 应该都防住了吧?

正解HTTPS 只加密网络传输链路,防止数据在途中被窃听或篡改。但 XSS 攻击发生在浏览器解析执行 HTML 的环节,CSRF 攻击发生在服务器信任凭证的环节,两者都在 HTTPS 之上运行。就好比信封用了加密信封,但骗子是在信纸上贴金句——信纸内容该骗还是骗。正确做法是 CSP、输出编码、Token 校验等多层应用层防御。

为何常见:浏览器地址栏的锁图标给人强烈的「安全」心理暗示,让人误以为传输安全等于应用安全。

误区我在前端加了输入长度限制和 `<script>` 关键字过滤,恶意内容都拦住了,后端不用再处理了吧?

正解前端过滤只是用户体验层面的便利,攻击者完全可以用 curl、Postman 等工具绕过页面直接向服务器发送原始请求。真正的防线必须在后端:一边做输入校验(白名单,拒绝非法字符),一边做输出编码(在渲染位置转义)。这就像门口安检再严,也拦不住有人把东西从窗户直接扔进屋。前后端分层设防,才是对的。

为何常见:大部分入门教程以「输入框校验」作为示例,让人误以为拦截发生在用户界面就是全部。

误区我把 Token 放进 Cookie,前端再读出来放到请求头,两层都有,总该万无一失了吧?

正解双重提交 Cookie 的安全前提是「攻击者读不到 Cookie 里的 Token」。可一旦站点同时存在 XSS 漏洞,脚本就能随意读取 Cookie;即使没有 XSS,子域失守也可能泄露 Token。更稳妥的是「服务端会话绑定 Token + 请求体/请求头携带」的同步 Token 模式。这就像把验证码和钥匙串在同一个钥匙扣上——小偷顺走一个,两个都没了。

为何常见:「双重验证」字面上听起来很安全,人们容易忽视两条防线之间其实共用同一份秘密。

误区项目里用了很多内联脚本,直接写 `'unsafe-inline'` 多方便,CSP 其他部分还起作用。

正解'unsafe-inline' 意味着「允许所有内联脚本执行」,而绝大多数反射型和存储型 XSS payload 正是以内联 <script> 或 onclick 这类事件属性的形式注入的。加上它,等于访客名单上写「任何人不用登记都能进」,CSP 形同虚设。如果必须用内联脚本,应改用 nonce(一次性随机数)或 hash 机制精确放行,而不是一刀切地允许所有内联代码。

为何常见:内联脚本在开发中确实方便,开发者宁愿「开个口子」也不愿重构,结果把这个口子开成了门。

面试题

Q1. 同源策略要求协议与域名相同,还要求下列哪项也相同?

单选题 · easy

  • A.文件路径相同
  • B.服务器端口相同← 正确答案
  • C.浏览器版本相同
  • D.页面标题相同

答案:B。同源三要素是协议、域名、端口,三者缺一不可。文件路径、浏览器版本、页面标题都不参与同源判定,就像门禁卡只认「楼栋+单元+门牌」,不认你长了什么发型。

Q2. 反射型 XSS 的典型触发场景是下列哪一项?

单选题 · easy

  • A.评论区内容渲染
  • B.URL 参数页内回显← 正确答案
  • C.前端 JS 拼接 DOM
  • D.数据库内容读回

答案:B。反射型 XSS 的 payload 藏在 URL 参数中,服务端未过滤直接回显到网页。评论区渲染是存储型的典型场景,前端拼接 DOM 更接近 DOM 型,数据库内容回读也与反射型无关。

Q3. 以下哪些属于 XSS 的有效防御手段?

多选题 · mid

  • A.部署 CSP 白名单← 正确答案
  • B.后端输出转义← 正确答案
  • C.输入参数校验← 正确答案
  • D.关闭浏览器 JS

答案:ABC。CSP 限制脚本来源、输出转义让 payload 变成纯文本、输入校验拦截非法数据,都是有效防御。关闭浏览器 JS 虽然能防 XSS,但现代 Web 应用几乎无法运行,不算可取方案。

Q4. HttpOnly 标志的主要作用是下面哪一项?

单选题 · easy

  • A.阻止跨站请求携带
  • B.禁止脚本读取该 Cookie← 正确答案
  • C.强制使用 HTTPS 传输
  • D.限制 Cookie 的访问域名

答案:B。HttpOnly 让 JavaScript 无法通过 document.cookie 读取 Cookie,专门对付 XSS 窃取会话。阻止跨站携带是 SameSite 的职责,强制 HTTPS 是 Secure 标志,限制域名则是 Domain 属性。

Q5. CSRF 攻击成功的决定性前提是下列哪一项?

单选题 · hard

  • A.受害者处于已登录状态← 正确答案
  • B.攻击者窃取了服务器源码
  • C.目标站点采用 HTTP 协议
  • D.受害者浏览器版本过旧

答案:A。CSRF 的本质是「浏览器冒充受害者发请求」,前提是受害者已经在目标站登录且会话有效,否则请求没有凭证。攻击者不需要源码,协议和浏览器版本都不是决定性因素。这就好比遥控器要能指挥你的音箱,音箱得先处于通电待命状态。

Q6. 以下哪些措施能有效防御 CSRF 攻击?

多选题 · mid

  • A.表单同步 Token 校验← 正确答案
  • B.校验 Origin 请求头← 正确答案
  • C.双重提交 Cookie 模式← 正确答案
  • D.限制用户密码长度

答案:ABC。同步 Token 在请求体中携带服务端比对用的随机值,Origin 校验确认请求来源同源,双重提交 Cookie 让攻击者无法同时伪造两处一致的 Token,三者都是常见防御。限制密码长度与 CSRF 完全无关。

Q7. SameSite=Lax 下,以下哪种场景会携带 Cookie?

单选题 · mid

  • A.用户点击站内链接跳转← 正确答案
  • B.站外图片标签加载
  • C.跨站表单 POST 提交
  • D.iframe 跨站页面嵌入

答案:A。Lax 模式允许第一方请求和顶层导航的 GET 请求携带 Cookie,因此站内链接跳转会带。站外图片、跨站 POST、跨站 iframe 属于子资源或跨站上下文,浏览器不会携带 Cookie,这正是 Lax 防 CSRF 的关键机制。

Q8. 关于 DOM 型 XSS,下列说法正确的是哪几项?

多选题 · hard

  • A.攻击的最终执行发生在客户端← 正确答案
  • B.服务端过滤不能完全防御← 正确答案
  • C.与 document.write 等 API 相关← 正确答案
  • D.只能通过 HTTPS 防护

答案:ABC。DOM 型 XSS 的 payload 不经过服务器或仅在服务器存储后进入前端 DOM API 执行,所以最终执行在客户端,服务端过滤难以覆盖所有路径,document.write、innerHTML 正是常见注入点。HTTPS 不参与 DOM 执行防护,它只保护传输。

Q9. CSP 中 script-src 'self' 表达的含义是下列哪一项?

单选题 · mid

  • A.只能执行同源加载的脚本← 正确答案
  • B.禁止所有 JavaScript 执行
  • C.内联脚本全部放行不拦截
  • D.脚本必须先进行加密验证

答案:A。'self' 表示只信任当前站点自身加载的脚本,外域脚本和内联脚本都被拒绝。它并非禁止所有 JS,只是限制来源。内联全部放行是 'unsafe-inline' 的行为,加密验证也不是 CSP 的语义。

Q10. 关于 CSRF Token 的设计,下列表述正确的有哪些?

多选题 · extreme

  • A.Token 应绑定具体用户会话← 正确答案
  • B.Token 应放置于请求体或头部← 正确答案
  • C.Token 存入 Cookie 单独校验即可
  • D.每次请求复用同一个静态 Token

答案:AB。Token 必须与会话绑定并在每次会话中独立生成,且放在请求体或自定义请求头中随请求提交。若只放 Cookie,浏览器会在跨站请求中自动携带,攻击者无需读取即可利用;复用同一静态 Token 则让攻击者可以预先获取并重放,都违背了「动态暗号」的设计初衷。