ruCTFe 2012 Write-Up: buster

November 25, 2012

Update: Thanks to ius from team Eindbazen I cleared up a small error in the blog post.

Buster was a compiled java service, that used a sqlite backend to store the flags. The gameserver communicated with the server by sending serialized objects. To secure the communication, the server sends the authentication token in an encrypted form using Triple-DES in CBC mode, the message being padding with PKCS5.

So, lets look at the keywords from the above text. The important ones are basically SQL and CBC mode. SQL might always allow for SQL-injection and in CBC cipher mode, the attacker can control parts of the decrypted message.

Basics in crypto

So, lets look at how CBC works first. The following picture shows the encryption when using CBC (in this case, using AES as the cipher).

Basically, Cipher-Block-Chaining means that previous to putting the cleartext data block into the cipher itself (AES, DES, Triple-DES, …) it is XORed with the previous cipher block. This works fine for all but the first cleartext block, as – of course – there is no previous cipher block. So, the encrypting entity chooses a random value of block size (8bytes for DES, 16bytes for AES) to use in the first XOR. This value is the so-called Initialization Vector or IV. The following picture depicts the decryption using CBC.

Basically, the decryption works very similarily to encryption. This time, the ciphertext block is put through the decryption routine and is then XORed with the previous ciphertext block. Also, for the first block, we use the IV again. The important thing to understand at this point is the following. If, for some reason, we can deduce what comes out of the AES block in the first cipher (what is denoted here as the Intermediary Message (IM)), we can produce any “plain text” we want. Why can we do that? Well, CBC uses the IV to XOR the IM and we usually control this. So, for each byte of message we want to “generate”, we choose the IV as follows:

IV[n] = IM[n] ^ DesiredMessage[n]

If you wonder how you might deduce the IM, look up “padding oracles” on Google.

As both DES and AES are block ciphers, the length of the given input  must always be a multiple of the block size. As messages might not fit this condition, the plaintext is padded to a multiple of block size. However, the decrypting entity must somehow know, how much padding was append to the original cleartext. There a multiple ways of doing this, we will focus on PKCS5 as it was needed in this challenge.

PKCS5 encodes a padding of n bytes by filling the all of the padded “slots” with n. Basically, if we have only one byte padding, the last byte will be 1. If we have e.g. 5 bytes padding, the last 5 bytes will all be set to 5. Please note, that padding must always be provided. Thus, if the message actually had a length which was a multiple of the block size, there will be exactly one block added to the message. For 8byte ciphers like DES, we then have a block of length 8b filled completely with 8s.

The Challenge

Ok, now we covered the basics on crypto needed for this service. Now, lets look at the actual service. The service generates upon login a token, which is encrypted using Triple-DES in CBC mode. The content of the token is the MD5 sum of the username that logged in. The MD5 sum is stored in hex-representation before sending it to the client. Thus, it takes up 32b of space.So if we consider this information, the packet must be looking something like this (each color box denotes a 8byte block):

The gameserver stores a flag after it logged in the first time. When coming back, it logs in, retrieves the the token and then calls the get-method. The server in turn takes this token, decrypts it and then uses it… in an SQL statement! The first idea was to mess with the IV. The problem is, the IV is generated by the server and is not even sent to the client. Thus, we cannot use this.

Let’s see what kind of information we have. The most important piece of information we have is that at offset 32 to  39 (denoted by the red block in the picture shown before), we have only 0x8. Looking back at CBC, we know that these values in decryption were arrived at by XORing the Intermediary Message (IM) of the red block with the blue block (MD5[12:15]). So, what can we do here?

Lets first calculate the Intermediary Message that comes out for the red block. We then get a block of 8bytes which XORed with the content of the blue block results in 0x8 0x8 0x8 0x8 0x8 0x8 0x8 0x8. What shall we do with this now? Oh, well, how about a SQL injection? 🙂

As stated earlier, if we know the IM, we can craft the IV (or in this case the previous cipher block) in such a way that we create our own message! We therefore change the second half of our green block in such a manner, that upon decryption, the red block is changed from

0x8 0x8 0x8 0x8 0x8 0x8 0x8 0x8


a' O R '  ' = ' 0x1

Now, two things happen. The last part of the original MD5 sum is scrambled because we provided a bogus cipher text. However, the complete message now looks as follows:

<3blocks_of_real_md5><random garbage>a'OR''='

Lets now look at the query used in our service.

ResultSet rs = stat.executeQuery("select * from camouflage where name='" + name + "' order by id desc limit 111;");

So, in total, the query now looks like this (the red part denotes our input):

select * from camouflage where name=’<3blocks_of_real_md5><random garbage>a’OR”=’

Yeah, this gives us a lot of flags!!

In the CTF, we fixed this vulnerability by just filtering out queries that contained an “OR”. I suppose, prepared statements could have worked as well as just not using CBC but ECB. This is valid because bruteforcing a DES key is not feasible (esp. if it is randomly generated and is not using a known charset) and each team has its own key due to the key being generated on startup.

At this point, I would like to thank Hackerdom for this amazing service and a fun CTF. Next year, just get a little bit more bandwidth 🙂

Leave a Reply