2026-11-02 — Contrôle d’accès et mauvaises configurations

Contexte

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

🎯 Objectifs de la séance

  • Connaître le référentiel OWASP Top 10:2025 et la méthode du cours.
  • Exploiter et corriger les failles de contrôle d’accès (A01) : IDOR, élévation de privilèges, mass assignment, path traversal, SSRF, CSRF, CORS.
  • Repérer et corriger les mauvaises configurations (A02) : comptes par défaut, erreurs verbeuses, en-têtes manquants, fichiers exposés, XXE.

Introduction — méthode

  • Rappels HTTP : méthodes, en-têtes, cookies, codes de statut, paramètres.
  • Outillage de l’auditeur : navigateur + DevTools, proxy d’interception (Burp / ZAP), curl.
  • Déroulé de chaque faille : 🎯 exploitation → 💥 code vulnérable → 🛡 correction → 🧪 lab.
  • ⚖️ Cadre : tests uniquement sur les environnements fournis.

OWASP Top 10:2025

#Catégorie
A01Broken Access Control
A02Security Misconfiguration
A03Software Supply Chain Failures
A04Cryptographic Failures
A05Injection
A06Insecure Design
A07Authentication Failures
A08Software or Data Integrity Failures
A09Security Logging & Alerting Failures
A10Mishandling of Exceptional Conditions

Partie 1 — A01 Broken Access Control

Définition

Le serveur exécute une action sans vérifier que l’utilisateur y a droit. Cause commune : on fait confiance à une valeur contrôlée par le client.

NiveauFailleExemple
ObjetIDOR (horizontal)lire / modifier l’enregistrement d’un autre utilisateur
Endpointélévation verticaleatteindre une fonction admin
Propriétémass assignmentécrire un champ sensible (role, owner)
Fichierpath traversalsortir du dossier prévu
RéseauSSRFfaire émettre au serveur une requête vers une cible interne (fusionné dans A01 en 2025)

IDOR — Insecure Direct Object Reference

Identifiant d’objet exposé et manipulable dans l’URL, un paramètre ou le corps JSON :

GET /api/invoices/1043   →   GET /api/invoices/1044

Démarche d’exploitation

  1. Repérer un identifiant prévisible (numérique, séquentiel, encodé faiblement).
  2. Capturer la requête au proxy et la rejouer en changeant l’ID.
  3. Vérifier l’absence de contrôle de propriétaire : 403 attendu, 200 obtenu.
  4. Automatiser l’énumération pour mesurer l’impact.
// 💥 Vulnérable (Express) : aucune vérification du propriétaire
app.get('/api/invoices/:id', (req, res) => {
  const inv = db.invoices.find(req.params.id)
  res.json(inv)
})
 
// 🛡 Correction
app.get('/api/invoices/:id', auth, (req, res) => {
  const inv = db.invoices.find(req.params.id)
  if (inv.ownerId !== req.user.id) return res.sendStatus(403)
  res.json(inv)
})

Règle

Vérifier la propriété de l’objet, pas seulement l’authentification. Idéalement, filtrer en base par ownerId.

Élévation de privilèges & mass assignment

  • Verticale : atteindre une fonction réservée (admin) via un endpoint non protégé, une fonction cachée, un override de méthode.
  • Mass assignment : le client envoie un champ sensible (role, isAdmin) que le serveur recopie aveuglément.
# 💥 Flask : tous les champs envoyés sont recopiés, y compris "role"
u = User.query.get(uid)
for k, v in request.json.items():
    setattr(u, k, v)
db.session.commit()
 
# 🛡 Correction : allowlist des champs modifiables
ALLOWED = {"name", "bio", "avatar"}
for k, v in request.json.items():
    if k in ALLOWED:
        setattr(u, k, v)

Path traversal / LFI

Un nom de fichier contrôlé par l’utilisateur est concaténé à un chemin sans filtrage : ../ remonte l’arborescence.

GET /?page=../../../../etc/passwd
// 💥 Vulnérable
$f = $_GET["page"];
include("/var/www/pages/" . $f);
 
// 🛡 Correction : basename + allowlist
$f = basename($_GET["page"]);
$allow = ["home", "about"];
if (in_array($f, $allow, true)) include("/var/www/pages/$f.php");

SSRF — Server-Side Request Forgery

L’application récupère une URL fournie par l’utilisateur (webhook, aperçu de lien, import d’image) : on la pointe vers l’infrastructure interne.

url=http://169.254.169.254/latest/meta-data/     ← métadonnées cloud
  • Cibles : services internes non exposés (127.0.0.1, 10.x, métadonnées cloud, Redis…).
  • Contournements de filtre : IP en décimal / octal / hexa, IPv6, redirection, DNS rebinding.
  • Autres schémas : file://, gopher://, dict:// selon le client HTTP.
// 💥 Vulnérable : URL entièrement contrôlée
app.get('/fetch', async (req, res) => {
  const r = await axios.get(req.query.url)
  res.send(r.data)
})
 
// 🛡 Correction : résoudre puis valider l'hôte
const { hostname } = new URL(req.query.url)
const ip = await dns.lookup(hostname)
if (isPrivate(ip.address)) return res.sendStatus(400)
// + allowlist de domaines + interdire les redirections

