[De1CTF 2019]SSRF Me
一道python代码审计,发现审计能力真的很重要。
看到源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
|
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)): 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
@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')
|
分析:
有三个路由:/geneSign
/De1ta
/
/geneSign
路由是获取签名的 通过get给param传参 然后这里action不是我们能改变的
一起传入getSign()
返回 secert_key+param+action
的md5值 这里只有param我们能控制
1 2 3 4 5 6 7
| @app.route("/geneSign", methods=['GET', 'POST']) def geneSign(): param = urllib.unquote(request.args.get("param", "")) action = "scan" return getSign(action, param) def getSign(action, param): return hashlib.md5(secert_key + param + action).hexdigest()
|
再来看/De1ta
1 2 3 4 5 6 7 8 9 10
| @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())
|
get方式给param传参 cookie传action和sign的值
经过一个waf 这里用来防止用协议
然后传入Exec()
先经过checkSign函数检查签名是否符合
1 2 3 4 5
| def checkSign(self): if (getSign(self.action, self.param) == self.sign): return True else: return False
|
分别有两个if判断
第一个if: 当有action有scan时注意这里是in
而不是 ==
,生成一个tmpfile文件
将param传入scan函数
1 2 3 4 5 6
| def scan(param): socket.setdefaulttimeout(1) try: return urllib.urlopen(param).read()[:50] except: return "Connection Timeout"
|
这里能看到urllib.urlopen().read()
这里就是利用点
相当于打开param这个文件 题目也提示flag在flag.txt中
这也就是为什么waf要禁用file协议和gopher协议(也就是http协议的前身)
因为ssrf漏洞一般都可以搭配很多协议来用
然后将scan内容写入到tmpfile文件中 但是没法读
第二个if:当action中有read时 就会读出来赋值给result[data]然后 Exec函数返回result结果
做到这里当思路就很清晰
action同时包括scan和read时候既能写入文件也能读出文件,但是我们action的值是我们修改不了的该怎么办呢
再来看生成签名这里hashlib.md5(secert_key + param + action)
这里我们可以给param传入flag.txtread拼接起来 就算我们没有给action传read 最后md5值是将整个字符串拼接起来包含read值 这就可以了
9b231caf6bf82362fa5f78bf8da3d6ec
记录下我们的签名
成功!