Code audit | [De1CTF 2019]SSRF Me

[De1CTF 2019]SSRF Me

Preface

I thought it was a flash template injection, but after reading the other masters' writeup, I found that it was a code audit process, so I'll be relieved to audit the code.

Prompt flag in / flag.txt

Code after sorting

#! /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, let's 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())

First of all, a Task class is created. The values of action and sign are obtained from cookie s. The value of param is the value of passing param parameter through GET method directly. ip is your ip address. Then param parameter will pass waf. If it passes waf, Exec of this class will be executed.
Following this idea, we trace back to the waf method:

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

This waf is still a relatively simple waf. It only requires that param parameters not start with gopher and file, but also filter the two protocols, so that we cannot read files through the protocol.

In the end, the Exec method of Task class is the key to the conclusion. Let's follow up:

   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() is true, we can enter the param parameter passed into the scan method, and follow up the scan method first:

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

Here is the key. Through the param parameter we construct, we can find the effect of reading any file. So what we need to do now is to make self.checkSign() true and follow up:

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 sign = = getsign (action passed in cookie, param passed in get) in cookie, it will return True

Here we don't know the value of secert_key, so we can't get the value returned by getSign, but we find:

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

Here we can GET the value of getSign('scan ', param passed by GET), which is the only place we can use. The value of param parameter we GET is very clear, that is, flag.txt. The value of sign we can GET through geneSign is md5(secret_key+param +' scan '), and finally the value of / De1ta?param = must be flag.txt, which must meet the following requirements:

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

In this way, the value of param parameter in / geneSign is flag.txtread, so the sign we get is MD5 (secret_key + flag. Txtredscan), while the value passed to / De1ta?param is flag.txt, and the value of action passed in through cookie is readscan, so

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

We know the sign, so we can read the flag.txt successfully


sign=38713ea9d6fcf9affcbfe082fcb2fcea;
Pass the action and the sign and get the flag!

Published 17 original articles, won praise 3, visited 889
Private letter follow

Tags: socket JSON Python encoding

Posted on Sun, 09 Feb 2020 06:58:28 -0800 by overlordofevil