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 |
|---|---|
| A01 | Broken Access Control |
| A02 | Security Misconfiguration |
| A03 | Software Supply Chain Failures |
| A04 | Cryptographic Failures |
| A05 | Injection |
| A06 | Insecure Design |
| A07 | Authentication Failures |
| A08 | Software or Data Integrity Failures |
| A09 | Security Logging & Alerting Failures |
| A10 | Mishandling 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.
| Niveau | Faille | Exemple |
|---|---|---|
| Objet | IDOR (horizontal) | lire / modifier l’enregistrement d’un autre utilisateur |
| Endpoint | élévation verticale | atteindre une fonction admin |
| Propriété | mass assignment | écrire un champ sensible (role, owner) |
| Fichier | path traversal | sortir du dossier prévu |
| Réseau | SSRF | faire é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/1044Démarche d’exploitation
- Repérer un identifiant prévisible (numérique, séquentiel, encodé faiblement).
- Capturer la requête au proxy et la rejouer en changeant l’ID.
- Vérifier l’absence de contrôle de propriétaire : 403 attendu, 200 obtenu.
- 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 redirectionsRè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) :
- Jeton anti-CSRF par formulaire, vérifié côté serveur.
- Cookie
SameSite=LaxouStrict(+HttpOnly,Secure). - 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 credentialsPartie 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é utilisateurEn-têtes de sécurité
| En-tête | Rôle |
|---|---|
Strict-Transport-Security | force HTTPS (HSTS) |
Content-Security-Policy | limite les sources de scripts (anti-XSS) |
X-Content-Type-Options: nosniff | empêche le MIME sniffing |
X-Frame-Options / frame-ancestors | anti-clickjacking |
Cookies Secure, HttpOnly, SameSite | protè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 .envXXE — 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/.envjamais servis, XXE désactivé.