Jean-Sébastien Bash

We get a shell which only accepts encrypted commands, and the /help function tells us the format is:

/cmd AES(key, CBC, iv).encrypt(my_command)

We also get a sample command, and if we try running it we will get some fairly helpful output:

Running b'ls -l'
total 8
-rw-r--r-- 1 root root   21 Apr 25 21:18 flag.txt
-rwxr-xr-x 1 root root 2066 Apr 25 21:50 server.py

If we try running /cmd <random string>, we will just get a “What do you mean?!” error. We can assume that we get the error due to incorrect padding, and can try an attack that looks a bit like a padding oracle (but doesn’t quite go through the entire process). We will take an arbitrary 32-byte string, and try all possible values for the sixteenth byte until we hit a string with valid padding (since the last byte of the decrypted data is xored with the last byte of the first block).

Once we find it, the server will tell us what it decrypts to and try to execute (and get a bunch of errors, since it is gibberish). We can now change the start of the first block to get a command in the second block. Since we cannot control what comes before the command, we will simply add a semicolon before and after:

# the string with correct padding we found
get = b'\x15\x02\x9c;\x95\xe6\xcc\xe3\x10B\x97\x90\x9d7EYU\xad0A\xf8\xd6\x1f\xea\xaf\xd6\xf7\xa0>\x84D'

want = '; cat flag.txt;' 

for i, w in enumerate(want):
    base[i] = base[i] ^ ord(w) ^ get[i+16]

print('/cmd', binascii.hexlify(bytes(base)).decode('ascii'))

Submitting this to the server runs <random data>; cat flag.txt; <random>, which throws several errors, but also prints the flag.