· 5 min read

(Yep, another) Natas writeup - level 20->21

A Decorative Image

In case you missed it, NATAS is a wargame/CTF that teaches various web hacking techniques through 34 levels of pure fun. Of those levels, several stood out to me as worthy of a writeup of my own (as I mentioned previously, NATAS writeups are all over the web, so I only write when I have something interesting to contribute). This time, I want to talk about level 20->21, which required static analysis of PHP code.

A Decorative Image

We access the page as an already logged-in user who is not the admin. Logging in as admin would let us see the credentials we seek for level 21. Based on this info, we can assume this level will involve some sort of session-based shenanigans, similarly to levels 18 and 19, where we exploited easily guessable session IDs. Here, however, the session ID cookie has enough entropy to block that path (26 alphanumeric chars give us 2.91 × 10⁴⁰ possibilities), and so we need to look elsewhere. Luckily, we have the sourcecode:

A Decorative Image

First of all, we see that print_credentials() checks the $_SESSION variable for a key-value pair, where the key is admin and its value is set to 1. We can also see how the backend processes and stores the session info:

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

Given $data, which we are told contains the serialized version of $_SESSION, the existing serialization is promptly ignored, and the $data variable contents thrown out, replaced with an empty string. The session variable undergoes ksort(), which tells us two things: it is an array (since that’s what PHP’s ksort() takes), and it might contain more than one key-value pair. This will come useful later. Next, each key-value pair is added back to $data as a string of the format key value\n. Note that newline character, it will be useful as well. Finally, $data is written to a file named mysess_SID to be accessed later.

Using imaginary keys and values
Using imaginary keys and values

Next, let’s see how the file is read back:

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() ?: "";
}

The contents of the file are split (via php’s explode() function) to lines using the newline character, then each line is split to its key and value parts using the space character, and each key-value pair is added to the $_SESSION array.

A Decorative Image

The clincher here, the main thing to notice, is the fact that the contents of $_SESSION aren’t actually validated or sanitized anywhere prior to undergoing the self-proclaimed better encoding in mywrite(). Suppose we could provide a value for a key that would contain a newline character: Alice/nHackedU. When saving, this key would go as-is into the session file. When read, however, the /n we hid in the name will be treated like any other newline, and HackedU will become a new key in the array.

Now that we know we can exploit the backend with a crafted parameter, let’s see bow which means this parameter will get there. As it happens, there is some user input involved, namely in changing our name. In the sourcecode we can see the "name" param of the request is stored in the $_SESSION variable under the same key. And, of course, to figure out what to inject in the name we pass, we just need to look at the first function in the code: print_credentials(), which looks for admin -> 1.

A Decorative Image

Upon our first request, our malicious name has been written to the session file, but not much else has happened:

A Decorative Image

We can see the URL-encoded newline and space characters have been decoded before being written into the session file. Next, we will send another request. It doesn’t have to have any data attached to it, it’s only important for it to have the same session ID. As the session data is loaded from the session file, our payload is parsed as an actual key-value pair in the $_SESSION variable, fooling the credentials function into disclosing the password to us. Success!

A Decorative Image

Some notes I found useful following this level:

  • It’s bad enough to have vulnerable code, but the real problem is leaving comments that point out the vulnerabilities.
  • URL-encoding “/n” is not the same as URL-encoding the /n newline character. Which in hindsight might be obvious, but at the time got me stuck for a while. I highly recommend having a post-it with special character encodings in a visible place (or, like, memorize them, I heard some people can do that).

Hope this walkthrough has been helpful, and I strongly recommend the NATAS wargame in case you came here just looking for memes. Also, sorry about the lack of memes.

Peace!