Code audit | [De1CTF 2019] SSRF Me

[De1CTF 2019]SSRF Me


Foreword

I thought it was injected into the flask template, but after reading the writeup other master found a code audit process, it is reassuring audit the code.

Tip flag in the /flag.txt

After finishing the code

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

First we look at this route:

@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())

The first is to create a Taskclass, action、signthe value is determined by cookie obtained, and paramthe value is passed directly through the GET method paramparameters to be worth, ipis your ip地址, then param参数it will go through waf, if passed waf, this class is executed Exec .
Along the way, we traced back to wafthis method:

def waf(param):
    check=param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True
    else:
        return False

The waf waf is relatively simple, only requires param参数not to gopherand fileat the beginning will be able to over-waf, which is filtered by these two agreements, the agreement that we can not read the file.

The final Taskclass of Execmethods is the key to concluding the natural, we follow this:

   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

If self.checkSign()true, then we can be passed param参数into the scanmethod, the first follow-up scanmethods:

def scan(param):
    socket.setdefaulttimeout(1)
    try:
        return urllib.urlopen(param).read()[:50]
    except:
        return "Connection Timeout"

Here is the key, through our structure param参数was found to achieve the results were read arbitrary files, so we need to do now is how to make self.checkSign()true, follow:

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()

As long as we pass in the cookie sign== getSign ( cookie passed in Action , GET passed param ) will be able to returnTrue

Here we do not know secert_keythe value, and thus can not obtain getSignthe value returned, but found here:

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

Here can be getSign ( 'scan', param GET passed) value, which is the only thing we can take advantage of the place, here we GET the param参数value is clear, is flag.txt , we can get the value of a sign of geneSign MD5 (SECRET_KEY + param+ 'Scan'), but in the end we /De1ta?param=value must be flag.txt, and must be met:

if "scan" in self.action
if "read" in self.action:

We can, in /geneSignthe param参数value of flag.txtread, so we get a sign that md5 (secret_key + flag.txtreadscan), and the access /De1ta?paramtransfer value flag.txt, and by the action of incoming cookie value readscan, so

getSign(self.action, self.param) == getSign(flag.txtreadscan) 
== md5(secret_key+flag.txtreadscan)

And this sign we know, it can be successfully readflag.txt

Here Insert Picture Description
Obtained sign=38713ea9d6fcf9affcbfe082fcb2fcea;
Here Insert Picture Descriptiontransfer actionand obtained sign, resulting In Flag!

Published 17 original articles · won praise 3 · Views 965

Guess you like

Origin blog.csdn.net/crisprx/article/details/104240574