Skip to content

XSS、CSRF、点击劫持攻击原理与防御

难度等级:🟡 中级(2-4年)
分类:浏览器安全
归档日期:2026-09-03


📚 理论知识

一、XSS(跨站脚本攻击)

核心定义:攻击者想办法把自己的恶意脚本"混"进目标网站的页面里,让这段脚本在受害者的浏览器里,以"目标网站自己的脚本"这个身份执行。因为脚本运行在目标网站的源(origin)下,它能读到这个网站的 Cookie、能操作这个网站的 DOM、能以用户的身份发请求——同源策略在这里保护不了用户,因为脚本"看起来"就是网站自己的代码。

三种类型

  • 反射型(Reflected XSS):恶意脚本作为参数拼在 URL 里(比如 search?q=<script>...</script>),服务端没做转义就把这个参数原样"反射"回页面并执行。特点是不经过数据库存储,需要诱导用户点击一条精心构造的恶意链接才能触发,攻击范围局限在点击了链接的人。
  • 存储型(Stored XSS):恶意脚本被服务端存进了数据库(比如评论区、用户昵称、商品描述这类允许自由输入的字段),之后任何人只要访问了包含这段内容的页面就会中招,不需要专门诱导点击链接,危害面更广、持续时间更长,是三种里危害最大的。
  • DOM 型(DOM-based XSS):全程不经过服务端,纯粹是前端 JS 自己把不可信的数据(location.hashlocation.searchdocument.referrer 等)直接塞进了 DOM(比如 innerHTML),浏览器解析这段 DOM 时触发执行。常见于纯前端渲染、SPA 场景。

前端防御手段

  1. 输出转义 / 避免危险 API:默认把用户输入当纯文本渲染。React 的 {} 插值默认会转义,但 dangerouslySetInnerHTML、Vue 的 v-html 会绕过转义,直接插入未处理的用户数据就是在给自己挖坑。
  2. 富文本场景做白名单过滤:像评论区支持基础排版这种必须渲染 HTML 的场景,用 DOMPurify 这类库做白名单清洗,只保留安全的标签和属性,过滤掉 <script>onerror 这类危险内容。
  3. CSP(Content-Security-Policy):即使代码层面有疏漏被注入了脚本,严格配置的 CSP 也能阻止内联脚本执行、限制脚本只能从白名单来源加载,是最后一道纵深防御。
  4. HttpOnly Cookie:让 JS 读不到敏感 Cookie(比如登录态),即使 XSS 成功注入了脚本,也偷不走这部分凭证。
  5. 避免把用户输入拼进 eval()new Function()setTimeout(字符串, ...) 这类会把字符串当代码执行的 API。

二、CSRF(跨站请求伪造)

核心定义:利用浏览器"同源请求会自动携带该域名下的 Cookie"这个机制。用户在目标网站保持登录状态时,如果被诱导访问了一个恶意页面,这个恶意页面向目标网站发起请求(比如一个自动提交的表单、一个 <img src="目标网站/transfer?to=attacker&amount=1000">),浏览器会自动把用户在目标网站的登录 Cookie 带上,服务端单看这个请求,完全无法区分它是用户本人主动发起的,还是被恶意页面伪造的。

和 XSS 的本质区别(这是最常被追问的点):

  • XSS 攻击的是"内容"层面——恶意代码本身在目标网站的上下文里被执行,攻击者能拿到执行结果、能读能写。
  • CSRF 攻击的是"请求来源"层面——攻击者不需要也没办法在目标网站里执行任何代码,只是"借用"用户已经登录的身份,伪造一次看起来合法的请求。CSRF 是盲打,攻击者发出请求后拿不到任何返回内容,只能让操作"发生",不能"窃取数据"。

防御手段

  1. SameSite CookieStrict(跨站请求完全不带这个 Cookie)/ Lax(跨站的顶层导航 GET 请求允许携带,其他跨站请求禁止,是现代浏览器的默认值)。这是目前最主流、成本最低的一层防御。
  2. CSRF Token:服务端生成一个随机 token 下发给前端(写入表单隐藏字段或响应头),前端提交请求时带上,服务端校验这个 token 是否和当前 session 匹配。第三方恶意页面读不到这个 token(同源策略挡住了它跨站读取目标网站页面内容的能力),所以伪造不出携带正确 token 的请求。
  3. 校验 Origin / Referer 头:服务端核对请求头里带的来源信息是不是自己的域名。
  4. 敏感操作用 POST 而不是 GET,避免通过 <img><link> 这类标签天然发起的 GET 请求就能触发。

