从跨域与同源策略谈CSRF防御与绕过
之前偶然看到群里有小伙汁问这个token相关的问题,当时我酝酿了一下子,没想好怎么总结,今天来说一下
CSRF在过去还属于OWASP TOP10 ,现在已经不是了(补充一点:关于OWASP API 请参考https://www.cnblogs.com/iamver/p/14331357.html)
在实际中也没咋遇到过(不排除自己太菜的原因),但是一些原理还是有必要了解一下的 ,比如网上最常说的防御与绕过
0x01 前言
CSRF-Cross Site Request Forgery 跨站请求伪造
之前我在DVWA那部分介绍过一些CSRF(感兴趣的可以往前翻翻往期随笔)
原理简单来说,就是这张图
1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
2. 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行
额,所以说想利用CSRF的条件是:受害者正在访问你要攻击的网站A,且同时又打开了你的恶意链接
钓鱼佬狂喜
实际上,CSRF更像是一种“形式”,侧重于讨论对于用户请求的“伪造”、“篡改”,因此上述关于漏洞过程的描述中没有提到具体的、固定的代码和操作,而是简单描述了一下整个流程
突然想起一个事,觉得十分有必要说一下
所以这里补充一个知识点,关于跨域的问题
众所周知,浏览器存在同源策略(SOP即same-orgin policy),这个策略限制了不同源之间如何进行数据交互,属于一种安全机制
是否同源由URL的协议、域名、端口号决定,如果两个URL 的这三者均相同,则认为他们是同源的,否则有一个不同就是不同源,不同源之间相互访问资源会受到某些限制(注意不是任何资源都完全访问不了,而是某些方法会受到限制比如Document对象的很多属性啦、XHR生成的HTTP请求啦。。。)
那可麻烦了,如果按照这个标准,不同源的多了去了,难道都不能访问了?
于是就有了跨域,实现绕过同源策略
关于同源策略
- 通常允许跨域写操作(link、redirect、表单提交)
- 通常允许跨域资源嵌入(script、img、video...)
- 通常禁止跨域读操作(ajax)
- 可以正常发送请求,可以携带Cookie(withCredentials),但是浏览器会限制来自于不同域的资源的接收
请看下图:
即:
1.无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
2.无法接触非同源网页的 DOM
3.向非同源地址发送 AJAX 请求浏览器会拒绝响应
(注:AJAX(Asynchronous JavaScript + XML)是一种描述现有技术集合及标准的模型,主要是网页增量更新用的,不用重新加载整个界面。虽然说是叫XML,但是常用json。AJAX常见的格式如下,更详细的内容参考:https://www.cnblogs.com/caifenglin/p/7797811.html,我就不细说了)
<script type="text/javascript"> window.onload = function () {//页面刷新即加载 $.ajax({ url:'http://www.lcx.com', //请求URL地址 type:'GET', //请求方式GET POST async:true, //是否异步,或写false即同步,默认为true即异步 timeout:5000, //超时时间 dataType:'json', //返回的数据格式:json/xml/html/script/jsonp/text beforeSend:function(xhr){//请求发送之前进行的操作 console.log(xhr) console.log('发送前') }, success:function(data,textStatus,jqXHR){//请求成功之后的返回结果 console.log(data); }, //还可以加上complete:function(...){......},做请求完成的处理 error:function(xhr,textStatus){//请求失败之后的返回错误信息 console.log('错误',xhr.responseText); console.log(xhr); console.log(textStatus); } }) } </script>
常见的跨域有JSONP跨域与CORS跨域(当然不止这两种方法,还有很多种,例如:https://zhuanlan.zhihu.com/p/81809258,这篇文章也只是列举了一些作者认为主流的方式,还有其他的没列举出来)
JSONP(json with padding)利用了<script>标签的跨域能力实现了跨域数据访问(就比如说script标签的src不受同源策略限制),请求动态生成的JavaScript脚本同时带一个callback函数名作为参数。服务端收到请求后,动态生成脚本产生数据,并在代码中以产生的数据为参数调用callback函数
详情我就不再展开了,请参考:https://cloud.tencent.com/developer/article/1058337?from=information.detail.jsonp%E8%B7%A8%E5%9F%9F%E5%8E%9F%E7%90%86 (话说我今天要讲啥来着???)
抓包的时候,如果看到返回数据包中有长类似这样callback({ "name": "lcx" , "msg": "获取成功" }); 的东西,那就是JSONP格式的
关于JSONP格式与JSON的区别请看:https://blog.csdn.net/SmCai/article/details/86647288
JSONP威胁点通常有两个:1、对于输入的callback函数名过滤不严格,导致输入的数据直接输出到前端造成xss 2、JSONP劫持漏洞,由于对于来源域没有严格限制,因此来源于不安全的域的请求也会被响应
关于JSONP劫持漏洞参考:https://saucer-man.com/information_security/309.html、https://xz.aliyun.com/t/5143
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。通过该标准,可以允许浏览器向跨源服务器发出XMLHttpRequest 请求,从而克服了AJAX
只能同源使用的限制,进而读取跨域的资源。CORS允许Web服务器通知Web浏览器应该允许哪些其他来源从该Web服务器的回复中访问内容
简单来说,采用了简单请求的请求头中附带origin或者非简单请求用预检机制,并根据服务器返回的响应内容来实现跨域的约定
详情我也不展开了(展开能说到地老天荒),请参考:https://www.cnblogs.com/zhaosq/p/10511875.html、https://www.cnblogs.com/zhaosq/p/10529803.html
CORS配置漏洞参考:https://zhuanlan.zhihu.com/p/83099266、https://www.freebuf.com/articles/web/204023.html
关于这两种方法的跨域,这篇文章总结得很好:https://www.freebuf.com/articles/web/208672.html
JSONP与CORS漏洞总结:https://www.freebuf.com/articles/web/207802.html
说了一下跨域的内容,实际上有些通过这种跨域带来的安全问题引发的攻击也属于广义上的CSRF
比如,举个例子,一个用jsonp窃取token绕过后端检测实现CSRF的例子:https://www.cnblogs.com/blacksunny/p/9124578.html
综上,CSRF有以下特点:
CSRF的攻击一般来源于第三方域名;CSRF不能获取cookie但是可以借助浏览器特性去使用;接口的所有参数都是可以预测的或者说已知的
根据我上文所说,那么这里存在一个问题:cookie也是受到同源策略限制的,两个不同源的网站其中一个应该是不会让另一个获得自己的cookie的,那为什么CSRF攻击仍然有效?
答:
- 除了跨域 XHR 请求情况下,浏览器在发起请求的时候会把符合要求的 cookie 自动带上 (涉及:域名,有效期,路径,secure 属性)
- 跨域 XHR 的请求的情况下,也可以携带 cookie
- 浏览器允许跨域提交表单
cookie是可以跨域的,且在某些跨域情况下,浏览器中有页面或网站向某个域名发送请求时,其请求都会自动带上该域名下的所有满足条件的 cookie
总而言之,虽然说如果没有同源策略CSRF会更猖狂,但是同源策略阻止不了CSRF,同源策略确实是会拦截某些(不是全部)请求后的HTTP回复(而不是禁止请求执行),有些请求是可以跨域且附带cookie的,还有些情况下你啪的一下子请求成功发出去了,这次的攻击就完成了,后端程序已经执行了,是否有回复已经不重要了
一个cookie常见属性如下(domain---cookie对于哪个域是有效的、expires---cookie失效时间、httpOnly---设置cookie是否能通过 js 去访问,为true则不可以通过js访问cookie只能在服务端改,防XSS读取cookie例如document.cookie、path---这个cookie影响到的路径、samesite---用于防CSRF、secure---安全标志,指定为true后,只有在使用SSL链接即HTTPS的时候才能发送到服务器,如果是http链接则不会传递该信息、value---存储在cookie中的字符串值):
其中,和我这篇文章相关性最强的samesite留到下一小点CSRF防御细说
0x02 防御
1.Referer(referrer)
HTTP请求头中包含Referer字段表示请求来源,告诉服务器我是从哪个链接来的,如果是从恶意网站发出的请求Referer字段不会是正常的请求来源
这部分的校验是在后台代码中编写的
2.Origin
与Referer思想类似,但区别在于Origin只存在于同域的post请求(Referer存在于所有类型请求)或者某些跨域情形(比如我前文说的CORS跨域)中(如果浏览器不能获得请求源,但是满足这俩条件,也会携带Origin只不过值为null),且只包含是谁(哪个源)发出的请求(origin包括协议和域名还有端口;referer除此之外还有路径和查询参数),没有太多的信息,隐私方面好些。。。
3.token
说来说去,CSRF还是因为服务器无法判断当前的请求是否是合法用户的自定义操作
所以抵御CSRF的关键在于在正常请求中加上一些攻击者不能伪造的东西,且其不存在于cookie,灵魂发问如何证明你是你
于是可以在HTTP请求中单独加入一个参数---一个随机生成的令牌---token
服务器在用户登录后,会给用户一个唯一的合法令牌,每次操作都会对此令牌进行校验
对于post请求可以以类似下面的方式把token以参数的形式加入请求
<form method="post" action="/transfer">
<input type="hidden" name="csrf_token" value="token_value"/>
<input type="text" name="amount"/>
<input type="hidden" name="account"/>
<input type="submit" value="Transfer"/>
</form>
这里有小伙汁有个问题:如果放在了页面表单中的hidden字段中,那攻击者是不是可以通过下载页面的源码找到隐藏字段并再次进行伪造请求呢?
很显然是不可能的,因为每个用户的token都不一样,我的是我的,你的是你的,攻击者下载页面得到的token在被攻击者那边无效,根本通不过校验,而且攻击者是无法通过CSRF 拿到被攻击者的token的
可以把token放进cookie中 ,但是不能仅仅把token放在cookie中(当然存放token的cookie不要设置HTTP only,否则自己都无法正常使用了,如果有XSS能造成危害那另说),它必须也作为一个参数存在于HTTP请求体中
我们需要以表单提交的方式把token提供给后台服务器,后台服务器可以比较作为参数提交到服务器的token和用户cookie中的token是否一致,一致则继续校验token进行下一步操作,不一致直接GG
或者把token存在服务器session中,用户进行操作时,服务器会从此用户对应的session文件中取出token与用户提交到服务器的token进行某种对比,如果比较成功,则正常执行功能,否则我有理由怀疑你是CSRF
正如前文所说,CSRF最多也就是通过浏览器自动带上符合条件的cookie,却无法操作cookie来获取token并加进HTTP请求的参数中(这位师傅研究了一些泄露CSRF token的方法:https://xz.aliyun.com/t/7084),猜不到参数也无法伪造正确的token
这正是token能防CSRF的根本原因
4.samesite
此属性专门为了防止CSRF
有三个值:Strict、Lax、None
Strict:最严格,完全禁止第三方cookie ,跨站时任何情况下都不会发送cookie,只有同站请求允许携带cookie(例如在一个网站中有一个链接,点击会跳转到GitHub上,在设置了strict的情况下,GitHub永远都是未登录状态)
Set-Cookie: CookieName=CookieValue; SameSite=Strict;
Lax:一般严格,大多数情况下不发送第三方cookie,导航到目标网址的GET请求除外,导航到目标网址的GET请求只包括三种情况:链接、预加载请求、GET表单,即除了同站请求允许携带cookie之外也有特定的情况允许携带cookie
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
附带Strict与Lax之后,基本可以杜绝CSRF
None:关闭samesite属性(前提是必须同时设置Secure属性,否则无效),同站跨站请求都可以携带cookie
一个有效的设置:
Set-Cookie: widget_session=abc123; SameSite=None; Secure
参考链接:http://www.ruanyifeng.com/blog/2019/09/cookie-samesite.html
详细参考:https://zhuanlan.zhihu.com/p/137408482
5.验证码
思路与token类似,发送请求之前需要用户输入基于服务端判断的某种验证码
这东西强制了用户搞一个人机智障交互后,判断成功了,才能实现正常的请求,很粗暴很有效
无力吐槽
这东西说到底是个辅助的二次验证防御手段,如果每次每种操作都有验证码,体验极差,用户直接原地爆炸,不然就是开发者要爆炸
还有一些防君子不防小人的东西,思路类似的、或者杀敌一千自损八百的方法就不说了
在提交表单或者GET请求的地方,非静态操作处,凡是没有用户进行二次验证或没有token的地方大概率存在csrf漏洞
0x03 绕过
可以利用点击劫持(参考:https://www.cnblogs.com/-qing-/p/10871625.html)欺骗被攻击者(请求确实是你自己点击自己发出的啊)
或者更改请求方法(POST 改 GET)
如果采用了token验证,且后台代码存在问题,可以尝试删除token参数 或发送空token 或替换token
还有借助session固定攻击(参考:https://blog.csdn.net/h_mxc/article/details/50542038)和我上文提到的双token(请求参数与cookie中均有token)校验钻空子
如果有Referer验证,可以尝试去除Referer字段 或者后台基于白名单检查Referer,可以尝试绕过验证URL的正则表达式,可以参考:https://cloud.tencent.com/developer/news/360329
参考:https://xz.aliyun.com/t/6176
未经允许,禁止转载