SecBlog

A Simple b(log) of interesting things

Natas Level 20

Level: Natas Level 20
Solved: 30th June
Remarks: Read PHP code and documentation

Quest

We are presented with following webpage

Here is the backend code

<?
function debug($msg) { 
    if(array_key_exists("debug", $_GET)) {
        print "DEBUG: $msg<br>";
    }
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);
}
if(array_key_exists("name", $_REQUEST)) {
    $_SESSION["name"] = $_REQUEST["name"];
    debug("Name set to " . $_REQUEST["name"]);
}

print_credentials();

$name = "";
if(array_key_exists("name", $_SESSION)) {
    $name = $_SESSION["name"];
}
?>


Solution

Lets breakdown the above code, we have following functions:

The print_credentials() function

  - This function will reveal the password for next level if $_SESSION["admin"] is equal to 1.

The mywrite() function in two parts:

Part 1

 debug("MYWRITE $sid $data"); 
 // make sure the sid is alnum only!!
 if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) {
 debug("Invalid SID"); 
    return;
 }

Above code checks if $sid is valid (contains alphanumrics only), strspn function documentation

Part 2

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

Above foreach loop, loops over key value pairs of $_SESSION array and concatenates it as a string and store the whole string in $data variable, which is then stored in a file.

Note that each $key - $value pair is separated by \n (newline).

Summary: $_SESSION variable is stored in a file, each key value pair is stored on a newline separated by a space.


Now, lets look at myread() function

Part 1

debug("MYREAD $sid"); 
// Checks if sid is not alphanumeric
if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) {
  debug("Invalid SID"); 
  return "";
}

Again, this code checks if $sid is valid (contains alphanumrics only)

Part 2

// Create a filename with full path  | session_save_path() gives dir name, rest things are concatenated
$filename = session_save_path() . "/" . "mysess_" . $sid; // Ex: /var/lib/php5/sessions//mysess_5i33p3iojt5et82337v9l9qoe0
if(!file_exists($filename)) {  // exit if file doesn't exist
    debug("Session file doesn't exist");
    return "";
}
debug("Reading from ". $filename);
$data = file_get_contents($filename);  // If it does  exist, read it into $data var
$_SESSION = array();
foreach(explode("\n", $data) as $line) { read each line of file
    debug("Read [$line]");
$parts = explode(" ", $line, 2);
if($parts[0] != "") $_SESSION[$parts[0]] = $parts[1];
}
return session_encode();
}

Above we read the contents of the file line by line, which were stored previously using mywrite function.
The code reads a line and then checks if it contains a key and a value separated by space, if it does, then put that in $_SESSION variable as a key value pair.

Suppose file contains:

name john
hair blue

then $_SESSION variable will contain key value pair like $_SESSION['name'] = 'john' and $_SESSION['hair'] = 'blue'

Alright, now lets fire up Burp, and send some requests.

Below is a screenshot of the first request sent with debug parameter set.

Observations from above request

  - It’s a POST request, sent when we entered name admin and press enter
  - The PHPSESSID is set as $sid in the code. (evident from DEBUG output)
  - $_SESSION["name"] is set to "admin" (This is important)
  - Being the first request, $_SESSION variable is not stored in a file yet

Now send this request to Burp repeater and send the same request again, we get this result.

Above we can see $_SESSION stored and read from /var/lib/php5/sessions//mysess_5i33p3iojt5et82337v9l9qoe0 file.
  - DEBUG: Read [name admin] implies $_SESSION['name'] = 'admin'

Now, we know that in order to reveal the password we need $_SESSION['admin'] = 1, and to do that we append the following line in our POST Body –> admin 1

Above payload sets $_SESSION[admin] to 1 and reveals the password for next level.


Takeaway

  - Before starting the challenge, i had no knowledge about the various PHP functions used in Backend code.
  - Always read the documentation. I read docs for explode, strspn, session_save_path
  - RTFM
  - When faced with large code, break it down to chunks to understand the big picture.

That’s all for Level 20.


« Back