OverTheWire Natas Level 14 Walkthrough

This is a walkthrough for level 14 of OverTheWire’s Natas wargame. After doing posts 0-5 and 6-10 in groups, Natas walkthroughs from level 11 onward get their own blog post due to the length of the walkthroughs.

What is Natas?

Natas is an online hacking game meant to help you learn and practice security concepts.

OverTheWire is a website with a number of “war games”, which are online hacking games that allow you to practice security concepts. If you are looking for a beginner introduction to web security (albeit an older tech stack), then Natas is a great place to start.

Natas is hosted on different subdomains following the pattern of http://natas<level#>.natas.labs.overthewire.org. As you progress through the levels, you’ll need to increment the level number in the URL in order to view the correct level.

Each level requires the levels below it to be solved, so you will need the level 14 flag found in level 13 to begin this walkthrough. As before, make sure you keep notes and write down the passwords as you find them!

Level 14 ➔ 15

First, grab the password from level 13 and head to http://natas14.natas.labs.overthewire.org/, then login with username natas14 and the password. The page looks like this:

We’re given the source code. The relevant part looks like:

To summarize:

  • If the request includes “username” as a key in the data,
  • Connect to the MySQL database and
  • Perform a SELECT query from the users database with user input.
  • Additionally, if the request includes “debug”, show what query is being executed.
  • If the query returns 1 or more rows, print the password.
  • Otherwise, print “access denied”.

Looking at the database query

The main point of interest for us is the database query:

SELECT * from users where username=\"".$_REQUEST["username"]."\" and password=\"".$_REQUEST["password"]."\"

The escaped quotes (\") might throw you off, so here’s another way to view this query:

SELECT * from databaseTableOfUsers where username = "$userProvidedUsername" and password = "$userProvidedPassword"

SELECT * means “select all”, so the entire string means, select everything from the users table where [stated username and password] conditions are true.

By userProvided I mean something that the user is giving us and we are putting into our database query with no filtering or escaping.

That means that the user could provide something like " -- (the double dash is a comment in MySQL), and the query would look like:

SELECT * from databaseTableOfUsers where username = "" -- "" and password = "$userProvidedPassword"

But the -- means the rest of the line gets ignored, so that query is interpreted as

SELECT * from databaseTableOfUsers where username = "" --

Of course, selecting everything from the users table where the username is “” (an empty string) won’t get us anywhere.

Using Burp Suite to get debug feedback

Before we get to the “real” query, let’s make use of the “debug” option mentioned earlier. To do so, we’ll use Burp Suite. This blog post covers what Burp Suite is and how to get it installed and configured.

Once you have that done, visit http://natas14.natas.labs.overthewire.org/ and enter " -- into the username field and leave the password blank. You should get an “access denied” page.

In Burp Suite, go to the Proxy Tab and then click the HTTP History Tab. You should see your request there, with the username and password information (URL-encoded).

Right-click the row and select “Send to Repeater”.

The Repeater tab should light up. Click the Repeater tab and you should see the request. Type ?debug at the end of the index.php text:

Then click “Go”. You should see the query debug string in the response:

If you previously tried ' -- (single quote instead of double quote), then seeing this debug view will show you why your query wasn’t working.

Getting all the rows back

If we look again at the query we made earlier:

SELECT * from users where username="" -- " and password=""

It’s clear that we will not get any users back from this, unless the table has a user with a name of “” (seems unlikely…)

Because the query is essentially “select everything from this table where these conditions are met”, we seem very close to having all the users back. If you remember the source code from earlier, it will give us the password if we have 1 or more rows returned. Returning all the rows = more than one row.

But how do we get all the rows returned? If we’re able to add in our own content into the SQL query itself, why not just add another condition?

Something like:

SELECT * from users where username="$username" and password="$password" OR $otherConditionThatIsAlwaysTrue

If you’re familiar with Boolean algebra, then you know that logical OR joins conditions and returns true if one or more of the conditions are true.

If we use SQL injection (the official name for changing the query in unintended ways with our user input, as with " -- above), then we can append a condition that is always true. Because of the OR, the group of conditions after the where statement will be true, and as a result, all of the rows in the users table will be selected.

There’s more than one option for an “always true” condition, although some of it is dependent on the query language you are using. The one that is most frequently used is:

1=1

Of course, “does 1 equal 1?” is always true, so this satisfies the “always true” condition we’re looking for.

Natas Level 14 Solution

Earlier, we had " -- as a way of inserting our commands into the query (using ") and prematurely ending the query ( -- ). And we checked the debug output to make sure our query structure looked good and didn’t have syntactical errors.

All we need to do now is add the OR $alwaysTrueCondition between the " and --. If we use 1=1 as our always true condition, our query will be:

" OR 1=1 --

If we use that query:

We get the password for Level 15:

And if you go through Burp Suite, you can see that the full query string is SELECT * from users where username="" OR 1=1 -- " and password="" (or SELECT * from users where username="" OR 1=1 -- since everything after the — is ignored).

Takeaway: enable debug input for yourself where available, and look for SQL injection vulnerabilities where user input is not filtered or escaped.