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).

I’d like to extend my thanks to the creator for making these – you can reach out to him via Twitter or LinkedIn.

Level 1: Super Easy

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:

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:

The username and version were returned!

Level 2: Easy

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…

…so our final statement looks like this:

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:

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!

Level 3: Normal

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

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:

The username and version is now revealed.

Level 4: Normal

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


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

Level 5: Get your “bot-writing” skills

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:

Let’s now try the discovered password:

Level 6: Blind Challenge (Experienced)

Let’s start with a generic Union statement again:

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:

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:

Level 7: Medium

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:

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:

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

Level 8: Hard

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.

Level 9: Medium

A Union injection shows the following output:

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

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:

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

We now have the contents of /etc/passwd.

Level 10: Pro

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…

…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 again 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.

George O

Written by

George O


CTF Writeups

A collection of write-ups for various systems.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade