Here’s another writeup for a practice challenge on the backdoorCTF site. (WARNING….i went on a bit with this post, so it’s massively over-detailed, but..hopefully it explains everything properly).
They just had (April 2015) a full CTF which i took part in, but didn’t have much time to spend on challenges, so didn’t solve anything….but (as they do with all their CTF’s) they then make the challenges available in their “Practice Arena” for you to work on anytime. So i’ll be hitting some of those soon 🙂
OK, on with the writeup. This is a 120 point challenge called “sequel”.
When we click on the link to the web application we get the following:
Looking at the source code we see the following:
OK, so looks like a basic web authentication page, using a SQL database…but this is an SQLite database, as opposed to MySQL, PostGres etc, but it seems theres not much difference between statements compared to MySQL.
So i could fully test and see whats going on with this web application…and SQLite, i built a “test” system on a VM, so i could work through the code, and see what happens etc.
I built a simple Ubuntu 14.02.2 server, installed Apache, PHP and SQLite, built a simple database containing what i “thought” maybe in the actual one, along with some info etc.
I could see from the source code, that it queried a table in the database called users, also, there was at least 3 columns i was aware of (username, password_hash and name)
We could also assume that there would be an id column for each entry, so 4 columns all up.
It looked like the password that was entered, was converted to a sha256 hash, then checked (so the passwords were in this format in the database).
And finally there is reference to a user named “sdslabs“, so i made sure to create a user with that username……..remember this for later.
So my database ended up looking like this:
(The hashed passwords i created were p@ssw0rd, backdoor and football respectively.)
Next i created the index.html page, same as on the web application, and copied the source code into submit.php, and made a few “tweaks”.
Into submit.php, i changed the name of the database to match the name of the one i created.
And also added the following at the bottom:
This allows me to see the value of various variables after i’ve entered them into the web login form, and submit.php has done it’s stuff.
For example. If i entered the username john and password monday into the form, the returned page looks like:
Obviously john doesn’t exist in my database, so it fails, but i can see the entered username and password, the hashed password, and the SQL query that’s being run.
If i logged in with the sdslabs user i created i get:
So obviously, this time the user exists, and it’s the one that the script is looking for, so it would print the flag. It also shows the same info as before, but also the value of the $name variable, as well as the results of the query against each row.
Cool! So i have a working web application, with similar code (albeit with extra debug info) and a database which i THINK (note the underline) matches what the actual database is.
For those familiar with SQL, PHP and specifically SQL Injection, this might seem like a lot of overkill, and effort…but as I’m still learning, and want to fully understand what’s happening, and the processes involved…then this helps me 🙂
OK, lets start working through the script. The first line connects to the SQLite database.
Next, it sets the variable $username to the entered username, and also the variable $password to the entered password from the login form., but for the password it also hashes it using sha256. So entered the password “p@ssw0rd” would become “a075d17f3d453073853f813838c15b8023b8c487038436354fe599c3942e1f95”
Next the variable $query is set to the SQL query to be run.
This is a fairly generic SQL statement…which doesn’t look or work much differently than one for MySQL.
Next it sets the variable $result to the result of the SQL query that was run. If the query returns nothing (e.g the user doesn’t exist, or the password is incorrect, the query would fail, and no result would be returned) But if it was successful it would return the results (as an array) into the variable $result.
It then performs an IF statement, for each returned row in the result.
If there isn’t a row in the result (e.g….no user found, or incorrect password) it jumps down to the else statement, where it prints out “User not found or incorrect password” then the script ends
But if there is a result it continues on…
And first sets the variable $name to the value in the row, in position “1“. and also the variable $username to the value in the row, in position “2“.
To understand this a bit more, lets login with the account i created in the database with username “bob” and password “football“. On my test setup, the SQL query would look like this:
If we run this query directly within the database itself (and tag on a “;” on the end) it would look like this:
So the resulting row looks like below, which is an array.
So the value of $row is “Bob Smith” which corresponds to the value of name in the database, and $row is “bob” which corresponds to the value of username in the database.
OK…back to the script….there is another IF statement, which checks to see if the value in $username is equal to “sdslabs” and if so….tells us we are successful and displays the flag.
And if the value in $username isn’t equal, then it just prints the value in $row which is the value name in the database.
OK…so thats the script…..now….what criteria must we meet for this script, which will make the LIVE system display the flag. Well:
- We must return a valid result from the SQL query. If it returns nothing, it will simply say “User not found or incorrect password“.
- We must make sure the value of $username, which is the value in $row from the SQL query result is “sdslabs“.
So….first things first. The password. As this gets hashed up, whatever value we enter into this field is going to be turned into gobbledygook for us, so we can safely ignore this completely.
So lets pretend we don’t know a valid username for our test system, how can we get it to return a result, lets try the good ole “‘ OR 1=1 — ” trick.
OK, this at least returned a result, which as expected, returned the first row from the database.
How about if we just specify the username “sdslabs” then comment out the rest of the statement.
Woah! It worked….wow that was easy. Lets try that on the “LIVE” system…the actual challenge on backdoorctf…..yeah, you forgot we were actually trying to solve that didn’t you? 😉
Huh? Why? How come that works on my demo system, but not on the LIVE system?
I checked various things in my script, and also my database at this stage, and was confused. “How come the LIVE system doesn’t log me in with the user sdslabs when my demo system does???”……”I know the user exists, as it checks to see if the username equals that in the script!!!”…..”Wait!….it checks to see if the username equals “sdslabs” in the script……but that DOESN’T mean it’s actually a valid user in the database!”
Aha! Just because it checks that value….doesn’t mean that user exists in the database….sneaky!
I then tried the “‘ OR 1=1 — “ trick on the LIVE system and this returns.
OK….so obviously on the LIVE system, there is a user with the name “Dhaval Kupil” in the first row in their database. So using that trick does get us a result at least. Now onto how we can make sure the returned value from the query has “sdslabs” in position $row.
Back to our old friend UNION SELECT. If you are unfamilar with this function, it allows you to add one SQL statement onto another. And in this case, we want to add our OWN values to the result of the query.
So….on my test system, i have a database, with a table with 4 columns:
Using UNION SELECT, i can pass my own values into the query….so it returns these values in the results of the query.
Here’s an example. If we jump into the SQLite database and enter the following query.
SELECT * FROM users WHERE username = 'john';
But as this user doesn’t exist in my database, no row would be returned.
But if i use UNION SELECT i can specify what i want to be returned. So if the query is changed to look something like this:
SELECT * FROM users WHERE username = 'john' UNION SELECT "9", "John Smith", "john", "whateverpassword";
Despite the user not existing, it would return a row, with the values i specified.
So….using this, lets try importing our own values using UNION SELECT onto the LIVE system. As the only value the PHP code checks for is the username field, we’ll only pass that in the values, and use null for the rest.
Our new string we’ll enter into the username field is:
' OR 1=1 UNION SELECT null, null, "sdslabs", null --
This works on my test system:
But doesn’t on the LIVE system 🙁 But i don’t get an error, saying the user wasn’t found, which makes me think there’s something wrong in my SQL statement. But again, why? When it works on my test system?
Thinking what could be different again…i KNOW there are at least 4 columns in the database….what if there’s more?
So I modified my query to add a new column AFTER my “sdslabs” (as we know this one must be in the 3rd position (row). So new query is:
' OR 1=1 UNION SELECT null, null, "sdslabs", null, null --
YESSSS!!!!! So obviously there was another column in the database 🙂
I had heaps of fun figuring this…kind fairly simple challenge out, but i guess the more you understand the simple challenges, the more it helps in more detailed ones.
Again, i highly recommend people who are interested in challenges like this to head over to https://backdoor.sdslabs.co/ and have a go! They’ve got heaps available and as i mentioned earlier have all the ones from the recent BackDoorCTF2015.
If you’ve got this far…nice effort, and if i made a mistake, or you liked this post….drop a comment below 🙂