PicoCTF 2014 Write-ups

Injection 2 - 110 (Web Exploitation)

Writeup by patil215

Created: 2014-11-07 19:35:58

Last modified: 2014-11-09 23:28:11


Daedalus Corp.'s login woes continue. Can you sign in using their latest login system?


If only we could make the query return a fake row that we control...



Format a SQL injection that causes the table to return a fake row containing the fake information necessary to get past the check.


I'm going to assume that you're familiar with the basic knowledge used in Injection 1. If not, you should go back and read it.

The source for the server tells us that in order to get our flag, there needs to be exactly one user matching the query:

if (mysqli_num_rows($result) === 1) {

which also contains the password equal to the one we provided:

if ($row["password"] === $password) {

as well as a user level larger than or equal to 1337:

if ($row["user_level"] >= 1337) {

Since Daedalus still isn't escaping their input, we can again inject arbitrary SQL queries. However, since the password and user level are checked separately from the query, we can't simply skip the password check as we did in Injection 1. So the easiest way to do this, as the hint says, is to provide a fake row that we can control the value of.

First, enable debug mode again so we can see our queries (go look at Injection 1 if you're not sure how to do this).

A quick Google search for "return fake row SQL" gives us this convenient StackOverflow post. It looks like we need to append UNION ALL then another SELECT statement to our query, returning fake data using the AS keyword in SQL.

Let's work on constructing our query. The first thing we want to do is invalidate the first half of our query - since we are providing fake data, we don't want to check the username. The easiest way to do this is to add 'AND 1=0 to our query. The ' closes the username string and 1=0 will always return false, invalidating the first half.

random'AND 1=0

The second thing we want to do is provide our fake data. The union all statement allows us to concatenate two SQL select queries, so we append UNION ALL and then our fake select statement. We put in things we think will get past our checks, so UNION ALL SELECT 'admin' AS username, 'hax' AS password, 2000 AS user_level -- should do it.

So now we put in the entire statement into our username box:

random'AND 1=0 UNION ALL SELECT 'admin' AS username, 'hax' AS password, 2000 AS user_level --

and put "hax" in the password box (it needs to match our fake data because it is checked by the server).

We submit it and...

Get an error: "The used SELECT statements have a different number of columns".

What does this mean? Well, a user doesn't just have the username, password, and user level - there are other properties, or columns. We need the number of columns in our fake data to correspond to those in the actual user. So, we keep adding columns until we don't get this error anymore. This turns out to be:

random'AND 1=0 UNION ALL SELECT 'admin' AS username, 'hax' AS password, 2000 AS user_level, 10 AS dummy, 10 AS dummy2 --

The 'dummy' properties are just names for the properties we don't care about - it doesn't matter what these are called or their value.

So now we aren't getting an error, but we still aren't getting our flag. Well, this is because username, password, and user_level aren't in the right order - they need to be in the correct order to correspond to the correct columns. So we rearrange our query until it works:

random'AND 1=0 UNION ALL SELECT 'admin' AS username,  1000 AS dummy,  'hax' AS password, 10 AS dummy2, 2000 AS user_level -- x

and we get our flag.