2026-11-04 — Design, authentification et robustesse

Contexte

Matière : _Test d’intrusion web · Type : cours (Jour 3/3) · Source : support ESIEA_Audit_Web_Jour3 Précédent : 2026-11-03 — Dépendances, crypto et injection

🎯 Objectifs de la séance

  • Repérer les défauts de conception (A06) : logique métier, conditions de course, absence de limites.
  • Exploiter et corriger les défaillances d’authentification (A07) : brute force, énumération, JWT, sessions, reset, MFA.
  • Comprendre l’intégrité (A08), la journalisation (A09) et la gestion des erreurs (A10).

Examen

Vendredi 13 novembre, sur Moodle.


Partie 1 — A06 Insecure Design

Définition

La faille est dans la conception, pas dans un bug ponctuel : aucun correctif de ligne ne la règle. Correction : modéliser les abus dès la conception (threat modeling) et tout vérifier côté serveur.

  • Logique métier : sauter une étape, rejouer une transition, contourner un paiement.
  • Conditions de course (TOCTOU, double dépense) : requêtes concurrentes.
  • Absence de limites : pas de rate limiting, quotas non vérifiés.
  • Falsification de paramètres métier : prix, quantités, montants négatifs.

Contournement de logique métier

L’application suppose que l’utilisateur suit le parcours prévu. On casse cette hypothèse :

  • passer directement à /checkout/confirm sans payer ;
  • appliquer deux fois un code promo ; quantité ou prix négatif ;
  • omettre un paramètre qui déclenche une branche « par défaut » permissive ;
  • rejouer un webhook ou une transition déjà effectuée.

Conditions de course & absence de limites

Deux requêtes simultanées peuvent passer un contrôle avant que l’état ne soit mis à jour (double dépense). Sans rate limiting, brute force et abus deviennent triviaux.

// 💥 Vérifier puis agir (TOCTOU) : 2 requêtes en parallèle passent toutes deux le if
if (account.balance >= amount) {
  account.balance -= amount
  payout(amount)
}
-- 🛡 Opération atomique en base
UPDATE accounts
SET balance = balance - :amt
WHERE id = :id AND balance >= :amt;
-- + verrou / clé d'idempotence + rate limiting par compte / IP

Règle

Rendre l’opération atomique en base + verrou / clé d’idempotence, et limiter le débit.


Partie 2 — A07 Authentication Failures

Définition

Tout ce qui permet de se faire passer pour quelqu’un d’autre : prouver qui l’on est est mal implémenté.

  • Brute force & credential stuffing sans limite ni verrouillage.
  • Énumération d’utilisateurs : réponses différentes selon que le compte existe.
  • JWT mal vérifiés : algorithme none, secret faible, confusion de clés.
  • Sessions : fixation, absence d’invalidation, cookies non sécurisés.
  • Réinitialisation de mot de passe : jetons faibles, poisoning via l’en-tête Host.
  • MFA contournable : OTP sans limite, étape sautable.

Attaques sur les JWT

Un JWT n’est de confiance que si sa signature est vérifiée correctement. Deux classiques :

  1. accepter alg: none (jeton non signé) ;
  2. casser un secret HMAC faible hors ligne, puis forger ses propres jetons.
// 💥 Vérification permissive : "none" accepté
jwt.verify(token, key, { algorithms: ["HS256", "none"] })
// ou clé HMAC triviale ("secret") → forgeable hors ligne
 
// 🛡 Algorithme figé + clé longue et aléatoire
jwt.verify(token, key, { algorithms: ["HS256"] })
// ou RS256 : la clé publique sert uniquement à vérifier

Règle

Figer l’algorithme attendu et une clé forte (ou RS256) ; vérifier la signature systématiquement.

Brute force, sessions & réinitialisation

