题目给了代码:
整理一下:
#! /usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(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)
py的flask框架,有三个路由:
/De1ta
/geneSign
/
然后就是代码审计,先从第一个路由geneSign
入手:
先get param
参数,action
赋值为scan,然后交给getSign
方法,然后getSign方法利用md5加密(secert_key + param + action)
并回显,secert_key
未知.
第二个路由De1ta
:
get 参数 param
,get cookie action
和sign
,remote_addr获取ip
,然后通过waf(param)来对一些协议进行过滤,所以用协议读取文件的方法行不通,然后把四个参数交给Task类种进行下一步操作,最后将task.Exec()
的内容以字符串的形式返回。
第三个路由就没什么东西可以分析了。
分析完三个路由,可以看出,解题思路应该放在第二个路由。
所以到Task类种进行下一步分析:
__init__
这个方法就没什么可以分析的了。
重点在Exec()中:
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
咋一看,这个方法中有open(‘w’),open(‘r’),就很有可能是把flag.txt写入,然后再读出,咋一看看不出来也不要紧,一步一步分析,首先判断checkSign()
,移步到checkSign:
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
当sign值 == getSign(action, param)的值时,返回Ture,再移步getSign:
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
通过之前分析可以得到,param和action是可控参数,但是secert_key是未知的,但是好巧不巧,之前分析过的第一个路由就恰巧调用了这个getSign的方法,并且有return值,尝试payload:
/geneSign?param=
返回了一串getSign加密后的值:
b34ea5b8979c0d637ebcc71778ef7ee4
那么就可以通过第一个路由来回显我们所需要的sign的值。先不急着构造payload获取sign的值,继续分析Exec方法:
如果成功进去if判断语句内,判断"scan"是否在action参数内,有就将param的内容写入(如果param参数存在的话),然后判断"read"是否也在action参数内,有就read()。
那么经过这么一分析,思路就很明确了:
一、用geneSign路由回显getSign的值传给sign
二、checkSign()里的action和param和geneSign路由用的参数相同就能进入第一个判断语句
三、action同时包含scan和read后就能回显param的内容
所以我们要回显的param就是flag.txt,而getSign中md5加密是通过+
号拼接而成的字符串,即:
假设
secret_key = cccc,
param=flag.txt,
action=scan,
那么加密就是md5(“ccccflag.txtscan”),虽然在geneSign路由中,action的值已经是固定不变的了,但是通过加号拼接也可以达到action同时包含read和scan的效果,payload:
/geneSign?param=flag.txtread
拼接得md5(ccccflag.txtreadscan)
,返回
f62a71f8bd123044f4d315e7a97f021b
得到sign的值,然后:
就能得到flag。这个action和param通过拼接也得md5(“ccccflag.txtreadscan”),得到的加密值和sign值一样,并且满足read和scan在action中。
哈希长度扩展攻击可以参考这位师傅的博客:
https://www.freebuf.com/articles/database/164019.html,
虚拟机突然炸了,不行再截图,搞好了再来写,可以先看看其他师傅的wp:
https://xz.aliyun.com/t/5927#toc-1