浅析CSRF的防御和攻击案例
前言
之前看了美团写的一篇CSRF的文章, 写的很好
https://juejin.im/post/5bc009996fb9a05d0a055192
但是现在有些点发生了一些变化, 因此我想从2020年的角度来重新看CSRF的防御
当然我依然建议先阅读上面的文章, 部分相同的地方就不再细述了
CSRF原理
CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
CSRF的攻击流程如下
1.受害者登录了目标网站
2.攻击者向受害者发送恶意链接
3.受害者点击链接, 链接中的js代码执行, 向目标网站发送某个请求
4.由于用户已登录, 请求会被成功执行
我们可以看到主要的点有3个
1.目标网站会保存登录状态
这个是十分普遍的, 假如你登录了一次B站, 关闭标签页后再打开, 你发现你要重新登录, 这样的用户体验就很糟糕了, 因此大部分网站都会保存登录状态
2.请求被成功执行
这个是关键点, 要实现的前提就是我们发送的请求必须被服务器正常的验证通过并成功执行, 如果存在登录验证之外的验证(例如后面的CSRF-token)则可能会使得请求不会执行, 攻击不成功
3.用户点击链接
我们在进行渗透测试的时候, 应该默认用户会点击, 而不是默认用户会有足够的安全意识, 并且实际上大部分用户都会点击的2333如果不点, 是你的描述不够诱人
防御上, 主要是从1和2入手
防御
同源检测
通过对HTTP请求中的Origin
或者Referer
字段来判断请求的来源, 对不在白名单内的来源进行过滤
这个检测的问题在于
1.这两个字段为空
2.白名单的实现
空字段
我们是通过提取Origin
或者Referer
来判断来源, 那么如果字段为空呢?
比如说在302重定向的时候, 为了保护来源而不会携带Origin
字段, Referer
字段会受到Referer Policy
规则的限制, 比如
<img src="http://example.com/" referrerpolicy="no-referrer">
这样的请求将不会携带Referer
头, 具体的内容可以查看
https://juejin.im/post/5bc009996fb9a05d0a055192#heading-9
因此对于空字段的情况, 要看服务器在实现上是否允许通过
白名单
白名单的话其实和CORS的绕过差不多, 无非都是看他的正则写得有没有问题, 比如下面的例子
w+example.com
那我们可以用
fakeexample.com
来绕过, 再比如下面的例子
^example.com
那可以用
example.com.fake.com
来绕过, 这都是正则没写好的例子, 大厂的正则也会有或多或少的问题2333
因此这个防御方式应当作为辅助而不是主要的防御方式, 当然我认为应该不会有大公司是单纯靠这个来检测的2333
CSRF token
从第2点防御, 这也是现在用的最多的一种方式, 主要的表现为在表单或者cookie中加入一个hidden的token值
<input type="hidden" name="csrfmiddlewaretoken" value="xxxx">
现在大部分框架已经内置了CSRF token的实现, 例如在django中, 可以通过下面的代码来实现一个带有token的表单
<form method="post">{% csrf_token %}
这个值必须满足 不可预测 的要求, 一般的表现如下
每次刷新都会改变
在一段时间内有效
验证不通过不会执行请求
在CTF中有一类题目是通过逐位注入的方式来获取token, 但是在现实中, 这个值由于是一直变化的, 因此注入并没有意义.
SameSite cookie
从第1点进行防御, 是目前流行的方法, 也是写这篇文章的动机. 前面的文章提到
另外一个问题是Samesite的兼容性不是很好,现阶段除了从新版Chrome和Firefox支持以外,Safari以及iOS Safari都还不支持,现阶段看来暂时还不能普及。
在2020年的今天, 主流的浏览器已经准备 / 开始SameSite的普及了
https://medium.com/@azure820529/chrome-80-後針對第三方-cookie-的規則調整-default-samesite-lax-aaba0bc785a3
简单来说, 大概在2020年底之前, 在Chrome中, SameSite属性的默认值为Lax
, 而不是现在的None
, 如果你没有设置SameSite的值, 也会被设置为Lax
, 具体的时间表可以参考
https://www.chromium.org/updates/same-site
Firefox等浏览器也在积极的跟进.
先来回顾一下SameSite cookie的三种模式
Strict
用户在A页面登录后, 页面在cookie中存入了用户登录的凭证cookie: login=1; SameSite=Strict; Secure
, 当用户从百度进入A页面或者其他方式对A页面进行跨域请求时, 都不会携带cookie, 也就是说我们处于未登录状态
Lax
用户在A页面登录后, 页面在cookie中存入了用户登录的凭证cookie: login=1; SameSite=Lax; Secure
, 当用户从百度进入A页面, 使用的是a
标签进行跳转, 允许携带cookie, 同样允许的操作还有link
标签的prerender
, GET
方式的form
标签, 共三种情况允许携带cookie, 我们此时为登录状态, 其他情况则不允许
None
用户在A页面登录后, 页面在cookie中存入了用户登录的凭证cookie: login=1; SameSite=None; Secure
, 当用户从百度进入A页面或者对A页面进行任意的跨域请求, 浏览器都会带上cookie进行访问, 我们此时为登录状态
表格总结如下
另外要注意的是SameSite
属性在配置了Secure
属性(cookie只能通过https传输)的情况下才会生效, 单独使用不会生效
# 无效
Set-Cookie: SESSIONID=test; SameSite=None
# 有效
Set-Cookie: SESSIONID=test; SameSite=None; Secure
案例
为了加深理解, 找了一些比较新的CSRF案例进行学习
Microsoft子域CSRF更改用户信息
https://medium.com/@adeshkolte/cross-site-request-forgery-vulnerability-leads-to-user-profile-change-in-microsoft-express-logic-dc3481ab47ba
这里是找到了一个没有token保护的站点进行用户信息更改, 当然这种漏洞在国内的话应该不会通过的2333
Google子域CSRF删除账户
https://santuysec.com/2020/01/21/google-bug-bounty-csrf-in-learndigital-withgoogle-com/
可以看到这个站点是有token保护的, 但是在删除账户的时候, 并没有对token进行正确的校验, 也就是说我们只需要提供一个有效的token(我们自身的token)即可通过校验, 看一下poc就可以明白了
const url = 'https://learndigital.withgoogle.com/digitalgarage/profile/wipeout';
fetch(url, {
method: 'POST',
credentials: 'include',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'xsrf_token=<attacker_xsrf_token>'
});
Facebook CSRF账号接管
https://ysamm.com/?p=185
这是一个 25000刀 的漏洞, 值得一提的是作者发现了很多Facebook相关的漏洞, 有兴趣的可以去他的博客了解
漏洞利用主要分为两部分
第一部分是通过服务端的一个跳转
https://www.facebook.com/comet/dialog_DONOTUSE/?url=XXXX
在跳转到目标url的时候会自动带上csrf token, 使得后面的请求能够成功执行, 他提出了三种利用方式
1.在时间轴发布信息
2.删除个人资料图片
3.删除账户(需要用户输入密码)
这种方式是利用网站自身的特点绕过了CSRF保护, 而作者为了加大危害, 提出了第二部分, 也就是Account Takeover(账号接管)
一般来说账号接管是重置用户密码, 这种情况下我们需要将攻击者的邮箱添加到用户账号里面, 然后使用邮箱进行密码重置, 流程简化如下
1.用户点击链接, 添加攻击者邮箱到账户
2.用户点击邮箱中的确认链接
这里攻击者如果直接点击确认链接的话, 应该绕不过OAUTH认证, 所以需要由用户来进行点击, 这就引申出一个问题, 怎么让用户点击两次?
这个问题在实际中实行难度比较高, 你不能说
“hey, 这里有条消息你看一下”
“hey, 又有一条消息, 你再看一下”
虽然我们前面说要认为用户会点击, 但是连续点两次这种操作不符合直觉, 那么这个问题就变成, 怎么不用让用户点击两次?
作者非常巧妙的找到了另外一个跳转点使得从double click变成one click, 这里简单分析一下步骤
1.发送一个url给用户
https://www.facebook.com/comet/dialog_DONOTUSE/?url=
/ajax/appcenter/redirect_to_app%3fapp_id={ATTACKER_APP}%26ref=appcenter_top_grossing%26redirect_uri=https%3a//www.facebook.com/v3.2/dialog/oauth%3fresponse_type%3dtoken%26client_id%3d{ATTACKER_APP}%26redirect_uri%3d{DOUBLE_URL_ENCODED_LINK}%26scope%3d&preview=0&fbs=125&sentence_id&gift_game=0&scopes[0]=email&gdpv4_source=dialog
攻击者将ATTACKER_APP
授权给用户,然后重定向到 https://www.facebook.com/v3.2/dialog/oauth
使用/v3.2/dialog/oauth
进行认证, 绕过Facebook重定向保护,它将使用具有允许该应用程序范围的access_token自动重定向到攻击者网站(这种情况无需用户干预,因为该应用程序已使用端点/ ajax / appcenter / redirect_to_app进行了授权), 也就是DOUBLE_URL_ENCODED_LINK
2.攻击者网站会收到用户的access_token, 可以用于标识受害用户. 然后攻击者网站跳转到如下url
https://www.facebook.com/comet/dialog_DONOTUSE/?
url=/add_contactpoint/dialog/submit/%3fcontactpoint={EMAIL_CHOSEN}%26next=
/v3.2/dialog/oauth%253fresponse_type%253dtoken%2526client_id%253d{ATTACKER_APP}%2526redirect_uri%253d{DOUBLE_URL_ENCODED_LINK]
这个url执行的是添加攻击者邮箱到用户账号, 并再次重定向到攻击者网站
3.此时攻击者邮箱会收到一封邮件, 提取邮件中的网址并进行二次重定向, 使得邮箱成功添加到用户账号
具体的细节可以看原文 https://ysamm.com/?p=185
我们再分析一下他的跳转流程
用户点击链接 => 进入Facebook漏洞页面, 然后利用fapp和oauth的机制跳转到攻击者网站 => 攻击者网站跳转回facebook, 添加邮箱, 再跳转回攻击者网站=> 攻击者网站提取邮件链接, 跳转到邮件验证链接 => 成功添加
看起来会有点复杂, 但是攻击时间很短, 如果邮件发送的快的话只需要几秒就可以完成了, 方法十分巧妙, 可以看出作者对facebook的机制理解的很深.
后记
大部分情况下CSRF的利用并不复杂, 且常常与Account Takeover结合, 上面的三个案例都是来自国外的知名大厂, 所以不需要担心这种简单的漏洞不存在或者是已经被挖完了, 真正的难点是在于怎么去发现存在CSRF的端点.
“Simple is not synonymous for easy.”
“简单不等于容易.”Stephane Castellani
参考
1.https://juejin.im/post/5bc009996fb9a05d0a055192
2.https://www.ruanyifeng.com/blog/2019/09/cookie-samesite.html