Sujet🛡 Bonne pratique
Brute force / énumérationlimiter les tentatives, verrouillage, réponses et temps de réponse identiques (login & reset)
Fixation de sessionrégénérer l’identifiant de session après connexion ; invalider à la déconnexion
Cookies de sessionHttpOnly, Secure, SameSite ; durée de vie limitée ; expiration d’inactivité
Reset de mot de passejeton long, aléatoire, à usage unique et expirant ; ne pas se fier à l’en-tête Host
MFAlimiter les OTP, empêcher de sauter l’étape, vérifier sur tous les endpoints

Partie 3 — A08 Software or Data Integrity Failures

Définition

Faire confiance à des données ou du code sans vérifier leur intégrité : désérialisation non sûre, mises à jour non signées, intégrité CI/CD.

Désérialisation non sûre

Désérialiser des données contrôlées par l’attaquant peut instancier des objets arbitraires et déclencher du code pendant la reconstruction (gadget chains), souvent jusqu’au RCE.

# 💥 pickle : un objet forgé exécute du code via __reduce__
import pickle
obj = pickle.loads(request.get_data())
 
# 🛡 JSON = format de données, pas de code
import json
obj = json.loads(request.get_data())
# si un format binaire s'impose : signer (HMAC) et vérifier, restreindre les classes permises

Règle

Ne jamais désérialiser de données non fiables en format exécutable : préférer JSON, sinon signer et vérifier.

Intégrité du code, des mises à jour & du pipeline

  • 💥 Mises à jour / plugins installés sans vérification de signature ; artefacts de build empoisonnés, cache de pipeline altéré, images non signées ; objets sérialisés acceptés sans contrôle.
  • 🛡 Signer et vérifier binaires, mises à jour et artefacts (Sigstore, signatures) ; verrouiller les dépendances ; épingler les actions CI au SHA.

Partie 4 — A09 Security Logging & Alerting Failures

Définition

Sans journalisation ni alerte fiables, une attaque passe inaperçue, et les journaux eux-mêmes peuvent être falsifiés.

Défauts fréquents

  • Événements de sécurité non journalisés (échecs d’auth, accès refusés).
  • Pas d’alerte, pas de corrélation, pas de rétention.
  • Données sensibles en clair dans les logs.
  • Logs falsifiables : injection de fausses lignes.
# 💥 Log injection : user = "admin\n[INFO] ok" forge une fausse ligne
log.info("login: " + user)
 
# 🛡 Logs structurés (JSON), \r \n échappés
log.info("login", extra={"user": user})
# + ne jamais journaliser mots de passe ni jetons

Partie 5 — A10 Mishandling of Exceptional Conditions

Définition (nouveauté 2025)

Quand une erreur survient, l’application doit échouer de façon sûre (fail-closed), sans trop en dire, et sans s’effondrer sous une entrée malveillante.

  • Logique fail-open : une erreur accorde l’accès.
  • Fuites via messages d’erreur.
  • Exceptions non gérées, épuisement de ressources.
// 💥 Fail-open : une erreur → accès accordé
try { allowed = checkAccess(user) }
catch (e) { allowed = true }
 
// 🛡 Fail-closed
try { allowed = checkAccess(user) }
catch (e) { allowed = false; log.error(e) }
// + message générique à l'utilisateur + limites de taille / timeouts

Règle

Échouer en mode fermé (refus par défaut), journaliser l’erreur, message générique à l’utilisateur.


Récapitulatif OWASP Top 10:2025

CatégoriesCe qu’on retient
A01–A02vérifier l’accès côté serveur ; durcir la configuration
A03–A04maîtriser dépendances & chaîne CI/CD ; ne jamais bricoler la crypto
A05séparer données et code (paramétrage, échappement, pas de shell)
A06–A07concevoir contre l’abus ; authentifier et gérer les sessions correctement
A08–A10vérifier l’intégrité ; journaliser ; échouer de façon sûre

Fil rouge

Ne jamais faire confiance à une entrée contrôlée par le client.

🧪 Labs du jour

Voir Labs PortSwigger — Test d’intrusion web (A06 : 3 labs ; A07 : 5 labs ; A08 : 2 labs ; A10 reprend Information disclosure in error messages).