Overthewire Natas Level 22, 23, 24 and 25
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:
natas22
As ever so often we start with a page. Duh. But this time, we only see the button to reveal the source code to us:
session_start();
if(array_key_exists("revelio", $_GET)) {
// only admins can reveal the password
if(!($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1)) {
header("Location: /");
}
}
<html>
[...]
<body>
<h1>natas22</h1>
<div id="content">
<?
if(array_key_exists("revelio", $_GET)) {
print "You are an admin. The credentials for the next level are:<br>";
print "<pre>Username: natas23\n";
print "Password: <censored></pre>";
}
?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
Actually this seems like an easy one. If there is the word “revelio” set in the $_GET parameter we should get the password. However, if we simply send it by extending the URL by “?revelio=1”, we will get two replies from the server. One with the credentials and one with the blank page with “View source code” on it. Thus we need burp repeater:
Spoiler natas23:
Username: natas23Password: D0vlad33nQF0Hz2EP255TP5wSW9ZsRSE
natas23
Natas 23 begins with a password field and this possibility to see source code.
We get the following source code:
Password:
<form name="input" method="get">
<input type="text" name="passwd" size=20>
<input type="submit" value="Login">
</form>
if(array_key_exists("passwd",$_REQUEST)){
if(strstr($_REQUEST["passwd"],"iloveyou") && ($_REQUEST["passwd"] > 10 )){
echo "<br>The credentials for the next level are:<br>";
echo "<pre>Username: natas24 Password: <censored></pre>";
}
else{
echo "<br>Wrong!<br>";
}
}
// morla / 10111
Ok if we have an array key with “passwd” as key in our $_REQUEST and pass the next comparison, we get the credentials.
The second comparison includes strstr, which “Find the first occurrence of a string”.
Thus this operation finds the first appearance of “iloveyou” in the value stored with the key “passwd” in the $_REQUEST variable.
From the official PHP manual: strstr ( string $haystack , mixed $needle [, bool $before_needle = FALSE ] ) : string
Returns part of haystack string starting from and including the first occurrence of needle to the end of haystack.
What does $_REQUEST["passwd"] > 10
do?
There is a pretty cool webpage from w3schools, that allows you to write quick HTML/PHP examples and evaluate what they do.
First, let’s read more about PHP superglobals.
In general, PHP variables have three different scopes where they are available: local, global and static.
A variable of global scope is defined outside a function and can only be accessed outside a function. A variable with local scope is defined within a function and can only be accessed within that function. If we want a variable to maintain is asserted value after a function has been executed, we can do that by assigning it the static keyword.
Furthermore some rules apply for PHP variables: most importantly a variable starts with a “$” and continues with the name of the variable, e.g. $txt = "Test";
.
So what are superglobals now?
Superglobal variables, are always accessible, regardless of scope - and you can access them from any function, class or file without having to do anything special. [Source]
There are: $GLOBALS, $_SERVER, $_REQUEST, $_POST, $_GET, $_FILES, $_ENV, $_COOKIE, $_SESSION
So far we have dealt with $_REQUEST, $_POST, $_GET, $_COOKIE
and $_SESSION
and for this level we are going to analyze the $_REQUEST
variable:
“PHP $_REQUEST
is a PHP super global variable which is used to collect data after submitting an HTML form.”
And yes this is exactly what we are doing here in this level. The “name” field specifies the key by which the value can be extracted from the variable like this: $_REQUEST[key] = [value]
So in our case the key is “passwd”.
With all that being said, we can fool around with the PHP try out webpage from w3schools:
If we comment out the $_REQUEST["passwd"] > 10
part, the level is easy to solve:
Let’s play around a little:
$_REQUEST["passwd"] = "123iloveyou456";
echo strstr($_REQUEST["passwd"],"iloveyou");
echo ($_REQUEST["passwd"] > 10);
returns iloveyou4561
.
Thus strstr() does exactly what we expected, it finds the first occurence of a string and returns it together with every subsequent string attached to it, thus “iloveyou456”. Why the 1 at the end? Because ($_REQUEST["passwd"] > 10)
evaluates to true.
With that the level should be broken.
Let’s try that out. Let’s enter “123iloveyou456” and see what happens. We get this URL back…
http://natas23.natas.labs.overthewire.org/?passwd=123iloveyou456
Spoiler natas24:
Username: natas24Password: OsRmXFguozKpTZZ5X14zNO43379LZveg
natas24
We have yet another page, that dares us to enter a password. The source code reveals, that this time we are dealing with strcmp and we do not know the password, that we compare against. Tricky… :)
<?php
if(array_key_exists("passwd",$_REQUEST)){
if(!strcmp($_REQUEST["passwd"],"<censored>")){
echo "<br>The credentials for the next level are:<br>";
echo "<pre>Username: natas25 Password: <censored></pre>";
}
else{
echo "<br>Wrong!<br>";
}
}
// morla / 10111
?>
We know that the password field has a maximum length of 20. We could try to bruteforce every option. Let’s assume an alphabet of lower and upper case English alphabets and the numbers 0-9. That is for a 1 digit password 26+26+10 = 62 possible options. For a 2 digit password that is 62 * 62 = 3844 possible options. For a 20 digit password that is $62^{20} = 7,044 * 10^{35}$. Hm, that is way too big. So what else can we do?
Let’s read more about strcmp: If two strings are equal, the function returns 0, thus we need to negate that (thus the ! in the if-clause), to get access if we actually entered the correct password.
Googling for a stcmp vulnerability led me to this page. Here the vulnerability is presented very clearly: strcmp returns 0 if a string is compared with an empty array!
So let’s try this: We intercept the request with a simple password set with burp and put it to the repeater:
Spoiler natas25:
Username: natas25Password: GHF6X7YwACaYYssHVY05cFq83hRktl4c
natas25
Well, this is a new design.
What does the source code say?
// cheers and <3 to malvina
// - morla
function setLanguage(){
/* language setup */
if(array_key_exists("lang",$_REQUEST))
if(safeinclude("language/" . $_REQUEST["lang"] ))
return 1;
safeinclude("language/en");
}
function safeinclude($filename){
// check for directory traversal
if(strstr($filename,"../")){
logRequest("Directory traversal attempt! fixing request.");
$filename=str_replace("../","",$filename);
}
// dont let ppl steal our passwords
if(strstr($filename,"natas_webpass")){
logRequest("Illegal file access detected! Aborting!");
exit(-1);
}
// add more checks...
if (file_exists($filename)) {
include($filename);
return 1;
}
return 0;
}
function listFiles($path){
$listoffiles=array();
if ($handle = opendir($path))
while (false !== ($file = readdir($handle)))
if ($file != "." && $file != "..")
$listoffiles[]=$file;
closedir($handle);
return $listoffiles;
}
function logRequest($message){
$log="[". date("d.m.Y H::i:s",time()) ."]";
$log=$log . " " . $_SERVER['HTTP_USER_AGENT'];
$log=$log . " \"" . $message ."\"\n";
$fd=fopen("/var/www/natas/natas25/logs/natas25_" . session_id() .".log","a");
fwrite($fd,$log);
fclose($fd);
}
<h1>natas25</h1>
<div id="content">
<div align="right">
<form>
<select name='lang' onchange='this.form.submit()'>
<option>language</option>
foreach(listFiles("language/") as $f) echo "<option>$f</option>";
</select>
</form>
</div>
session_start();
setLanguage();
echo "<h2>$__GREETING</h2>";
echo "<p align=\"justify\">$__MSG";
echo "<div align=\"right\"><h6>$__FOOTER</h6><div>";
<p>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
Ok there is a lot going on. Essentially there is the possibility to choose between different languages for that page. Default is “en” but there is also the option “de”. Next thing is that we can include a new language via the $_REQUEST variable. Thus if we send something like $_REQUEST[lang] = "fr"
, we should get the language option “fr”.
However, whatever we insert, it is first checked against path traversal (e.g. ../) and whether we try to read out the password directly via “natas_webpass…".
Only if those tests are passed and the file exists, it appears on the list.
Ok where does the program get the quotes from?
With the include statement, the php script of the page DOES have a problem: “include will only produce a warning (E_WARNING) and the script will continue”.
Thus we can see whether a file exists or not.
Furthermore there is a log. If we include path traversal tries or the natas password file, this will get logged into "/var/www/natas/natas25/logs/natas25_" . session_id() .".log"
file.
But there is more: we cannot manipulate the messages that are being written into the log file, however we can manipulate the “$_SERVER[‘HTTP_USER_AGENT’]".
Let’s try this:
- Get out session cookie. We intercept a message via burp and extract the session cookie from “Cookie: PHPSESSID=HERE IS YOUR SESSION COOKIE”
- With the session cookie, we know into which file we are writing: /var/www/natas/natas25/logs/natas25_SESSIONCOOKIE.log
- Let’s try to read out that log file.
A word onServer: Apache/2.4.10 (Debian)
folder structure: The classic index.html is typically located in /var/www/html/index.html. As every natas level has its own webpage, let’s assume that the index.php of the natas25 page lies in: /var/www/natas/natas25/index.php.
How do we fool now the directory traversal guard? Well, if we include “…/./” that gets substituted to “../". We can test this again on the w3school website:Testing the “…/./” substitution. As we see, it works!
Equipped with that knowledge, let’s try to include the “/var/www/natas/natas25/index.php.” file:We include the index.php file. It works. Nice.
Ok now let’s move onto the “/var/www/natas/natas25/logs/natas25_SESSIONCOOKIE.log” file:
So far so good, now we need to include something useful into the logfile!
What about <?php readfile("/etc/natas_webpass/natas26") ?>
?
With that, we should get the content of the next level printed out to screen!
Let’s try this:
With that we have cracked natas25!!!
Spoiler natas26:
Username: natas26Password: oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T
Summary
Thi level we have worked a lot with PHP and even used an online PHP editor from w3schools to test out our ideas on how to break into a level. We exploited vulnerabilities in strcmp(), strstr(), learnt about PHP’s different important superglobal variables and at the end exploited several the fact, that we could write into a file which’s content got executed in the end.
That’s it for now! See you soon. :)