Portfolio
← 返回博客列表
XSS和CSRF

2026-09-02 · 阅读 1

XSS和CSRF

XSS,CSRF

一次讲透 XSS 与 CSRF:从攻击原理到项目实战防御

这篇文章源自我自己在做一个全栈项目(Next.js + NestJS)时踩坑 + 面试被问爆之后的系统复盘。以前这两个概念我总是背了就忘、答题时还会串台,后来发现只要抓住"攻击者到底拿到了什么"这一条主线,整个知识体系就自动立起来了。希望这篇能帮你一次记住,不再混淆。

0. 先用一句话分清两者

  • XSS(跨站脚本攻击):攻击者把恶意代码伪装成数据塞进网页,浏览器把它当代码执行了——数据被当作代码
  • CSRF(跨站请求伪造):攻击者从头到尾拿不到你的 Cookie,他只是借用了"浏览器发请求会自动带 Cookie"这个机制,让服务器误以为是你在操作——冒名顶替

口诀:XSS = 植入内鬼,在页面里执行代码、偷东西;CSRF = 冒名顶替,不偷东西,借浏览器自动带 Cookie 的身份。

1. XSS:数据被当作代码

1.1 为什么会发生

本质原因只有一个:输出时没有正确转义/过滤,导致浏览器分不清"这是用户想说的话,还是要执行的命令",把数据当成了代码。

1.2 三种攻击类型(一张表判定)

