Codegate 2019 Quals — Rich Project

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:

solve.py

“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!}

Leave a Reply

Your email address will not be published. Required fields are marked *