三、点击劫持(Clickjacking)

核心定义:攻击者用一个透明的 iframe把目标网站叠加在自己精心设计的诱饵页面上方,视觉上诱导用户点击诱饵页面的某个按钮(比如"点击领取奖品"),但实际上鼠标点击命中的是被藏起来的 iframe 里目标网站的敏感按钮(比如"确认转账""同意授权")。用户全程不知道自己真正点击的是哪个页面。

防御手段

  1. X-Frame-Options 响应头DENY(完全禁止被任何页面用 iframe 嵌入)或 SAMEORIGIN(只允许同源页面嵌入)。
  2. CSP 的 frame-ancestors 指令:更现代的替代方案,语法更灵活(可以指定一个允许嵌入的来源列表,而不只是"完全禁止/仅同源"两个选项),实践中常和 X-Frame-Options 一起设置做兼容。
  3. Frame-busting JS(在页面里判断 window.top !== window.self 就强制跳出 iframe)不够可靠,可以被 sandbox 属性或一些技巧绕过,只能当补充手段,不能作为唯一防线。

四、三者对比

XSSCSRF点击劫持
攻击本质让恶意代码在目标网站上下文里执行伪造请求,借用用户已登录的身份诱导用户在不知情下点击目标网站的真实按钮
需要执行恶意代码吗需要不需要不需要
能否拿到返回数据能(脚本在页面里能读能写)不能,是盲打不能,只是"借用"点击行为
核心防御转义/过滤输入、CSP、HttpOnly CookieSameSite Cookie、CSRF TokenX-Frame-Options、CSP frame-ancestors

常见误区

问题要点
认为 HttpOnly Cookie 能防 CSRF不能。HttpOnly 只是让 JS 读不到 Cookie 防 XSS 窃取,但浏览器发请求时依然会自动带上这个 Cookie,CSRF 利用的正是"自动携带"这个机制,跟 JS 能不能读没关系。
认为 CSRF Token 存在 Cookie 里就够了不够。如果 Token 本身也通过 Cookie 下发和携带,攻击者一样能"自动带上",起不到校验作用。正确做法是 Token 要通过表单字段或自定义请求头传递,不能让浏览器自动附加。
认为同源策略能防住 CSRF不能。同源策略限制的是"跨站读取响应内容",不限制"跨站发起请求"(表单提交、<img> 标签本来就允许跨站发送),CSRF 正是钻了这个空子。
认为点击劫持只是"UI 骗局",危害不大危害取决于目标网站里被劫持的操作,如果劫持的是"一键授权 OAuth 权限""确认转账"这类高危操作,后果可以非常严重。

❌ 错误答法示范

错误示例 1:「防止 CSRF 只要用 HttpOnly Cookie 就行,这样 CSRF 脚本读不到 Cookie 就没法伪造请求了。」
❌ 问题:混淆了 XSS 防御和 CSRF 防御。HttpOnly 阻止的是 JS 读取 Cookie(防 XSS 窃取),但 CSRF 根本不需要读 Cookie,浏览器发请求时会自动把 Cookie 附加上去,这一步和 HttpOnly 无关。

错误示例 2:「XSS 和 CSRF 都是跨站攻击,是同一类问题,防御思路是通用的。」
❌ 问题:两者攻击面完全不同——XSS 是内容层面的代码注入,CSRF 是请求伪造,前者防御靠"过滤输入 + 隔离脚本执行环境",后者防御靠"让浏览器不自动携带凭证 / 校验请求是否可信",防御手段不能互相替代。


🎤 模拟面试作答

这道题我按 XSS、CSRF、点击劫持三个分开说,最后串一下它们的区别。

XSS 本质是让攻击者的脚本在目标网站的上下文里执行,因为浏览器认为这段脚本就是网站自己的代码,所以能读 Cookie、能改 DOM、能以用户身份发请求。它分三种:反射型是把恶意参数拼在 URL 里,服务端没转义就原样返回执行,需要诱导点击链接;存储型是恶意内容被存进了数据库,比如评论区,谁访问都会中招,危害最大;DOM 型完全在前端,是前端代码自己把 location.hash 这类不可信数据直接塞进了 innerHTML。防御上主要是几层:输出的时候做好转义,别乱用 v-html/dangerouslySetInnerHTML;富文本场景用 DOMPurify 这类库做白名单过滤;配置 CSP 兜底,即使有漏洞也能挡住未授权脚本执行;敏感 Cookie 设成 HttpOnly,防止就算 XSS 成功了也偷不走登录凭证。