类型恶意代码存在哪里触发条件典型场景
反射型URL 参数里服务器把参数原样回显到页面搜索结果页回显关键词、错误页回显 URL
存储型数据库里(评论、昵称、文件名任何用户打开页面就执行论坛评论区植入脚本,所有看帖的人中招
DOM 型URL 的 hash 等前端可读处前端 JS 自己用 innerHTML 插进去前端读 location.hash 直接塞进 DOM

判定口诀:看恶意代码存在哪里——存数据库 → 存储型;在 URL 参数、靠服务器回显 → 反射型;在 URL hash、靠前端 JS 自己插 → DOM 型。

一个容易判错的例子:文件名场景。用户上传的文件叫 <img src=x onerror=alert(1)>.jpg,文件名被存进数据库、被渲染到文件列表页——这是存储型,不是反射型!因为恶意代码存在数据库里。

1.3 四层防御(面试按这个顺序答,层层递进)

  1. 输出转义(主防):JSX 的 {file.name} 渲染时 React 自动转义,永远只是文字;
  2. 不用危险 API:文件名/用户输入不塞 dangerouslySetInnerHTML / innerHTML;富文本要用 DOMPurify 之类的库先消毒;
  3. HttpOnly(保底):就算 XSS 真的执行了,document.cookie 也读不到,Token 偷不走;
  4. CSP(兜底):响应头声明"只允许执行白名单来源的脚本",内联脚本直接拒绝执行——XSS 就算发生了也跑不起来。

React 两行对照

<div>{file.name}</div>                       // ✅ 自动转义,永远是文字
<div dangerouslySetInnerHTML={{ __html: userContent }} />  // ❌ 危险API,等于手动关掉保护

1.4 我项目里的实际方案

我的项目采用 JWT + GitHub OAuth 双重认证,通过 HttpOnly Cookie 存储 Token 来减小 XSS 的伤害:

  • 浏览器每次请求会自动带上这个 Cookie;
  • 但页面里的 JS 用 document.cookie 读不到它;
  • 所以就算 XSS 发生了,恶意脚本也偷不走 Token。

注意措辞:HttpOnly 是减小伤害,不是阻止攻击本身——脚本照样能执行(还能钓鱼、键盘记录),只是偷不走凭证。这句话在面试里说出来,比背十个概念更能体现你理解到位。

2.1 攻击原理

CSRF 的完整链条是:攻击者拿到了什么?——什么都没拿到。他只是诱导已登录用户访问 evil.com,页面里藏着一个:

<!-- evil.com 上的钓鱼页 -->
<img src="https://mybank.com/transfer?to=hacker&amount=10000" />

浏览器加载这张"图片"时,对 mybank.com 发起 GET 请求,并且自动带上 mybank.com 的 Cookie(浏览器的同源策略不拦截"发请求",只拦截"读响应")。服务器只认 Cookie 不认人,于是转账成功。

2.2 三种防御手段

  1. SameSite Cookie:设置 SameSite=Lax/Strict,只有站内正常导航才带 Cookie。evil.com 的 img 请求一到,Cookie 根本不会跟过去——攻击链条断在第一步
  2. CSRF Token:服务器给表单/页面埋一个随机 Token,提交时必须原样带回。evil.com 因为同源策略读不到你页面的内容,所以伪造不了 Token;
  3. Token 放请求头而不是 Cookie:JWT 放 Authorization 头意味着每个请求都要前端代码主动携带,而 evil.com 的 <img>/<form> 标签没有能力伪造自定义请求头——CSRF 天然免疫。

2.3 Token 放哪?一张镜像取舍表

这是很多文章不讲、但面试官爱追问的点:Header 和 Cookie 的安全模型互为镜像

放置位置防 CSRF防 XSS 偷取
Authorization 请求头✅ 天然免疫(标签带不了自定义头)❌ JS 能读到,怕 XSS
Cookie + httpOnly❌ 谁发的都带,怕 CSRF✅ JS 读不到,防偷

所以没有完美方案,只有组合拳:放 Header 就要严防 XSS;放 Cookie 就必须加 sameSite 防 CSRF

2.4 项目里的一行三防

我的项目(NestJS)里就是这一行配置,三个参数各防一件事:

res.cookie('token', token, {
  httpOnly: true,  // 防 XSS 偷:JS 读不到
  sameSite: 'lax', // 防 CSRF:第三方请求不带
  secure: true,    // 只走 HTTPS:明文传输防中间人
})

记忆钩子:每个"所以"都接在它的病后面——谁发的都带 → sameSite;JS 能偷 → httpOnly;明文传输 → secure。

3. XSS vs CSRF 终极对照表

维度XSSCSRF
攻击本质植入代码,页面里执行冒名顶替,借 Cookie 身份
攻击者拿到什么页面执行权(能偷能改)什么都没拿到,只借自动带 Cookie 的机制
漏洞根源输出未转义,数据被当代码服务器只认 Cookie 不认请求来源
触发方式打开被污染的页面诱导用户点开不明链接/访问恶意页
核心防御输出转义 + 危险 API 禁用SameSite + CSRF Token
兜底防御HttpOnly(减小伤害)+ CSP校验 Origin/Referer
组合拳XSS 成功后可直接发起 CSRF——

关于"冒名操作"归属的澄清(我曾经混淆的点):冒名是 CSRF 的唯一手段,但只是 XSS 的众多后果之一。说 XSS 危害时,说三件套就够:偷 Cookie、篡改页面钓鱼、键盘记录,最后可以补一句"XSS 成功后还能直接发起 CSRF 打组合拳"——这句是加分项。

4. 30 秒面试版答案(附彩蛋)

被问"讲讲 XSS"时:

XSS 是攻击者把恶意代码伪装成数据塞进网页,浏览器把它当代码执行了。它有三种类型:存储型(存数据库)、反射型(URL 参数回显)、DOM 型(前端 JS 自己插入)。防御上我项目里做了输出转义、禁用危险 API,并用 HttpOnly Cookie 存储 Token 做保底,再加 CSP 兜底。

主动补的一句(展示概念清晰):

而且 XSS 和 CSRF 能打组合拳——XSS 偷不到 HttpOnly Cookie,但脚本本身可以在页面里直接发起 CSRF 请求,所以两层防御缺一不可。

5. 总结记忆卡

  • XSS 一句话:数据被当作代码执行
  • CSRF 一句话:攻击者拿不到 Cookie,只借浏览器自动带的机制冒名操作
  • XSS 三类型判定:看恶意代码存在哪(数据库/URL参数回显/URL hash前端插)
  • XSS 四层防御:转义 → 危险API → HttpOnly → CSP(主防→保底→兜底)
  • Token 取舍镜像:Header 怕偷,Cookie 怕伪造
  • 一行三防:httpOnly 防偷,sameSite 防带,secure 走 HTTPS

本文基于个人项目实战与面试复盘整理,如有疏漏欢迎指出。