这个题更偏向于代码审计。耐住性子慢慢理解,还是挺简单的。
很直接哦,就给源码。这么看不好看,得去pycharm里修正一下格式
#放在pycharm里Ctrl+Alt+L将代码格式化一下
1 #! /usr/bin/env python2 #encoding=utf-83 from flask import Flask4 from flask import request5 import socket6 import hashlib7 import urllib8 import sys9 import os10 import json11 reload(sys)12 sys.setdefaultencoding('latin1')13 14 app = Flask(__name__)15 16 secert_key = os.urandom(16)17 18 19 class Task:20 def __init__(self, action, param, sign, ip):21 self.action = action22 self.param = param23 self.sign = sign24 self.sandbox = md5(ip)25 if(not os.path.exists(self.sandbox)): #SandBox For Remote_Addr26 os.mkdir(self.sandbox)27 28 def Exec(self):29 result = {}30 result['code'] = 50031 if (self.checkSign()):32 if "scan" in self.action:33 tmpfile = open("./%s/result.txt" % self.sandbox, 'w')34 resp = scan(self.param)35 if (resp == "Connection Timeout"):36 result['data'] = resp37 else:38 print resp39 tmpfile.write(resp)40 tmpfile.close()41 result['code'] = 20042 if "read" in self.action:43 f = open("./%s/result.txt" % self.sandbox, 'r')44 result['code'] = 20045 result['data'] = f.read()46 if result['code'] == 500:47 result['data'] = "Action Error"48 else:49 result['code'] = 50050 result['msg'] = "Sign Error"51 return result52 53 def checkSign(self):54 if (getSign(self.action, self.param) == self.sign):55 return True56 else:57 return False58 59 60 #generate Sign For Action Scan.61 @app.route("/geneSign", methods=['GET', 'POST'])62 def geneSign():63 param = urllib.unquote(request.args.get("param", ""))64 action = "scan"65 return getSign(action, param)66 67 68 @app.route('/De1ta',methods=['GET','POST'])69 def challenge():70 action = urllib.unquote(request.cookies.get("action"))71 param = urllib.unquote(request.args.get("param", ""))72 sign = urllib.unquote(request.cookies.get("sign"))73 ip = request.remote_addr74 if(waf(param)):75 return "No Hacker!!!!"76 task = Task(action, param, sign, ip)77 return json.dumps(task.Exec())78 @app.route('/')79 def index():80 return open("code.txt","r").read()81 82 83 def scan(param):84 socket.setdefaulttimeout(1)85 try:86 return urllib.urlopen(param).read()[:50]87 except:88 return "Connection Timeout"89 90 91 92 def getSign(action, param):93 return hashlib.md5(secert_key + param + action).hexdigest()94 95 96 def md5(content):97 return hashlib.md5(content).hexdigest()98 99
100 def waf(param):
101 check=param.strip().lower()
102 if check.startswith("gopher") or check.startswith("file"):
103 return True
104 else:
105 return False
106
107
108 if __name__ == '__main__':
109 app.debug = False
110 app.run(host='0.0.0.0')
对各个函数逐一分析。先从路由开始。
@app.route("/geneSign", methods=['GET', 'POST'])def geneSign():param = urllib.unquote(request.args.get("param", ""))action = "scan"return getSign(action, param)
访问这个/geneSign路径,我们可以控制一个参数param,action已经被固定。然后去访问getSign函数。跟踪到getSign,看看它到底要干嘛。
def getSign(action, param):return hashlib.md5(secert_key + param + action).hexdigest()
将(secert_key+param+"scan")进行md5。
然后接着看一下第二个路由。
@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())
访问/De1ta路径。有4个参数"action","param","sign","ip"。我们能控制其中三个。"param"由get传入。"action","sign"由cookie传入。param会被waf函数检测。最终一下waf函数
def waf(param):
101 check=param.strip().lower()
102 if check.startswith("gopher") or check.startswith("file"):
103 return True
104 else:
105 return False
很简单哦,就是检测了一些协议问题。没什么影响。返回上次的地址。创建了一个Task对象,并将刚才的4个参数传入。继续追踪Task又是什么妖魔鬼怪。
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
第一步哦,初始化。直接略去。第二部,Task里的Exec函数,首先进入checkSign函数。继续追踪
def checkSign(self):if (getSign(self.action, self.param) == self.sign):return Trueelse:return False
又是调用getSign函数,并将action,param参数传入,进行md5,把返回的值与sign进行判断,相同返回True。然后继续返回Exec函数。 然后判断"scan"是否存在action变量里边。然后又执行scan函数。接着追踪scan函数功能。
def scan(param):socket.setdefaulttimeout(1)try:return urllib.urlopen(param).read()[:50]except:return "Connection Timeout"
scan函数能阅读文件!参数param还可以控制,并将返回值写入文件。返回Exec函数。然后再判断read是否存在于action,读取result.txt的值。所以action参数里边必须包含read与scan才能读取flag。而且必须获取sign的值。怎么获取呢?访问路由/geneSign即可
首先看路由/geneSign。假设secert_key是xxx。那么最终结果sign=MD5("xxx"+param+"scan")。
再看/De1ta路由Exec函数的要求MD("xxx"+param+action)=sign,其中param只能等于flag.txt才能被scan函数读取。
所有参数构造就很有趣了。在路由/geneSign下,param=flag.txtread-->MD5(xxxflag.txtreadscan)
再/De1ta路由下,param=flag.txt&action=readscan--->MD5(xxxflag.txtreadscan)。
这样不就相等了吗。
所以我们先获取md5值
再去构造数据包访问/De1ta
这样就拿到flag了。payload挺简单的,就是要读懂源码