1. 程式人生 > 其它 >[De1CTF 2019]SSRF Me(Flask框架的程式碼審計)

[De1CTF 2019]SSRF Me(Flask框架的程式碼審計)

技術標籤:wp

參考文章:Flask框架快速入門學習

#! /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')

總的來看,有三個路由,分別對應'/'讀取code.txt'/De1ta'獲取三個引數action,param,sign,並且傳入四個引數給task的構造方法,並將task類的task.Exec()方法執行輸出。"/geneSign"將一些引數加密。
task類中,Exec方法很關鍵:
首先呼叫checkSign()即驗證self.action, self.param是否會等於sign,然後再根據action中如果有scan,則掃描目錄,如果有read,則讀取檔案。也就是說action必須包含scan和read才能讀取param的檔案
問題來了,如何繞過checkSign()

if (getSign(self.action, self.param) == self.sign):
            return True

也就是說action和param getSign()處理後的值要等於sign

def getSign(action, param):
    return hashlib.md5(secert_key + param + action).hexdigest()

得想辦法提前得到我們需要的sign
首先param要為flag.txt和scan要包含readscan是已知的,那麼secert_key呢?我們無法得知
再看看geneSign()方法,直接計算好了getSign(action, param)

@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
    param = urllib.unquote(request.args.get("param", ""))
    action = "scan"
    return getSign(action, param)

所以我們構造payload
/geneSign?param=flag.txtread
預設action=scan
所以將得到secert_key +"flag.txtreadscan"的加密值,也就是我們需要的sign值6d96187919a7b931872af4b1331dfeb9
在這裡插入圖片描述
接下來構造payload

/De1ta?param=flag.txt
Cookie:action=readscan;sign=6d96187919a7b931872af4b1331dfeb9

在這裡插入圖片描述