Codegate 2019 Quals — Rich Project
I participated this weekend at Codegate 2019 Quals. We took the 4th place and we qualified for the finals.
This writeup is about one of the tasks, a web application task. The task was nice and involved multiple steps in order to be solved.
I was given the URL of the task http://110.10.147.112/ and nothing else. The main page of the site comes with some messages and one of the messages caught my attention: “be rich”.
So, this might be a site where you can buy coins (SCAMcoin).
After I created an account and went to the market page.
I saw that I have a trade page where I can exchange gold for SCAMcoins. After the registration I started with 10000 gold and I could only buy or sell SCAMcoins. One exchange would take me a tax that implies 2000 gold for buying SCAMcoins and 50 SCAMcoins if I want to sell.
Going to other tabs I noticed that there is a secret note in the “Board” section.
I couldn’t read it because it had that (s) which means it is secret and only the owner could read it. The owner was user “admin”.
After I tried to scan for important files like
.git/ .svn/
.hg/ .index.php.swp .config.php.swp .cache/ /.well-known/ ….. robots.txt ….. |
I found robots.txt
I took the zip and I couldn’t get the content because was password protected. The archive was created with a weak crypto and I could have obtained the content without knowing the password because of a known attack on zip files that aren’t encrypted with AES (more details can be found here: https://github.com/hyperreality/ctf-writeups/blob/master/2019-codegate/README.md).
But during the contest I didn’t extract the password using the weak crypto that was used to create the zip. I did it the intended way.
The intended way was to read the content of the “TOP SECRET” note from the board section. To do so I started to find a way to be “admin” or something else that enabled me to read that note. After unsuccessful attempts to reach an XSS on that part of the website I found a strange behavior when I create a username with quote. The first thought was that there is an SQL Injection and I started to confirm it. After I created a user like ‘ or 1=’ I was able to see all board messages from all users:
That confirmed me the SQL Injection. I was thinking that I could use this to retrieve the content of the “TOP SECRET” and I started to guess the column name of the content which was “contents”.
I created a script to retrieve that content of the “TOP SECRET” note:
“they are manipulating the price of coins!! \nhow can this be? when i knew that, i decided to expose that.\nfortunately, i have a master password (not flag). it is..\n\n\n’d0_n0t_re1e@5e_0ther5′”After I ran the python script I got:
The password of the zip is: “D0_N0T_RE1E@5E_0THER5”. I extracted it with lowercases and I tried to make uppercase on all letters and it worked. Otherwise I would have made a little brute-force on lowercase and uppercase in order to obtain it.
Having the source code of the application, it was very easy to solve the task. I found that I had to go on “pay” page in order to obtain the flag:
File: pay.php
File: flag.php:
There was a file which caught my attention “reserve.php”:
Something that is also important is the following php code from “market.php” file:
The exploit is to reserve an amount of coins at a date that is at least one second after the last inserted date from the “coin_price” table. If I would go now on the market and want to get SCAMcoin without spending any “gold” from my wallet I would have to reserve the coins at a date with at least one second after last date from ‘coin_price ‘.
#file: market.php
### if reserv is exists $q = “SELECT * FROM coin_price where Date like ‘%{$row[‘reserv’]}%’ order by Date limit 1”; $res = mysqli_query($conn,$q); $row2 = mysqli_fetch_assoc($res); |
Because when the date will be selected from the “coin_price” table using the date I was given it won’t exist ( {$row[‘reserv’]} ) therefore $row2[‘price’] var will be NULL.
As you can see in the php code above it checks if we have gold to buy SCAMcoins:
if($row[‘cash’] < $row2[‘price’]*$row[‘amount’])
echo “Not enough cash to buy SCAM coin. your reservation is failed..</br>”; |
$row[cash] is the amount of gold we have, $row2[price] is the price from ‘coin_price’ table (in our case it will be NULL because the date was after now(), I choose 2020/01/01) and $row[amount] is the SCAMcoin that was reserved. Having NULL on $row2[price] will pass the check even if our amount of SCAMcoin would be much bigger than gold, something like 1111111111.
The following line won’t affect the $cash (gold amount from my account). Because it will be row[cash] – 0. It is 0 because of the row2[price] which is NULL
$cash = $row[‘cash’] – ($row2[‘price’] * $row[‘amount’]); |
To make it easy I reserved 1111111111 SCAMcoins for 2020/01/01 and then visited the market.php file to get the SCAMcoins without spending any gold for those SCAMcoins.
My balance on my account was:
Reserving 1111111111 SCAMcoins for 2020/01/01 gave me 1111111111 SCAMcoins without spending any gold.
Visiting the “market” page I could see 1111111111 SCAMcoins and the amount of gold didn’t change.
Then I trade it SCAM coins for gold and read the flag:
Flag was: FLAG{H0LD_Y0UR_C0IN_T0_9999-O9-O9!}