In today’s post, we’re going to be looking at Level 14 of the Natas wargame, provided by Over The Wire.
What’s Going On?
Upon logging in, we see what looks like a log-in form with two text fields for a username and password, as well as a submit button and our usual PHP source code link. Let’s go a head and look at the source code first.
Looking at the source, we can see that the main PHP section starts by performing a check for the username field of the HTTP request. If it doesn’t exists, the form is displayed on the screen. If it did exist, the code assumes the field was submitted. If the username field did exist, the next thing that happens is PHP makes a connection to a MySQL database. After connecting to the database, a query is created by concatenating strings for the query and the values directly from the username and password fields of the HTTP request. Next, there is a check for the “debug” field of the HTTP request, and if it was provided, the query string is printed to the screen. Finally, the query is executed, and if it returns at least one row, then the password for the next level is printed to the screen.
Now that we know what’s happening, how can we use it to get the password to the next level without actually knowing any usernames or passwords from the database?
Exploit
Looking at the PHP code, we saw that the value of the username and password fields of the HTTP request get directly placed into the query string without validation or sanitization. This means that we need to find some way to alter the SQL query with the value of those fields, such that the query itself will return at least one row. Again, since we don’t know the usernames or passwords, we’ll have to alter the query to return a row regardless of those factors. Lets look at the query we want to alter and think about how to alter it:
SELECT * from users where username=”username” and password=”password”;
This is the query that would execute if the username and password fields held the values username and password respectively. Now, let’s try to determine a similar query which would return at least one row, regardless of the username or password entered. To do this, we’re going to have to alter the logic of the query. We are able to alter the logic based on what we input for either the username or password, since control characters are not filtered out of either field. Lets look at an example query structured around this one which will return fields:
SELECT * from users where username=”” and password=”” or ‘1’=’1′ “”;
The above query will select all rows from the users table where the username and password are blank OR where ‘1’=’1′. Since ‘1’=’1′ is a tautology, it is always true. This means that every row will be selected. So, how can we go about making that query from what we were presented with? Well, it turns out it’s quite easy. All we have to do is leave the username field blank, and put a specific value into the password field. Since the password field encloses it’s value in quotes, we can pretend the first quote following the equal sign is the first quote of the enclosure, and the last quote before the semi-colon is the last quote of the enclosure. Thus, to form this query, we just need to enter the enclosed value into the password field:
” or ‘1’=’1′ ”
Upon submitting the above line as the password, we get a message that the log-in was successful and we are presented with the password for the next level.
So What?
Yet again we see how not verifying, sanitizing or escaping user supplied input can be very dangerous. While those are good ways to protect against this type of attack (SQL injection in this case), another way is to use better API calls which combine input in a safe manner, such as parameterized/prepared statements.