id: 23 lang: en date: 2014-06-30 title: NdH2k14 choucroute CTF write up category: CTF, Security licence0: This article is released under the *CC BY-SA* licence. licence1: Illustrations of the CBC mode in the *public domain*. *[CTF]: Capture the flag *[ECB]: Electronic codebook *[CBC]: Cipher feedback *[AES]: Advanced Encryption Standard *[SQL]: Structured Query Language *[JSON]: JavaScript Object Notation *[IV]: Initialisation Vector Overview ======== Last weekend, I was at the [Nuit du Hack](http://www.nuitduhack.com/), which is a security event near Disneyland Paris mixing conferences, workshops, and much more, including some wargames and CTF. This article is about the “choucroute” challenge on the public CTF. Even though no team managed to solve it in due time, I still worked after the deadline was over and solved it the next day (actually, a few hours of sleep later). The entry point of the challenge was a webpage where you could basically buy some sauerkraut online, using a coupon if you have some. The flag was supposed to appear after the payment processing, but unfortunately, this very service seemed to be on strike, leaving us with nothing to eat and no flag to score. As for many web challenges, you are looking for the regular vulnerabilities: SQL injection, upload forms and so on. Soon, you will figure out that an old version of the `index.php` file is available at the address `/index.php~`. The `index.php~` together with the exploit described in this article are [available for download][DL code and exploit]. [DL code and exploit]: /media/ndh2k14_choucroute_exploit.zip Code analysis ============= The PHP file has all the logic inside, except for the value of a secret key and what happens during the payment step, or if that step is not needed. Those points were replaced by `TODO` comments, leaving us to believe that we only need to reach that step with a non-positive `$to_pay` variable. Some coupons are hard-coded in the source code, but proper sanitisation ensures that the amount to pay is always strictly positive. More work is obviously needed to solve the challenge, but before anything else, let us patch the PHP file so that we know when we would have got the flag, by adding some `echo` statements: :::php 0) { echo "\n\n
HANDLE PAYMENT
\n\n"; } else { echo "\n\nSEND CHOUCROUTE
\n\n"; }?> To reach this point, a POST request has to been made, with a special cookie: :::php window.location.href="index.php?go";'); $to_pay = $values['to_pay']; $quantity = $values['quantity']; if (hash('md5', $quantity . '|' . $to_pay) !== $values['security_check']) die(''); // proceed ?> In fact, everything comes to the `decode_cookie` function, which is reproduced below: :::php 'e-mail', 'coupon' => 'coupon', 'invalid_coupon' => false, 'quantity' => 1 ); } $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $supa_secret_key, $encrypted, MCRYPT_MODE_CBC, $iv); if (strpos($decrypted, "\x00") !== FALSE) $decrypted = substr($decrypted, 0, strpos($decrypted, "\x00")); $values = json_decode($decrypted, true); if (json_last_error() !== JSON_ERROR_NONE) { return array( 'email' => 'e-mail', 'coupon' => 'coupon', 'invalid_coupon' => false, 'quantity' => 1 ); } return $values; } ?> As you can see, the main two actions implemented by this fonctions are: 1. Decrypt the cookie with RIJNDAEL (AES) in CBC mode; 2. JSON-decode that value and returns it. Cipher feedback mode ==================== I have written about an other block cipher mode, ECB in several posts in the past: - [ECB: you're doing it wrong](https://r.rogdham.net/16); - [iCTF secretvault write up](https://r.rogdham.net/22). In a nutshell, ECB is a very weak block cipher mode. However, the present challenge uses CBC, which is quite stronger. Indeed, there is a feedback added: the ciphertext of the previous block is used for the computation of the next block. As usual, a picture makes things easier to understand (if you prefer, feel free to read the [Wikipedia article][WP-CBC]).  As you can see, an initialisation vector (IV) is used to replace the “previous ciphertext” on the very first block. As far as decryption is concerned, just reverse all the steps:  For our attack, we control all the cipher blocks as well as the initialisation vector. We can think of two attacks right away: - Altering the IV allows us to fully control the first 16 bytes of the JSON data; - We can fully control any plaintext block in the chain by changing the previous ciphertext block, but this will introduce a plaintext block full of garbage as a result. However, both ideas are not applicable here: if we take a valid cookie and try to modify it, we need to alter the values of no less than two keys: `to_pay` and `security_check`. The problem is that the last one would be at the very end of the cookie, and is 32 bytes long (the MD5 value is hex-encoded). In other words, we need to be able to choose more than 32 **consecutive** bytes (we have to get the JSON key right as well as the JSON syntax correct). [WP-CBC]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_.28CBC.29 Decryption oracle ================= > We are all here to do what we are all here to do. > > — The Oracle, Matrix Reloaded, 2003 Go and see the oracle --------------------- A closer look at the `decode_cookie` function reveals some interesting points: - Before the JSON-decoding, the value is cut just before the first null byte; - If the JSON-decoding fails, we have some default values being returned. Now, look at the first step of the website: :::php