De1CTF 2019 SSRF Me 1题解


De1CTF 2019 SSRF Me 1题解

题目详解

打开题目,有hint:flag在flag.txt中

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

分析函数,geneSign是对传入的param与其他字符串拼接并返回其md5值

@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是主要,传入3个参数,以及ip,先判断param是否是gopher或者file开头的参数,不是则过到Task中,并且返回task的Exec()函数结果,

@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()) 
def waf(param): 
    check=param.strip().lower() 
    if check.startswith("gopher") or check.startswith("file"):
        return True 
    else: 
        return False 

最后看task类

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 

__init__方法进行了最初始的赋值,同时对ip进行md5加密后的路径进行检测,如果存在则进入。

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) 

checkSign判断sign的正确性。

def checkSign(self): 
    if (getSign(self.action, self.param) == self.sign): 
        return True 
    else: 
        return False 
def getSign(action, param): 
    return hashlib.md5(secert_key + param + action).hexdigest() 

exec则是先检测sign,然后判断action,如果是scan会进行查询并写入,如果是read会读取。

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 scan(param): 
    socket.setdefaulttimeout(1) 
    try: 
        return urllib.urlopen(param).read()[:50] 
    except: 
        return "Connection Timeout" 

捋一捋思路,我们知道flag在flag.txt中,所以我们先要利用scan将结果写入flag.txt/result.txt中,再读取出来,也就是传入的action为scanread或readscan,param为flag.txt。接下来就只需要想办法绕过checkSign(),由于有secret_key的存在,我们无法自己加密,但是注意到geneSign函数,它会对secret_key+param+action进行加密,同时action已经被限制为scan,我们可以输入param为flag.txtread从而获得secret_key+flag.txtreadscan的md5值进行绕过。
首先访问/geneSign?param=flag.txtread得到c7df6fac4eeeb0e291da6f108f620186,最后访问/De1ta?param=flag.txt并且在cookie中传值action=readscan;sign=c7df6fac4eeeb0e291da6f108f620186。回显{“code”: 200, “data”: “flag{24f82fee-2007-4538-920e-edaaff47ae6f}\n”}
得到flag:flag{24f82fee-2007-4538-920e-edaaff47ae6f}。
参考:
https://blog.csdn.net/qq_42967398/article/details/103549258
https://blog.csdn.net/qq_40519543/article/details/107224099


Author: kingkb
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source kingkb !