I had a lot of fun playing the web challenges from the FBCTF 2019 — even for a little while. In this post I will talk about my thought process and the things that I tried.
This one is about the first challenge called Product Manger, I thought it was a nice and well made challenge that highlighted and used a lesser-known MySQL feature for a nice advantage.
Believe it or not, the first authentication bypass bug I ever found was caused by something similar. I don’t know how many people in InfoSec now know the wonderful (all dead for a while now) Infinity Exists, SecurityOverride.org and EvilZone — I remember seeing a tiny post by a user called CrashOverride about MySQL features that documented a nasty MySQL trick around 2010. That was a long time ago, it reminds me of all the significant turning points in my life where I realized I wanted to do security thanks to all the incredible and helpful friends I made over IRC. InfoSec today is different, bug bounties and zeroday industry are killing how many people are willing to share. But lets not get into that.
Details
With the challenge we are given a tar file that contains the source code of the challenge. In there we have 6 php files. It seems the application lets you create and hide a secret description with a password and name.
In db.php we see a comment the flag (secret) is stored in a table name called facebook. We can view other tables if we know their password and name. We know the product’s name is facebook, we are looking for a way to find the secret.
This is view.php:
and check_name_secret() looks like this:
At first I thought it might be a misconfigured PDO for SQLi. But PDO is well used and everything is sanitized properly.
What we want to do is call get_product(“facebook”) on line 8 but that isn’t possible because of check_name_secret() requiring secret. All we need is for check_name_secret() to return True and we can get to that call. That basically means tricking the SQL statement to return any kind of response (anything thats not null) — I initially spent few minutes trying to trick the SQL to generate some kind of error so it bypasses
if($rest->fetch_assoc() !== null){
$valid = true;
}
I was thinking if we can get it to throw any kind of error, we can turn $valid into true, and control a to call to get_products() but that turned out to be a goose chase because of the error reporting being off.
My second idea was if we did:
SELECT name FROM products WHERE name = facebook AND secret = a%
SELECT name FROM products WHERE name = facebook AND secret = b%
Those statements would also be true because % is a wildcard in MySQL that means LIKE everything that starts with a- or b- in the table and return some kind of data, also turning $valid to true. This approach also won’t work thanks to PDO turning the user controlled values to strings.
My next approach was clear after noticing insert_product() — seeing that data is written and retrieved by name.
So if we created a product called “facebook” with our own secret, when we go to view.php and try to open it with our secret, MySQL would actually retrieve the first “facebook” row’s value. (not ours)
Then I tried doing that, it was no luck because of a certain code in add.php:
Here is where the MySQL trick that comes to play, when querying data with MySQL, trailing whitespaces are ignored by default.
This means simply by creating a product called “facebook “ (mind the space) with our secret, we can trick MySQL to create a product called “facebook” in the database.
After this tricking check_name_secret() to return true was straight forward, we use product name “facebook” and our secret and MySQL will query and open the first product instance called “facebook” (not ours!)
This gets us the fb{} flag!
TAKE
The problem here was how they used a user-controlled value as key to query data in and out of a database, instead of a server controlled “ID” or something that links and distinguishes one product from another.
My authentication bypass bug came with this flaw, by creating a user called “admin ” (note the space) with my password, I was able to login with the “admin” username thanks to insecure querying.
Most of the other web challenges were fun as well, I only got to solve the secret note one in time using browser-side channel attacks but maybe a story for another time.
Thanks for the nice CTF!