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.