Union SQLi Challenges (Zixem Write-up)

George O
George O
Oct 21, 2018 · 9 min read

I’ve always avoided learning more about SQL Injections, since they’ve always seemed like quite a daunting part of Infosec. Because of this, I finally decided to put in some time to an SQLi-focused wargame in order to sharpen my skills a little. You can find the challenges at the website below:

There are only a few rules:

  • Find the username (user()) and version (version()) of the site.
  • Use Union statements for all.
  • Do not use information_schema to get any information.
  • Obviously, no tools (i.e. Sqlmap, Sqlninja etc).

This challenge was a straight-forward Union-based injection.

To help us do this, let’s take a guess at what the statement looks like to find the price:


With the UNION statement, we can combine the output with other data that we want.

We first need to identify the number of columns in the table.


Let’s now try entering this:

The error message tells us that we need to enter more columns, so let’s do that:

Since the error is gone, we know that there are three columns!

However, the data isn’t showing on the page. This is because the data from the original statement is coming through before our data. To solve this, we just need to invalidate the first id:

SELECT * FROM exploits WHERE ID=1 AND 1=2 UNION SELECT 1,2,3--

Now, only the data from our own selection will be returned:

The ID and Price is returned as 2 and 1 respectively, which means that our own inputs are being returned.

Let’s now change 2 and 1 to version() and user(), so our final statement looks like:

SELECT * FROM exploits WHERE ID=1 AND 1=2 UNION SELECT version(),user(),3--

The username and version were returned!


Trying the same payload as last time doesn’t return anything:

I tried changing the number of columns, but that still revealed nothing.

Let’s take another look at our concept statement:


It’s possible that this web-page is taking the ID in as a string, instead of an integer.

To fix our statement, we just need to close the quotes…

SELECT * FROM exploits WHERE ID=1'

…so our final statement looks like this:

SELECT * FROM exploits WHERE ID=1 AND 1=2' UNION SELECT version(),user(),3--

The error tells us that there is an error relating to the apostrophe. We can solve this by simply adding an apostrophe to the end of our statement:

SELECT * FROM exploits WHERE ID=1 AND 1=2' UNION SELECT version(),user(),3--'

We are now told that the number of columns is wrong. By adding a fourth column to the selection, we are returned the username and version!


Let’s start by trying the same union injection as before:

SELECT * FROM exploits WHERE ID=3' UNION SELECT user(),version(),3,4--'

The error message tells us that the statement is being interpreted as “uni select user(),version(),3,4 — ’”, which means that the on of union is being filtered out. To combat this, we can add another on, so that even after it is removed, the statement still reads union:

SELECT * FROM exploits WHERE ID=3 AND 1=2' UNIONON SELECT user(),version(),3,4--'

The username and version is now revealed.


I’m not too sure why this was a separate challenge, as the solution is almost identical to that of Level 2.


7 AND 1=2' UNION SELECT user(),version(),3 --'

…told us that the number of columns entered was wrong, so trying a couple more eventually gave me the working solution:

7 AND 1=2' UNION SELECT user(),version(),3,4,5 --'

Viewing the source code of the page shows this:

Heading there shows:


So let’s now try the password zixemhf.

We now know exactly what we need to bruteforce.

I wrote this short python script to do just that:

The script here starts at 1300 so you don’t have to wait too long.

Let’s now try the discovered password:


Let’s start with a generic Union statement again:

10 AND 1=2 UNION SELECT 1,2,3,4--

We know now for sure that there are 4 columns in a table. I began by working out the table name, which after a couple of guesses resulted in being teachers:

10 AND 1=2 UNION SELECT 1,2,3,4 FROM teachers--

The rest of the challenge is simply a matter of guesswork.

The four columns ended up being id, teacher, teacher_age (that one took a while to guess) and price:

All we need to do now is ensure that the output is for the user with an ID of 11, which we can accomplish with the Where statement:

10 AND 1=2 UNION SELECT id,teacher,teacher_age,price FROM teachers WHERE id=11--
The challenge has been completed!

This level was really painful to do, since the text changing was only visible when viewing the source code of the page, which I didn’t think to do for ages.


Once again, let’s start with the most simple form:

1 AND 1=2 UNION SELECT 1,2,3--

While nothing is immediately visible on the page, the source code shows the following:

We see that there is a hidden input, with the value ok2. Let’s now change the 2 in our select statement to user() to view the user:

1 AND 1=2 UNION SELECT 1,user(),3--

The username is revealed in the value box! We can change user() to version() to find that too:

1 AND 1=2 UNION SELECT 1,version(),3--

In this challenge, I quickly discovered that entering a space into the URL causes the following error:

My first thought was to encode the space to different things, such as:

  • A plus sign (+)
  • A simple URL encoded space (%20)
  • A null byte (%00)
  • A newline (%0a)
  • A tab (%09)
  • A carriage return (%0d)

Fortunately, using either a tab or a carriage return seemed to work:


However, trying to use a Union select shows the following:

So it looks like the SELECT statement is being ignored.

In an earlier challenge, the ON of UNION was being ignored, so we added it twice. Let’s apply this to the SELECT statement too, by using SELSELECTECT, so that even when SELECT is removed, the words around it still spell out SELECT:


Our input is being returned!

Now let’s change this to show the user and version:


It’s worth mentioning that while attempting this challenge, I found another way to make this work without using tabs or carriage returns:


EDIT: After having spoken to Zixem, it turns out that this second method is the intended one.


A Union injection shows the following output:

1 AND 1=2' UNION SELECT 1,2,3--'

It looks like (i) our parameters aren’t getting parsed, and (ii) it’s looking for a string. So, let’s update our query:

1 AND 1=2' UNION SELECT "a","b","c"--'

Running this gives an error which says that we have the wrong number of columns. After a couple of guesses, it turns out that this uses only 2:

1 AND 1=2' UNION SELECT "a","b"--'

It looks like it’s trying to open “a”, from the “./” directory. Let’s try some directory traversal to /etc/passwd:

1 AND 1=2' UNION SELECT "../etc/passwd","b"--'

We now have the contents of /etc/passwd.


The first thing that is immediately noticeable here is the parameter being passed in as x in the URL by default. We can URL decode & then Base64 decode this in CyberChef:

I quickly recognized this output as Uuencode from a CTF I was involved in previously. We can use a Uuencode decoder to decode this:

As this is just 1, let’s try another standard Union injection by using this encoder:

I then used CyberChef to Base64/URL encode this again, and submitted it:

I then tried a few different column sizes, before discovering that there were only 2:

All that needs to be done now is to change the 2 to user()/version().

So, my final statement looks like this…

1 AND 1=2 UNION SELECT 1,CONCAT(user(),"   ",version())--

…which turns to this, when Uuencoded…


…which in turn becomes…


…when Base64 encoded.

And with that, the final challenge has been completed!

I hope that reading these solutions helped you form a better understanding of Union injection, and how they aren’t quite as scary as they initially seem. At first I was hesitant to post the solutions online, but after Googling the challenge, it seems like many people have done it already. Thanks to Zixem for making these, seeing as there are very few SQLi wargames out there.

CTF Writeups

A collection of write-ups for various systems.