Règle

Résoudre et valider l’hôte avant la requête, bloquer les IP internes, interdire les redirections.

CSRF — Cross-Site Request Forgery

Un site tiers déclenche une requête authentifiée : le navigateur joint automatiquement le cookie de session. L’utilisateur n’a rien demandé.

<!-- 💥 Formulaire auto-soumis depuis le site de l'attaquant -->
<form action="https://banque.example/transfer" method="POST">
  <input name="to" value="attacker">
  <input name="amount" value="1000">
</form>

🛡 Correction (cumuler) :

  1. Jeton anti-CSRF par formulaire, vérifié côté serveur.
  2. Cookie SameSite=Lax ou Strict (+ HttpOnly, Secure).
  3. Vérifier les en-têtes Origin / Referer.

CORS mal configuré

Une politique CORS trop permissive laisse un site tiers lire des réponses authentifiées. Piège classique : refléter l’Origin + autoriser les credentials.

// 💥 Reflète n'importe quelle origine, avec credentials
res.set("Access-Control-Allow-Origin", req.headers.origin)
res.set("Access-Control-Allow-Credentials", "true")
 
// 🛡 Allowlist explicite
const ALLOW = new Set(["https://app.exemple.fr"])
if (ALLOW.has(req.headers.origin))
  res.set("Access-Control-Allow-Origin", req.headers.origin)
// jamais "*" avec credentials

Partie 2 — A02 Security Misconfiguration

Définition

La faille n’est pas dans le code métier mais dans la configuration de la stack.

  • Comptes / clés par défaut laissés actifs (admin:admin, tokens d’exemple).
  • Messages d’erreur verbeux : stack traces, chemins, versions.
  • En-têtes de sécurité absents.
  • Fichiers exposés : listing de répertoire, .git/, .env, sauvegardes.
  • Parsers XML mal configurés → XXE ; mode debug laissé en production.

Identifiants par défaut

  • Interfaces d’admin livrées avec des identifiants connus jamais changés : Tomcat Manager, consoles d’admin, bases de données, CI.
  • Secrets d’exemple (clé de session, JWT par défaut) → forge de sessions / tokens.
  • 🛡 Correction : changer tout secret au déploiement, désactiver les comptes d’exemple, secrets générés aléatoirement et stockés hors du code (coffre / variables d’environnement), durcir ou retirer les consoles d’admin exposées.

Erreurs verbeuses & stack traces

Une erreur non gérée affichée à l’utilisateur révèle la structure interne : chemins, versions, requêtes SQL, fragments de code.

# 💥 Flask en debug : traceback complet + console de debug interactive
app.run(debug=True)
 
# 🛡 Correction
app.run(debug=False)
 
@app.errorhandler(500)
def err(e):
    log.exception(e)              # détail côté serveur
    return "Erreur interne", 500  # message générique côté utilisateur

En-têtes de sécurité

En-têteRôle
Strict-Transport-Securityforce HTTPS (HSTS)
Content-Security-Policylimite les sources de scripts (anti-XSS)
X-Content-Type-Options: nosniffempêche le MIME sniffing
X-Frame-Options / frame-ancestorsanti-clickjacking
Cookies Secure, HttpOnly, SameSiteprotègent le cookie de session

🛡 Correction : les ajouter au niveau serveur / framework (ex. helmet pour Express).

Fichiers exposés

Un serveur qui liste ses dossiers ou sert .git/, .env ou des sauvegardes laisse récupérer code source, secrets et historique.

GET /uploads/      → index complet du dossier
GET /.git/config   → reconstruction du code source
GET /.env          → secrets, mots de passe
# 🛡 nginx : pas de listing, bloquer les dotfiles
autoindex off;
location ~ /\. { deny all; }
# Apache : Options -Indexes
# Et surtout : ne jamais déployer .git ni .env

XXE — XML External Entity

Un parser XML qui résout les entités externes permet de lire des fichiers locaux, scanner le réseau interne, voire provoquer un déni de service.

<?xml version="1.0"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<data>&xxe;</data>   <!-- l'entité est résolue et renvoyée dans la réponse -->
// 💥 Vulnérable : entités externes résolues
$doc = new DOMDocument();
$doc->loadXML($body, LIBXML_NOENT | LIBXML_DTDLOAD);
 
// 🛡 Correction : ne pas activer NOENT / DTDLOAD, pas de réseau
$doc->loadXML($body, LIBXML_NONET);
// PHP ≥ 8 : entités externes désactivées par défaut. Sinon, préférer JSON.

Règle

Désactiver DOCTYPE et entités externes ; préférer un format sans entités (JSON) quand c’est possible.


🧪 Labs du jour

Voir Labs PortSwigger — Test d’intrusion web (A01 : 9 labs, A02 : 5 labs).

✅ À retenir

  • A01 : chaque requête doit être autorisée côté serveur, objet par objet. 403 attendu → 200 obtenu = faille.
  • SSRF est désormais rangée dans A01.
  • CSRF se corrige par jeton + SameSite + vérification d’Origin.
  • A02 : la faille est dans la configuration, pas dans le code. Debug off, en-têtes posés, .git/.env jamais servis, XXE désactivé.