CSRF 和 XSS 的本质区别在于它不需要执行任何恶意代码,是借用了浏览器"同源请求自动带 Cookie"这个机制,诱导用户在已登录状态下访问一个恶意页面,恶意页面发请求过去,浏览器自动把登录 Cookie 带上,服务端没法判断这是不是用户本人发起的。而且 CSRF 是盲打,攻击者拿不到任何返回内容,只能让操作发生。防御主要靠 SameSite Cookie,跨站请求不自动带 Cookie 了;或者用 CSRF Token,因为同源策略挡住了第三方页面读取目标网站的能力,所以伪造不出正确的 Token。

点击劫持是用一个透明 iframe 把目标网站叠在诱饵页面上,用户点的是诱饵按钮,实际命中的是 iframe 里目标网站的真实按钮。防御主要是 X-Frame-Options 头,或者更现代的 CSP frame-ancestors 指令,直接禁止或限制页面被嵌入 iframe。

三者放一起看,核心区别是:XSS 破坏的是"内容"的可信度,CSRF 破坏的是"请求来源"的可信度,点击劫持破坏的是"用户点击行为"本身的可信度,所以防御手段完全不通用,得分开做。

如果面试官感兴趣,我还可以聊聊 CSRF Token 具体怎么在前后端之间传递、或者 CSP 的 nonce 机制是怎么在允许必要内联脚本的同时挡住攻击者注入的脚本的。


❓ 面试官追问预测

追问 1:HttpOnly Cookie 已经能防 XSS 窃取 Cookie 了,那为什么还需要额外配置 CSP?

应对思路:HttpOnly 只保护"Cookie 不被读走"这一件事,但 XSS 脚本一旦执行,还能做很多别的坏事——篡改页面内容做钓鱼、劫持用户后续所有操作、读取页面里其他没有 HttpOnly 保护的数据(比如 localStorage)、发起任意请求。CSP 是从"根本不让未授权脚本执行"这个更早的环节兜底,两者防的是不同层面,不是互相替代关系。

追问 2:SameSite=Lax 已经是浏览器默认值了,是不是就不需要再单独做 CSRF Token 了?

应对思路:Lax 只挡住了"跨站的非导航请求"(比如跨站的 POST 表单、Ajax),但对"跨站顶层导航的 GET 请求"是放行的,这类场景依然可能被利用;而且 SameSite 是客户端行为,依赖用户浏览器版本支持,对老旧浏览器不生效。所以实践中通常是 SameSite 打底 + 关键接口再加 CSRF Token 做双重保险,而不是二选一。

追问 3:DOM 型 XSS 和存储型 XSS 都会把恶意内容渲染出来,怎么区分排查?

应对思路:看恶意内容的"来源路径"——如果恶意脚本是从服务端接口返回的数据(哪怕接口本身没被篡改,是数据库里存了脏数据)触发的,是存储型;如果整个流程根本没经过服务端,是纯前端代码把 URL 上的参数或 document.referrer 直接插入了 DOM,那就是 DOM 型。排查时看 Network 面板里响应体是否已经包含恶意内容就能大致判断。

追问 4:点击劫持既然设置了 X-Frame-Options: DENY,是不是所有 iframe 嵌入都会失败,会不会误伤正常的业务场景(比如自己产品内部需要 iframe 嵌套的页面)?

应对思路:会。所以实际配置时要按页面粒度区分——大部分敏感操作页面(支付、授权确认)设 DENY,需要被自己产品内其他页面嵌入的场景设 SAMEORIGIN,如果是需要被指定的第三方站点嵌入(比如开放平台场景),就要用 CSP 的 frame-ancestors 指定具体允许的来源列表,而不是简单粗暴地全部禁止。

追问 5:如果一个页面同时存在 XSS 漏洞,攻击者能不能借助 XSS 来绕过 CSRF Token 防御?

应对思路:能,这也是为什么说"一旦有 XSS,几乎所有其他防御都可能被绕过"。因为 CSRF Token 防的是第三方页面读不到 Token,但 XSS 脚本运行在目标网站自己的页面里,本来就有权限读到页面上任何地方的 Token(DOM 里、响应里),可以直接拿着正确 Token 发起请求。所以 XSS 是优先级最高必须堵住的漏洞,CSRF Token 只能防"没有 XSS"这个前提下的跨站伪造。

个人面试题归档,持续更新中