Niveau 11

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 y a plusieurs manière de trouver la solution à ce problème :

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 :

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