Natas 11
Level Goal
Username: natas11
Password: U82q5TCMMQ9xuFoI3dYX61s7OZD9JKoK
URL: http://natas11.natas.labs.overthewire.org
En se connectant à la page du challenge (curl http://natas11.natas.labs.overthewire.org -u natas11:U82q5TCMMQ9xuFoI3dYX61s7OZD9JKoK
) on a une page avec un formulaire indiquant que les cookies sont protégés via XOR :
Cookies are protected with XOR encryption<br/><br/>
<form>
Background color: <input name=bgcolor value="#ffffff">
<input type=submit value="Set color">
</form>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
Si l'on regarde le code source on a :
$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");
function xor_encrypt($in) {
$key = '<censored>';
$text = $in;
$outText = '';
// Iterate through each character
for($i=0;$i<strlen($text);$i++) {
$outText .= $text[$i] ^ $key[$i % strlen($key)];
}
return $outText;
}
function loadData($def) {
global $_COOKIE;
$mydata = $def;
if(array_key_exists("data", $_COOKIE)) {
$tempdata = json_decode(xor_encrypt(base64_decode($_COOKIE["data"])), true);
if(is_array($tempdata) && array_key_exists("showpassword", $tempdata) && array_key_exists("bgcolor", $tempdata)) {
if (preg_match('/^#(?:[a-f\d]{6})$/i', $tempdata['bgcolor'])) {
$mydata['showpassword'] = $tempdata['showpassword'];
$mydata['bgcolor'] = $tempdata['bgcolor'];
}
}
}
return $mydata;
}
function saveData($d) {
setcookie("data", base64_encode(xor_encrypt(json_encode($d))));
}
$data = loadData($defaultdata);
if(array_key_exists("bgcolor",$_REQUEST)) {
if (preg_match('/^#(?:[a-f\d]{6})$/i', $_REQUEST['bgcolor'])) {
$data['bgcolor'] = $_REQUEST['bgcolor'];
}
}
saveData($data);
Le code a grosso-modo l'effet suivant :
- il créé un tableau (en PHP les tableaux peuvent avoir une clef spécifique, comme les objets en javascript ou les dictionnaires en Python) par défaut avec pour valeur
{ showpassword: "no", bgcolor: "ffffff" }
- si l'utilisateur a un cookie
data
avec une valeur le code va "déchiffrer" son contenu en le décodant de base64 vers ASCII puis en appliquant une clef XOR - si le résultat peut être transformé en un tableau et que ce tableau a des clefs
showpassword
etbgcolor
, et que cette dernière a pour valeur une couleur sous forme hexadécimal (ex:#ffffff
) - alors le tableau par défaut est ré-écrit avec ces données
- puis si l'utilisateur a envoyé une couleur
bgcolor
via une requête, le contenu de celle-ci est filtrée par regex et remplace la valeurbgcolor
du tableau - le tout est transformé en JSON, chiffré avec XOR et encodé en base64 puis sauvegardé dans un cookie
data
Il y a plusieurs manière de trouver la solution à ce problème :
- utiliser le fait de pouvoir envoyer de la donnée arbitraire via le champ
bgcolor
pour trouver une partie de la clef utilisée pour l'opération de XOR - envoyer un cookie modifé de manière à modifier la valeur de
showpassword
deno
àyes
- utiliser les informations révélées par le code source et le contenu du cookie
data
pour retrouver la clef XOR
Un point d'importance : une opération XOR est commutative et associative. Cette particularité fait que si l'on XOR un texte chiffré par XOR avec sa valeur déchiffrée on obtient la clef XOR
Si l'on refait une requête CURL en regardant les cookies :
curl --cookie-jar - http://natas11.natas.labs.overthewire.org -u natas11:U82q5TCMMQ9xuFoI3dYX61s7OZD9JKoK
On obtient :
natas11.natas.labs.overthewire.org FALSE / FALSE 0 data ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw%3D
Le contenu du cookie est en base64 encodé via url encoding (le %3D
à la fin correspond à =
). On pourrait le décoder en un texte illisible via la commande suivante :
echo "ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=" | base64 --decode
Or, on sait que la chaîne encodée est au départ est le tableau PHP $defaultdata
qui a été mis au format json avant d'être chiffré avec XOR et encodé en base64.
Comme indiqué au-dessus, si on chiffre un texte avec XOR, le résultat chiffré avec le texte d'origine donne la clef utilisée pour le chiffrement XOR
On sait également que le texte chiffré via XOR et enregistré dans le cookie en base64 a pour valeur par défaut :
{"showpassword":"no","bgcolor":"#ffffff"}
Une solution simple peut être écrite en Python :
import base64
cookie_content = base64.b64decode('ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=').hex()
default_value = '{"showpassword":"no","bgcolor":"#ffffff"}'.encode("utf-8").hex()
hex_key = format(int(cookie_content, 16) ^ int(default_value, 16), "x")
print(bytes.fromhex(hex_key).decode('utf-8'))
Ce code peut être détaillé comme suit :
- on importe la bibliothèque
base64
permettant de décoder du texte en base64 - on décode le contenu du cookie (qui est en base64) et on le transforme au format hexadécimal
- on transforme au format hexadécimal le json par défaut
- on XOR le tout
- on transforme l'hexadéciamal résultant en texte lisible
Le fait de transformer du texte en hexadécimal pour des opérations XOR est une bonne pratique qui permet de s'assurer de la précision des opérations
Si l'on exécute ce code on obtient le résultat suivant :
~ python xor.py
qw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jq
On peut donc en déduire que la clef XOR a pour valeur qw8J
répétée jusqu'à atteindre la longeur de la chaine à chiffrer.
Sachant cela on peut donc créer un cookie ayant la valeur showpassword
à yes
en faisant un XOR de {"showpassword":"yes","bgcolor":"#ffffff"}
avec la clef qw8J
répétée autant de fois que nécessaire, ce qui donne : ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK
. On peut utiliser la commande suivante :
curl http://natas11.natas.labs.overthewire.org -u natas11:U82q5TCMMQ9xuFoI3dYX61s7OZD9JKoK \
--cookie "data=ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK"
Et on obtient la réponse :
The password for natas12 is EDXp0pS26wLKHZy1rDBPUZk0RKfLGIR3