L'OWASP API Security Top 10 est le référentiel de sécurité applicative le plus utilisé au monde pour identifier, hiérarchiser et corriger les failles spécifiques aux interfaces de programmation. Publié pour la première fois en 2019 puis profondément révisé en 2023, ce classement recense les dix risques les plus critiques qui menacent aujourd'hui les API REST, GraphQL et gRPC exposées en production. Contrairement au Top 10 applicatif historique, centré sur l'injection et le cross-site scripting, la version dédiée aux API place l'autorisation au premier plan : sept des dix catégories touchent directement au contrôle d'accès, et non à la validation de données. Ce guide complet, mis à jour pour 2026, détaille chaque risque avec sa définition officielle, un scénario d'attaque réel observé en environnement de production, un extrait de code vulnérable commenté et la contre-mesure à déployer immédiatement. Il s'adresse aux développeurs back-end, aux architectes logiciels et aux équipes DevSecOps qui doivent sécuriser un parc d'API en croissance permanente, documenter leur conformité NIS 2 ou ISO 27001, et intégrer des tests de sécurité automatisés dans leur chaîne d'intégration continue sans ralentir les livraisons.
Pourquoi un OWASP Top 10 dédié aux API était devenu indispensable
Pendant vingt ans, la sécurité applicative s'est construite autour d'un modèle mental simple : un serveur génère du HTML, un navigateur l'affiche, et l'essentiel des attaques consiste à injecter du contenu hostile dans ce flux. L'injection SQL, le XSS et le CSRF dominaient logiquement les classements. L'architecture moderne a rendu ce modèle obsolète. Aujourd'hui, une application typique est un front-end JavaScript ou une application mobile qui consomme des dizaines d'endpoints REST, eux-mêmes appelant des microservices internes, des passerelles de paiement, des fournisseurs d'identité et des services tiers.
Ce déplacement de la logique métier vers le client change radicalement la surface d'attaque. L'attaquant ne manipule plus un formulaire : il lit la documentation OpenAPI, intercepte le trafic de l'application mobile avec un proxy, et découvre l'intégralité du contrat d'interface. Il connaît les noms des paramètres, les formats d'identifiants, les codes d'erreur. Les protections implicites du monde HTML — le fait qu'un utilisateur ne voie qu'un bouton, ou que la navigation contraigne l'ordre des opérations — disparaissent complètement.
Le constat empirique est sans appel : la grande majorité des compromissions d'API ne relèvent pas d'un défaut technique exotique mais d'un contrôle d'autorisation manquant. Le serveur authentifie correctement l'appelant, mais oublie de vérifier que cet appelant a le droit d'accéder à la ressource demandée. C'est précisément ce que reflète le classement 2023, dont la première position revient au BOLA. Les entreprises soumises à la directive NIS 2 doivent d'ailleurs documenter ces contrôles dans leur analyse de risque : notre guide pratique de conformité NIS 2 détaille les exigences applicables aux interfaces exposées.
Le référentiel officiel est consultable librement sur le site du projet : OWASP API Security Top 10 – 2023. Il constitue la base normative de ce guide.
API1:2023 — Broken Object Level Authorization (BOLA) : définition et mécanique
Le Broken Object Level Authorization, souvent abrégé BOLA et historiquement appelé IDOR (Insecure Direct Object Reference), désigne l'absence de vérification que l'utilisateur authentifié possède bien un droit sur l'objet identifié dans la requête. C'est la vulnérabilité la plus répandue et la plus impactante du monde des API, car elle est à la fois triviale à exploiter et massivement automatisable.
La mécanique est élémentaire. Un endpoint expose une ressource via un identifiant : GET /api/v1/invoices/48219. Le serveur valide le jeton d'authentification, constate qu'il est valide, récupère la facture 48219 et la renvoie. Il n'a jamais vérifié que la facture 48219 appartenait au client authentifié. Un attaquant disposant d'un compte légitime incrémente l'identifiant et récupère l'intégralité de la base de facturation en quelques minutes de script.
Scénario d'attaque concret
Une plateforme de télémédecine expose GET /api/patients/{id}/records. L'application mobile n'appelle jamais cet endpoint avec un autre identifiant que celui de l'utilisateur connecté, ce qui donne à l'équipe de développement une fausse impression de sécurité. Un chercheur intercepte le trafic, remplace son identifiant patient par une valeur voisine et obtient un dossier médical complet : antécédents, ordonnances, coordonnées. L'exploitation ne nécessite aucun outil sophistiqué, seulement un proxy d'interception et une boucle de vingt lignes.
Code vulnérable
// ❌ VULNÉRABLE — Express.js
app.get('/api/patients/:id/records', requireAuth, async (req, res) => {
// L'utilisateur est authentifié... mais rien ne vérifie
// qu'il a le droit d'accéder à CE patient précis.
const records = await db.records.findMany({
where: { patientId: req.params.id }
});
res.json(records);
});
Le décorateur requireAuth donne l'illusion d'un contrôle. Il ne fait qu'authentifier : il répond à la question « qui es-tu ? », jamais à la question « as-tu le droit ? ». Cette confusion entre authentification et autorisation est la cause racine de la quasi-totalité des BOLA en production.
API1 — Protections concrètes et architecture anti-BOLA
Corriger un BOLA ponctuel est facile. Garantir qu'aucun BOLA ne réapparaîtra dans les trois cents endpoints suivants est un problème d'architecture. Trois niveaux de défense doivent être combinés.
Niveau 1 : la vérification explicite à chaque accès
// ✅ CORRIGÉ — l'autorisation est portée par la requête elle-même
app.get('/api/patients/:id/records', requireAuth, async (req, res) => {
const link = await db.careRelationship.findFirst({
where: {
patientId: req.params.id,
practitionerId: req.user.id,
revokedAt: null
}
});
if (!link && req.user.patientId !== req.params.id) {
// 404 et non 403 : ne pas divulguer l'existence de la ressource
return res.status(404).json({ error: 'Not found' });
}
const records = await db.records.findMany({
where: { patientId: req.params.id }
});
res.json(records);
});
Niveau 2 : le scoping systématique au niveau de la couche de données
La meilleure protection consiste à rendre structurellement impossible l'écriture d'une requête non filtrée. Plutôt que de laisser chaque développeur ajouter une clause de sécurité, on encapsule l'accès aux données dans un dépôt qui exige toujours un contexte d'appelant.
// ✅ Go — le contexte utilisateur est un paramètre obligatoire
func (r *InvoiceRepo) FindByID(ctx context.Context, actor Actor, id int64) (*Invoice, error) {
var inv Invoice
err := r.db.QueryRowContext(ctx,
`SELECT id, amount, customer_id FROM invoices
WHERE id = ? AND customer_id = ?`,
id, actor.CustomerID,
).Scan(&inv.ID, &inv.Amount, &inv.CustomerID)
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
return &inv, err
}
Le filtre de propriété fait partie de la signature du dépôt. Un développeur pressé ne peut pas l'oublier : le code ne compile pas sans l'acteur. C'est le principe du secure by construction, infiniment plus fiable qu'une revue de code humaine.
Niveau 3 : identifiants non prédictibles et tests automatisés
Remplacer les entiers auto-incrémentés par des UUIDv4 ou des identifiants ULID ne corrige pas le BOLA — un attaquant qui obtient un identifiant valide par une autre voie exploitera toujours la faille — mais élimine l'énumération de masse, qui transforme une fuite unitaire en fuite massive. Complétez par un test d'intégration systématique : pour chaque endpoint paramétré, un test doit vérifier qu'un utilisateur A reçoit un 404 en demandant la ressource d'un utilisateur B. Ce test se génère automatiquement à partir de la spécification OpenAPI.
Attention aux relations indirectes
Le BOLA le plus difficile à détecter n'est pas celui de la ressource principale mais celui des ressources imbriquées. GET /api/projects/12/tasks/887 peut vérifier que le projet 12 appartient à l'utilisateur sans vérifier que la tâche 887 appartient au projet 12. Chaque segment de chemin doit faire l'objet d'une validation de cohérence.
API2:2023 — Broken Authentication : les défaillances du contrôle d'identité
La catégorie API2 couvre toutes les faiblesses permettant à un attaquant d'usurper une identité : mécanismes de connexion mal protégés, gestion de jetons défaillante, réinitialisation de mot de passe contournable, absence de limitation des tentatives. Les endpoints d'authentification sont, par construction, exposés sans authentification préalable : ils constituent la porte d'entrée la plus attaquée d'une API.
Scénario concret
Une API bancaire expose POST /api/auth/otp/verify avec un code à six chiffres valable cinq minutes. Aucun compteur de tentatives n'est associé au jeton de session temporaire. Un attaquant ayant obtenu un mot de passe par credential stuffing parcourt les un million de combinaisons possibles en parallèle sur cinquante connexions et valide le second facteur en moins de deux minutes. Le MFA était présent, mais inopérant.
Code vulnérable
# ❌ VULNÉRABLE — pas de limitation de tentatives, comparaison non constante
@app.post("/api/auth/otp/verify")
def verify_otp(payload: OtpPayload):
session = redis.get(f"otp:{payload.session_id}")
if not session:
raise HTTPException(400, "Session expirée")
if payload.code == session["code"]: # comparaison sensible au timing
return {"token": issue_jwt(session["user_id"])}
raise HTTPException(401, "Code invalide")
Contre-mesure
# ✅ CORRIGÉ
import hmac
MAX_ATTEMPTS = 5
@app.post("/api/auth/otp/verify")
def verify_otp(payload: OtpPayload):
key = f"otp:{payload.session_id}"
session = redis.hgetall(key)
if not session:
raise HTTPException(400, "Session expirée")
attempts = redis.hincrby(key, "attempts", 1)
if attempts > MAX_ATTEMPTS:
redis.delete(key) # invalidation immédiate
audit.log("otp_bruteforce", session["user_id"])
raise HTTPException(429, "Trop de tentatives")
if hmac.compare_digest(payload.code, session["code"]):
redis.delete(key) # code à usage unique
return {"token": issue_jwt(session["user_id"])}
raise HTTPException(401, "Code invalide")
Les règles non négociables sont les suivantes : limitation de débit par compte et par adresse IP, comparaison à temps constant pour tout secret, invalidation du code après usage ou après épuisement des tentatives, expiration courte des jetons d'accès (quinze minutes maximum) associée à des jetons de rafraîchissement rotatifs, et validation stricte de l'algorithme de signature JWT côté serveur. L'acceptation aveugle de l'en-tête alg reste une faille classique : un attaquant qui soumet {"alg":"none"} ou qui rétrograde RS256 vers HS256 en utilisant la clé publique comme secret HMAC contourne intégralement la signature. Nos bonnes pratiques de sécurisation des JWT détaillent la configuration correcte des bibliothèques les plus utilisées.
API3:2023 — Broken Object Property Level Authorization (BOPLA)
Le BOPLA fusionne deux catégories de l'édition 2019 : l'exposition excessive de données (excessive data exposure) et l'affectation de masse (mass assignment). Le principe commun est que l'autorisation ne s'arrête pas à l'objet : elle doit descendre au niveau de chaque propriété de cet objet, en lecture comme en écriture.
Volet lecture : exposition excessive
Le développeur sérialise l'entité complète en confiance, en supposant que le front-end n'affichera que les champs utiles. L'API renvoie pourtant tout : hachage de mot de passe, jeton de réinitialisation, notes internes, marge commerciale, score de risque.
// ❌ VULNÉRABLE — sérialisation de l'entité brute
app.get('/api/users/me', requireAuth, async (req, res) => {
const user = await db.users.findUnique({ where: { id: req.user.id } });
res.json(user); // renvoie passwordHash, resetToken, internalRiskScore...
});
Volet écriture : mass assignment
// ❌ VULNÉRABLE — le corps de requête est appliqué tel quel
app.patch('/api/users/me', requireAuth, async (req, res) => {
const updated = await db.users.update({
where: { id: req.user.id },
data: req.body // { "role": "admin", "creditBalance": 999999 }
});
res.json(updated);
});
Contre-mesure : listes d'autorisation explicites dans les deux sens
// ✅ CORRIGÉ — schémas d'entrée et de sortie explicites
import { z } from 'zod';
const UpdateProfileSchema = z.object({
displayName: z.string().min(1).max(80).optional(),
locale: z.enum(['fr', 'en', 'de']).optional(),
newsletterOptIn: z.boolean().optional()
}).strict(); // rejette toute propriété non déclarée
function toPublicUser(u) {
return {
id: u.id,
displayName: u.displayName,
locale: u.locale,
createdAt: u.createdAt
};
}
app.patch('/api/users/me', requireAuth, async (req, res) => {
const parsed = UpdateProfileSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ error: 'Invalid payload' });
}
const updated = await db.users.update({
where: { id: req.user.id },
data: parsed.data
});
res.json(toPublicUser(updated));
});
La règle structurante : jamais de liste noire, toujours une liste blanche. Une liste noire oublie systématiquement le champ ajouté le mois suivant. Le mode strict() qui rejette les propriétés inconnues est préférable au mode silencieux qui les ignore, car il transforme une tentative d'exploitation en erreur 400 journalisée. En GraphQL, ce contrôle se déporte au niveau des résolveurs de champ, chacun devant porter sa propre directive d'autorisation.
API4:2023 — Unrestricted Resource Consumption : le coût de l'absence de limites
Chaque appel d'API consomme du CPU, de la mémoire, de la bande passante, du stockage, mais aussi des ressources facturées à l'usage : SMS, e-mails, appels à des API tierces, tokens de modèles d'IA. Sans plafonds, un attaquant provoque soit un déni de service, soit une facture catastrophique — le phénomène désormais appelé denial of wallet.
Scénario concret
Une API de génération de documents expose POST /api/reports acceptant un paramètre pageCount non borné et un tableau recipients sans limite de taille. Chaque destinataire déclenche un e-mail transactionnel facturé. Un attaquant soumet cinquante requêtes avec dix mille destinataires et une pagination de cinq mille pages. Le service de rendu sature, et la facture du fournisseur d'e-mailing explose en une heure.
# ❌ VULNÉRABLE — aucune borne sur les entrées coûteuses
@app.post("/api/reports")
def create_report(req: ReportRequest, user=Depends(current_user)):
pdf = renderer.render(req.template, pages=req.page_count)
for r in req.recipients:
mailer.send(r, pdf) # coût externe non plafonné
return {"status": "sent"}
# ✅ CORRIGÉ — bornes déclaratives + quota métier + exécution asynchrone
class ReportRequest(BaseModel):
template: Literal["invoice", "audit", "summary"]
page_count: conint(ge=1, le=200)
recipients: conlist(EmailStr, min_length=1, max_length=25)
@app.post("/api/reports", dependencies=[Depends(RateLimit("10/hour"))])
def create_report(req: ReportRequest, user=Depends(current_user)):
if not quota.consume(user.org_id, "reports", amount=len(req.recipients)):
raise HTTPException(429, "Quota mensuel atteint")
job = queue.enqueue(render_and_send, req.dict(), user.id)
return {"job_id": job.id, "status": "queued"}
Les contrôles à mettre en place couvrent la limitation de débit à plusieurs granularités (IP, utilisateur, organisation, endpoint), la taille maximale du corps de requête au niveau de la passerelle, un délai d'exécution maximal par requête, la pagination obligatoire avec une taille de page plafonnée, la limitation de profondeur et de complexité des requêtes GraphQL, et des quotas métier pour toute opération engendrant un coût externe. Les traitements longs doivent systématiquement basculer en file asynchrone plutôt que de maintenir une connexion HTTP ouverte.
API5:2023 — Broken Function Level Authorization : l'accès aux fonctions d'administration
Là où le BOLA concerne l'accès à un objet, l'API5 concerne l'accès à une fonction. Un utilisateur standard parvient à invoquer une opération réservée aux administrateurs, généralement parce que le contrôle de rôle est appliqué de manière inégale entre les routes, ou parce qu'une méthode HTTP a été oubliée.
Scénario concret
Une application SaaS protège correctement GET /api/admin/users par un middleware de rôle. Le développeur qui ajoute six mois plus tard DELETE /api/admin/users/{id} copie la ligne de route sans le middleware. La documentation Swagger publique expose l'endpoint. Un utilisateur curieux supprime des comptes concurrents.
// ❌ VULNÉRABLE — protection appliquée route par route, donc oubliable
router.get('/admin/users', requireAuth, requireRole('admin'), listUsers);
router.post('/admin/users', requireAuth, requireRole('admin'), createUser);
router.delete('/admin/users/:id', requireAuth, deleteUser); // ⚠️ oubli
// ✅ CORRIGÉ — refus par défaut au niveau du routeur
const adminRouter = express.Router();
adminRouter.use(requireAuth, requireRole('admin')); // s'applique à tout
adminRouter.get('/users', listUsers);
adminRouter.post('/users', createUser);
adminRouter.delete('/users/:id', deleteUser);
app.use('/api/admin', adminRouter);
// Filet de sécurité global : toute route sans politique déclarée est refusée
app.use((req, res, next) => {
if (!req.route?.authorizationPolicy) {
logger.error({ path: req.path }, 'Route sans politique d\'autorisation');
return res.status(403).json({ error: 'Forbidden' });
}
next();
});
Le principe directeur est le deny by default : une route non explicitement autorisée doit être refusée, jamais ouverte. Ajoutez un test de non-régression qui parcourt la table de routage complète et échoue si un endpoint ne déclare pas de politique. Vérifiez également toutes les méthodes HTTP : un endpoint protégé en GET mais ouvert en PUT reste exploitable, tout comme les variantes de casse d'URL sur les serveurs insensibles.
API6:2023 — Unrestricted Access to Sensitive Business Flows
Cette catégorie, nouvelle en 2023, ne décrit pas un bug technique mais un abus de fonctionnalité parfaitement légitime. L'API fonctionne exactement comme prévu ; c'est son usage automatisé à grande échelle qui nuit à l'entreprise. Aucun scanner de vulnérabilité ne détecte ce risque, car il n'existe aucune signature technique à repérer.
Exemples de flux sensibles
L'achat automatisé de stock limité par des bots revendeurs, la création massive de comptes pour capter des offres de bienvenue, la publication en masse de commentaires ou d'avis, l'exploitation d'un endpoint de parrainage pour générer du crédit, ou encore la réservation puis l'annulation systématique de créneaux pour bloquer un concurrent. Dans tous ces cas, chaque requête prise isolément est valide.
// ❌ VULNÉRABLE — flux d'achat sans aucune friction anti-automatisation
app.post('/api/checkout', requireAuth, async (req, res) => {
const order = await createOrder(req.user.id, req.body.items);
await reserveStock(order);
res.json(order);
});
// ✅ CORRIGÉ — détection comportementale et friction graduée
app.post('/api/checkout', requireAuth, async (req, res) => {
const signals = await riskEngine.evaluate({
userId: req.user.id,
accountAgeHours: req.user.accountAgeHours,
deviceFingerprint: req.headers['x-device-id'],
ipReputation: await ipIntel.score(req.ip),
ordersLastHour: await counters.get(`orders:${req.user.id}:1h`),
requestCadenceMs: await counters.cadence(req.user.id)
});
if (signals.score > 80) {
return res.status(403).json({ error: 'Blocked' });
}
if (signals.score > 50) {
return res.status(428).json({ challenge: await captcha.issue(req.user.id) });
}
const order = await createOrder(req.user.id, req.body.items);
await reserveStock(order);
res.json(order);
});
La démarche commence par un travail d'analyse métier, pas par un audit de code : identifiez les flux dont l'automatisation massive coûterait de l'argent ou de la réputation, puis instrumentez-les. Les leviers efficaces sont l'empreinte d'appareil, la détection de clients non officiels (analyse des en-têtes, TLS fingerprinting), les quotas rapportés à l'ancienneté du compte, la détection de cadence non humaine et les défis progressifs. L'objectif n'est pas de bloquer tous les bots, ce qui est impossible, mais d'élever suffisamment le coût de l'automatisation pour la rendre non rentable.
API7:2023 — Server Side Request Forgery (SSRF)
Le SSRF survient lorsqu'une API récupère une ressource distante à partir d'une URL fournie par l'utilisateur sans valider la destination. Le serveur, qui dispose d'un accès réseau privilégié, devient un relais vers les ressources internes : services de métadonnées cloud, bases de données non exposées, tableaux de bord d'administration.
Scénario concret
Une API permet d'importer un avatar depuis une URL : POST /api/profile/avatar {"url": "..."}. Un attaquant soumet http://169.254.169.254/latest/meta-data/iam/security-credentials/. Le serveur, hébergé sur une instance cloud, récupère les identifiants temporaires du rôle IAM attaché et les renvoie dans la réponse ou les stocke comme image. L'attaquant obtient un accès direct à l'infrastructure.
# ❌ VULNÉRABLE
@app.post("/api/profile/avatar")
def import_avatar(payload: AvatarPayload, user=Depends(current_user)):
resp = requests.get(payload.url, timeout=10)
storage.put(f"avatars/{user.id}", resp.content)
return {"ok": True}
# ✅ CORRIGÉ — validation stricte + résolution DNS contrôlée
import ipaddress, socket
from urllib.parse import urlparse
ALLOWED_SCHEMES = {"https"}
MAX_BYTES = 2 * 1024 * 1024
def assert_public_url(raw: str) -> str:
parsed = urlparse(raw)
if parsed.scheme not in ALLOWED_SCHEMES:
raise ValueError("Schéma non autorisé")
if parsed.port and parsed.port != 443:
raise ValueError("Port non autorisé")
infos = socket.getaddrinfo(parsed.hostname, 443, proto=socket.IPPROTO_TCP)
for info in infos:
ip = ipaddress.ip_address(info[4][0])
if (ip.is_private or ip.is_loopback or ip.is_link_local
or ip.is_reserved or ip.is_multicast):
raise ValueError("Destination interne interdite")
return raw
@app.post("/api/profile/avatar")
def import_avatar(payload: AvatarPayload, user=Depends(current_user)):
url = assert_public_url(payload.url)
resp = requests.get(url, timeout=5, stream=True,
allow_redirects=False) # pas de rebond
content = resp.raw.read(MAX_BYTES + 1)
if len(content) > MAX_BYTES:
raise HTTPException(413, "Fichier trop volumineux")
storage.put(f"avatars/{user.id}", content)
return {"ok": True}
Deux pièges méritent une attention particulière. Le premier est la redirection : valider l'URL initiale ne sert à rien si le client suit un 302 vers 127.0.0.1 ; il faut désactiver le suivi automatique ou revalider chaque saut. Le second est le DNS rebinding, où le domaine résout vers une IP publique lors de la validation puis vers une IP privée lors de la connexion effective. La parade robuste consiste à résoudre l'adresse une seule fois, à la valider, puis à se connecter directement à cette IP en forçant l'en-tête Host. En complément, isolez les sorties réseau de vos services applicatifs derrière un proxy sortant avec liste blanche de destinations, et imposez IMDSv2 sur vos instances cloud.
API8:2023 — Security Misconfiguration : les défauts de configuration
Cette catégorie regroupe tout ce qui relève de l'exploitation d'une configuration inadaptée plutôt que d'un défaut de code : verbosité excessive des erreurs, CORS permissif, en-têtes de sécurité absents, méthodes HTTP inutiles activées, TLS mal configuré, dépendances obsolètes, journalisation insuffisante.
Le cas emblématique : CORS permissif
// ❌ VULNÉRABLE — reflète n'importe quelle origine avec credentials
app.use(cors({
origin: (origin, cb) => cb(null, true),
credentials: true
}));
Cette configuration annule totalement la protection d'origine croisée : n'importe quel site visité par l'utilisateur peut appeler l'API avec ses cookies de session et lire la réponse.
// ✅ CORRIGÉ — liste blanche stricte
const ALLOWED = new Set([
'https://app.exemple.fr',
'https://admin.exemple.fr'
]);
app.use(cors({
origin: (origin, cb) => {
if (!origin || ALLOWED.has(origin)) return cb(null, true);
cb(new Error('Origin non autorisée'));
},
credentials: true,
methods: ['GET', 'POST', 'PATCH', 'DELETE'],
maxAge: 600
}));
app.use(helmet({
contentSecurityPolicy: { directives: { defaultSrc: ["'none'"] } },
hsts: { maxAge: 63072000, includeSubDomains: true, preload: true }
}));
app.disable('x-powered-by');
Erreurs verbeuses
Une trace d'exécution complète renvoyée au client révèle la structure des tables, les chemins du système de fichiers, les versions de bibliothèques et parfois des secrets présents dans les variables d'environnement. En production, l'API ne doit renvoyer qu'un identifiant de corrélation ; le détail reste dans les journaux serveur.
app.use((err, req, res, next) => {
const traceId = crypto.randomUUID();
logger.error({ traceId, err, path: req.path, userId: req.user?.id });
res.status(err.status || 500).json({
error: err.status < 500 ? err.publicMessage : 'Internal error',
traceId
});
});
Complétez par une revue systématique : désactivation de la documentation interactive en production ou protection par authentification, en-têtes Cache-Control: no-store sur les réponses sensibles, TLS 1.2 minimum avec suites modernes, désactivation de TRACE et OPTIONS non nécessaires, et mise à jour continue des dépendances. Le REST Security Cheat Sheet de l'OWASP fournit une liste de contrôle exhaustive sur ces points.
API9:2023 — Improper Inventory Management : les API fantômes
On ne protège pas ce qu'on ne connaît pas. L'API9 sanctionne l'absence d'inventaire à jour des interfaces exposées, de leurs versions et de leurs environnements. Deux figures dominent : l'API fantôme (shadow API), déployée hors du processus officiel, et l'API zombie (zombie API), ancienne version laissée en ligne après la migration.
Scénario concret
Une entreprise migre son API de v1 vers v2 et corrige au passage un BOLA majeur. L'ancienne version reste accessible « pour les clients mobiles non mis à jour », sans mainteneur ni supervision. Dix-huit mois plus tard, un attaquant découvre /api/v1/ par force brute de chemins et exploite la faille corrigée depuis longtemps sur /api/v2/. Le même schéma s'applique aux environnements de recette laissés accessibles publiquement avec une copie des données de production.
Mise en place d'un inventaire vivant
L'inventaire ne doit pas être un tableur maintenu à la main, qui devient obsolète en trois semaines. Il doit être généré automatiquement à partir de trois sources croisées : les spécifications OpenAPI produites par le code, la configuration de la passerelle d'API, et l'observation passive du trafic réel. Toute route observée en production mais absente de la spécification est un signal d'alerte immédiat.
# Étape CI : échec si le code expose des routes non documentées
- name: Détection des shadow endpoints
run: |
./scripts/dump-routes.sh > runtime-routes.json
npx openapi-diff-routes \
--spec openapi.yaml \
--actual runtime-routes.json \
--fail-on-undocumented
Chaque entrée de l'inventaire doit porter au minimum : le propriétaire métier et technique, l'environnement, la classification des données traitées, le mécanisme d'authentification, la date de dernière revue de sécurité, et surtout une date de fin de vie planifiée. Une version d'API sans date de retrait annoncée est une version zombie en devenir. Formalisez une politique de dépréciation : annonce, en-tête Sunset, réduction progressive des quotas, puis extinction effective.
API10:2023 — Unsafe Consumption of APIs : la confiance mal placée
Les neuf risques précédents concernent l'API que vous exposez. L'API10 concerne celles que vous consommez. Les développeurs appliquent une validation rigoureuse aux entrées utilisateur mais font confiance aveuglément aux réponses d'un partenaire, d'un fournisseur SaaS ou d'un microservice interne. Un tiers compromis devient alors un vecteur d'attaque direct sur votre système.
Scénario concret
Une plateforme e-commerce enrichit ses fiches produits via une API fournisseur. La description reçue est insérée telle quelle dans la base puis rendue dans le back-office. Le fournisseur est compromis et renvoie une description contenant une charge JavaScript. Chaque gestionnaire consultant la fiche exécute le code, ce qui permet le vol de sessions administrateur. La faille n'est pas chez le fournisseur : elle est dans l'absence de validation côté consommateur.
// ❌ VULNÉRABLE — confiance totale dans la réponse tierce
const { data } = await axios.get(`${SUPPLIER_API}/products/${sku}`);
await db.products.update({
where: { sku },
data: { description: data.description, price: data.price }
});
// ✅ CORRIGÉ — la réponse tierce est traitée comme une entrée hostile
const SupplierProduct = z.object({
description: z.string().max(5000),
price: z.number().positive().max(1_000_000),
currency: z.enum(['EUR', 'USD'])
}).strict();
const { data } = await axios.get(`${SUPPLIER_API}/products/${sku}`, {
timeout: 4000,
maxRedirects: 0,
maxContentLength: 512 * 1024,
httpsAgent: pinnedAgent // TLS strict, certificat épinglé
});
const parsed = SupplierProduct.safeParse(data);
if (!parsed.success) {
metrics.increment('supplier.invalid_payload');
throw new UpstreamValidationError(sku);
}
await db.products.update({
where: { sku },
data: {
description: sanitizeHtml(parsed.data.description, SAFE_POLICY),
price: parsed.data.price
}
});
Les principes à retenir : valider les réponses entrantes avec la même rigueur que les requêtes utilisateur, imposer TLS avec vérification complète de la chaîne, ne jamais suivre aveuglément les redirections vers d'autres domaines, appliquer des délais d'expiration et des disjoncteurs pour éviter qu'une lenteur amont ne se propage, et assainir tout contenu destiné à être rendu. Cette discipline s'applique aussi aux appels entre microservices internes : le modèle zero trust suppose qu'aucun segment réseau n'est intrinsèquement sûr.
Outils de test de sécurité des API
Aucun outil ne couvre l'ensemble du Top 10, précisément parce que les risques dominants sont des failles de logique d'autorisation qu'aucun scanner ne peut deviner sans connaître le modèle métier. Une stratégie efficace combine plusieurs familles.
Interception et exploration manuelle
Burp Suite et OWASP ZAP restent les références pour l'analyse manuelle. ZAP, entièrement libre, dispose d'un mode API dédié qui importe une spécification OpenAPI et génère automatiquement les requêtes de base. Sa fonction de comparaison de réponses entre deux sessions authentifiées est particulièrement utile pour détecter les BOLA : on rejoue les requêtes de l'utilisateur A avec le jeton de l'utilisateur B et l'on repère les réponses identiques, signe d'un contrôle d'autorisation absent.
Fuzzing piloté par la spécification
Schemathesis génère des cas de test à partir d'un schéma OpenAPI ou GraphQL et vérifie que les réponses respectent le contrat déclaré. Il détecte efficacement les erreurs 500 non gérées, les violations de schéma et les incohérences de types. RESTler, développé par Microsoft Research, va plus loin en inférant les dépendances entre endpoints pour construire des séquences d'appels réalistes.
# Fuzzing d'une API à partir de sa spécification, avec authentification
schemathesis run openapi.yaml \
--base-url https://api.exemple.fr \
--header "Authorization: Bearer $TOKEN" \
--checks all \
--hypothesis-max-examples 300 \
--report junit
Analyse statique et supervision des secrets
Semgrep permet d'écrire des règles maison ciblant précisément les motifs à risque de votre base de code : une requête de base de données sans filtre de propriétaire, un handler sans middleware d'autorisation, un appel HTTP avec une URL non validée. Ces règles maison, adaptées à vos conventions, sont bien plus efficaces que les règles génériques.
rules:
- id: db-query-sans-scope-tenant
languages: [go]
severity: ERROR
message: "Requête sans filtre de tenant — risque de BOLA (API1:2023)"
patterns:
- pattern: $DB.QueryContext($CTX, $SQL, ...)
- pattern-not: $DB.QueryContext($CTX, "=~/.*tenant_id\\s*=\\s*\\?.*/", ...)
Tests d'autorisation automatisés
La brique la plus rentable reste un harnais de test maison qui, pour chaque endpoint paramétré de la spécification, exécute la matrice complète : utilisateur légitime, utilisateur d'un autre locataire, utilisateur sans rôle, requête anonyme. Ce harnais se génère à partir de l'OpenAPI et devient un test de non-régression permanent. Il est plus efficace qu'un scanner commercial pour la catégorie de failles la plus fréquente. Pour une méthodologie d'évaluation offensive complète, consultez notre méthodologie de test d'intrusion applicatif.
Checklist de sécurité API en 12 points
Cette liste de contrôle synthétise les vérifications à effectuer avant toute mise en production d'un endpoint. Elle peut servir de modèle de revue de code ou de critère d'acceptation de definition of done.
- Autorisation au niveau objet — chaque accès à une ressource identifiée vérifie la propriété ou la relation, en base de données, pas seulement en mémoire applicative.
- Autorisation au niveau fonction — la route hérite d'une politique appliquée par défaut au niveau du routeur, sur toutes les méthodes HTTP.
- Autorisation au niveau propriété — schéma d'entrée en liste blanche stricte, schéma de sortie explicite, aucune sérialisation d'entité brute.
- Authentification robuste — algorithme JWT figé côté serveur, jetons courts, rotation des jetons de rafraîchissement, limitation des tentatives sur tous les endpoints d'identité.
- Limites de consommation — débit par IP et par compte, taille de corps maximale, pagination plafonnée, délai d'exécution borné, quotas sur les opérations facturées.
- Flux métier protégés — les parcours sensibles à l'automatisation sont identifiés et instrumentés avec des signaux comportementaux.
- Validation des URL sortantes — liste blanche de schémas et de destinations, refus des plages privées, redirections désactivées ou revalidées.
- Configuration durcie — CORS en liste blanche, en-têtes de sécurité présents, erreurs non verboses, documentation interactive protégée en production.
- Inventaire à jour — endpoint référencé dans la spécification, propriétaire désigné, environnement et classification des données documentés, date de retrait planifiée.
- Consommation défensive — les réponses des API tierces et des microservices internes sont validées, assainies et bornées en taille comme en durée.
- Journalisation et détection — les décisions d'autorisation refusées, les pics d'erreurs 403/404 et les cadences anormales sont journalisés et alertés.
- Tests de non-régression — la matrice d'autorisation est couverte par des tests automatisés exécutés à chaque intégration.
Intégrer la sécurité API dans une chaîne DevSecOps
Un audit annuel ne protège pas une API livrée trois fois par semaine. Les contrôles doivent s'exécuter à chaque modification, avec des seuils d'échec calibrés pour ne pas bloquer inutilement les équipes.
Répartition des contrôles selon le stade
Au moment du commit, les contrôles doivent durer moins de dix secondes : détection de secrets et règles d'analyse statique ciblées. Sur la pull request, on ajoute l'analyse statique complète, la vérification de conformité de la spécification OpenAPI, l'audit des dépendances et le diff de sécurité des routes ajoutées. En pré-production, on lance les tests d'autorisation dynamiques et le fuzzing par schéma. En production enfin, la supervision continue prend le relais avec la détection des endpoints non documentés et l'analyse comportementale.
name: api-security
on: [pull_request]
jobs:
static:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Règles SAST orientées API
run: semgrep --config ./security/rules --config p/owasp-top-ten --error
- name: Détection de secrets
run: gitleaks detect --redact --exit-code 1
- name: Audit des dépendances
run: npm audit --audit-level=high
contract:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validation de la spécification
run: npx @redocly/cli lint openapi.yaml --format codeclimate
- name: Aucune route non documentée
run: node ./scripts/check-shadow-routes.js --fail-on-undocumented
authz:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Démarrage de l'environnement éphémère
run: docker compose -f docker-compose.test.yml up -d --wait
- name: Matrice d'autorisation (BOLA / BFLA)
run: npm run test:authz-matrix
- name: Fuzzing par schéma
run: |
schemathesis run openapi.yaml \
--base-url http://localhost:8080 \
--checks all --hypothesis-max-examples 150
Éviter la fatigue d'alerte
Un pipeline qui échoue sur des centaines de constats de faible criticité sera contourné en une semaine. Calibrez : bloquez sur les catégories critiques (autorisation manquante, secret exposé, dépendance avec exploit public connu) et rapportez sans bloquer le reste. Utilisez un mécanisme de référence (baseline) pour n'échouer que sur les constats nouvellement introduits, ce qui permet d'adopter l'outillage sur une base de code existante sans paralyser les livraisons. Notre article sur la construction d'un pipeline CI/CD sécurisé détaille cette approche de mise en œuvre progressive.
Ce qui évolue en 2026 : GraphQL, gRPC et API pilotées par l'IA
Le Top 10 2023 reste le référentiel en vigueur, mais le paysage a évolué depuis sa publication et plusieurs tendances méritent une attention spécifique dans les architectures récentes.
GraphQL déplace le problème de l'autorisation du niveau de la route vers le niveau du champ. Un seul endpoint expose l'intégralité du graphe de données, ce qui rend inopérants les contrôles basés sur les chemins d'URL. Chaque résolveur doit porter sa propre décision d'autorisation, et la limitation de consommation doit reposer sur le calcul de complexité et de profondeur des requêtes plutôt que sur un simple compteur d'appels. L'introspection doit être désactivée en production.
Les API asynchrones — WebSocket, webhooks, files d'événements — échappent souvent aux contrôles des passerelles HTTP classiques. Une connexion WebSocket autorisée à l'ouverture doit voir ses permissions revalidées à chaque message, car une session peut durer des heures pendant lesquelles les droits de l'utilisateur ont pu être révoqués. Les webhooks entrants relèvent directement de l'API10 et exigent une vérification de signature HMAC ainsi qu'une protection anti-rejeu.
Les API exposant des modèles d'IA concentrent plusieurs risques simultanément : consommation non bornée avec un coût par token qui rend l'API4 particulièrement coûteuse, injection de prompt qui s'apparente à une forme d'API10 lorsque le modèle consomme du contenu externe, et exposition de données via des réponses non filtrées. Le durcissement passe par des quotas stricts en tokens, une validation des sorties du modèle avant tout usage en aval, et l'isolation des outils accessibles au modèle. Les incidents de fuite de données via ces interfaces relèvent des obligations de notification décrites dans notre guide de notification RGPD des violations de données.
Conclusion
L'enseignement central de l'OWASP API Security Top 10 tient en une phrase : la sécurité des API est avant tout un problème d'autorisation. Le BOLA, le BOPLA et le BFLA occupent trois des cinq premières positions, et l'écrasante majorité des fuites de données massives constatées ces dernières années remontent à un contrôle d'accès manquant plutôt qu'à une faille technique sophistiquée. Cette réalité a une conséquence pratique importante : aucun outil automatisé ne vous sauvera seul, car un scanner ne peut pas deviner qui a le droit d'accéder à quoi dans votre modèle métier.
L'approche gagnante consiste à rendre la sécurité structurelle plutôt que déclarative. Une couche d'accès aux données qui exige toujours un contexte d'appelant élimine des catégories entières de BOLA. Un routeur qui refuse par défaut toute route sans politique déclarée élimine les oublis d'autorisation de fonction. Des schémas d'entrée et de sortie explicites éliminent le mass assignment et l'exposition excessive. Ces mécanismes coûtent quelques jours à mettre en place et protègent ensuite chaque nouvel endpoint automatiquement, sans dépendre de la vigilance individuelle d'un développeur sous pression de délai.
Commencez par un inventaire honnête de vos API exposées, y compris les versions anciennes et les environnements de recette. Appliquez ensuite la checklist en douze points aux endpoints traitant les données les plus sensibles. Instrumentez enfin votre chaîne d'intégration continue pour que la matrice d'autorisation soit testée à chaque modification. Cette progression, menée sur un trimestre, transforme durablement le niveau de sécurité d'un parc d'API sans exiger de refonte architecturale.
Points clés à retenir
- L'autorisation domine le classement : API1 (BOLA), API3 (BOPLA) et API5 (BFLA) relèvent tous du contrôle d'accès. Authentifier n'est pas autoriser — la confusion entre les deux est la cause racine de la majorité des compromissions d'API.
- Le contrôle doit vivre dans la couche de données : un dépôt qui exige un contexte d'appelant obligatoire dans sa signature rend le BOLA structurellement impossible, là où une revue de code humaine laissera toujours passer un oubli.
- Liste blanche systématique : schémas d'entrée en mode strict et sérialisation de sortie explicite éliminent d'un coup le mass assignment et l'exposition excessive de données.
- Refus par défaut au niveau du routeur : appliquer la politique d'autorisation à un routeur parent plutôt que route par route supprime la classe entière des endpoints oubliés, y compris sur les méthodes HTTP secondaires.
- Bornez tout ce qui coûte : débit, taille de corps, pagination, durée d'exécution et quotas métier sur les opérations facturées protègent contre le déni de service comme contre le denial of wallet.
- L'API6 échappe aux scanners : l'abus de flux métier ne présente aucune signature technique et exige un travail d'analyse métier suivi d'une instrumentation comportementale.
- Validez les URL sortantes par résolution DNS : contre le SSRF, valider la chaîne de caractères ne suffit pas — il faut résoudre l'adresse, refuser les plages privées et désactiver les redirections.
- Un inventaire manuel est un inventaire faux : générez-le depuis le code, la passerelle et le trafic observé, et planifiez une date de retrait pour chaque version.
- Traitez les réponses tierces comme hostiles : la validation stricte s'applique aussi aux partenaires et aux microservices internes, conformément au modèle zero trust.
- Automatisez la matrice d'autorisation : un harnais de test généré depuis l'OpenAPI, exécuté à chaque intégration, est plus rentable qu'un scanner commercial sur la catégorie de failles la plus fréquente.
FAQ — Questions fréquentes sur l'OWASP API Security Top 10
Quelle est la différence entre l'OWASP Top 10 classique et l'OWASP API Security Top 10 ?
L'OWASP Top 10 classique cible les applications web dans leur ensemble et couvre principalement des risques liés au rendu de contenu et à l'injection : cross-site scripting, injection SQL, désérialisation non sécurisée. L'API Security Top 10 se concentre exclusivement sur les interfaces programmatiques, où la surface d'attaque est différente : la logique métier réside côté client, le contrat d'interface est documenté et prévisible, et l'attaquant dispose d'un accès direct à chaque opération sans passer par une navigation contrainte. Résultat, le classement API place l'autorisation au premier plan, avec sept catégories sur dix touchant au contrôle d'accès. Les deux référentiels sont complémentaires : une application moderne doit couvrir les deux, le Top 10 API venant préciser et compléter le référentiel généraliste sur le périmètre des services exposés.
Le BOLA est-il détectable par un scanner de vulnérabilités automatisé ?
Seulement partiellement, et jamais de manière fiable sans configuration spécifique. Un scanner générique ne connaît pas votre modèle d'autorisation : il ne sait pas si l'utilisateur A est légitimement autorisé à consulter la ressource 42, ni si une réponse 200 constitue un comportement normal ou une fuite. Les outils modernes détectent des schémas suspects — identifiants séquentiels, réponses identiques entre deux sessions distinctes — mais génèrent beaucoup de faux positifs et manquent les cas où l'autorisation dépend d'une relation métier complexe. L'approche fiable consiste à écrire un harnais de test qui, pour chaque endpoint paramétré, rejoue la requête avec plusieurs identités distinctes et vérifie que seule l'identité légitime obtient une réponse 200. Ce harnais se génère automatiquement depuis la spécification OpenAPI et devient un test de non-régression permanent.
Faut-il renvoyer un code HTTP 403 ou 404 lorsqu'un utilisateur accède à une ressource qui ne lui appartient pas ?
Un 404 est généralement préférable pour les ressources dont l'existence même constitue une information sensible. Un 403 confirme implicitement que la ressource existe, ce qui permet à un attaquant d'énumérer les identifiants valides et de cartographier le volume de données, une information exploitable pour du renseignement concurrentiel ou pour préparer une attaque ciblée. Le 404 supprime ce canal auxiliaire. Il existe cependant des contextes où le 403 est plus adapté : lorsque la ressource est publiquement connue et que seule l'action est restreinte, ou lorsque le message doit guider un utilisateur légitime vers une demande de droits. La règle pratique est de choisir le 404 par défaut pour les ressources appartenant à des locataires distincts, et le 403 pour les restrictions fonctionnelles au sein d'un même périmètre.
Une passerelle d'API ou un WAF suffit-il à couvrir le Top 10 ?
Non, et c'est une erreur d'appréciation coûteuse. Une passerelle d'API traite efficacement plusieurs catégories : limitation de débit et de taille pour l'API4, terminaison TLS et en-têtes de sécurité pour l'API8, validation de jetons pour une partie de l'API2, et détection d'endpoints non déclarés pour l'API9. En revanche, elle est structurellement incapable de traiter le BOLA, le BOPLA et le BFLA, car ces décisions requièrent la connaissance du modèle métier : la passerelle ne sait pas que la facture 48219 appartient au client 77. Elle voit passer une requête syntaxiquement valide, authentifiée, et la laisse passer. Une passerelle est donc une couche de défense complémentaire indispensable, mais l'autorisation reste et restera la responsabilité du code applicatif.
À quelle fréquence faut-il auditer la sécurité de ses API ?
La logique de fréquence fixe est mal adaptée au rythme de livraison moderne. Un audit annuel externe reste utile pour la conformité et pour bénéficier d'un regard neuf, mais il ne protège pas une API modifiée plusieurs fois par semaine. Le modèle recommandé combine trois rythmes : des contrôles automatisés à chaque intégration continue, couvrant l'analyse statique, la conformité de la spécification et la matrice d'autorisation ; une revue de sécurité manuelle déclenchée par l'événement plutôt que par le calendrier, à chaque nouvel endpoint exposant des données sensibles ou à chaque modification du modèle d'autorisation ; et un test d'intrusion externe annuel ou semestriel sur les périmètres les plus critiques. Cette combinaison offre une couverture continue tout en concentrant l'effort humain là où il apporte le plus de valeur.
Comment prioriser la remédiation quand on découvre plusieurs risques simultanément ?
Priorisez selon trois critères combinés : la sensibilité des données exposées, la facilité d'exploitation et le volume d'enregistrements atteignables. Un BOLA sur un endpoint renvoyant des données personnelles avec des identifiants séquentiels est une urgence absolue, car il permet une extraction complète de la base par un attaquant disposant d'un simple compte gratuit. À l'inverse, un en-tête de sécurité manquant sur un endpoint public sans authentification peut attendre le prochain cycle. En pratique, traitez d'abord toutes les défaillances d'autorisation sur les endpoints manipulant des données personnelles ou financières, puis les défauts d'authentification, puis les limites de consommation sur les opérations coûteuses, et enfin les durcissements de configuration. Documentez chaque décision de report avec sa justification : cette traçabilité est exigée par la plupart des référentiels de conformité.
À propos de l'auteur
Ayi NEDJIMI
Auditeur Senior Cybersécurité & Consultant IA
Expert Judiciaire — Cour d'Appel de Paris
Habilitation Confidentiel Défense
[email protected]
Ayi NEDJIMI est un vétéran de la cybersécurité avec plus de 25 ans d'expérience sur des missions critiques. Ancien développeur Microsoft à Redmond sur le module GINA (Windows NT4) et co-auteur de la version française du guide de sécurité Windows NT4 pour la NSA.
À la tête d'Ayi NEDJIMI Consultants, il réalise des audits Lead Auditor ISO 42001 et ISO 27001, des pentests d'infrastructures critiques, du forensics et des missions de conformité NIS2 / AI Act.
Conférencier international (Europe & US), il a formé plus de 10 000 professionnels.
Domaines d'expertise
Ressources & Outils de l'auteur
Testez vos connaissances
Mini-quiz de certification lié à cet article — propulsé par CertifExpress
Articles connexes
Patch Management 2026 : Stratégie et Outils pour Entreprises
Guide patch management 2026 — stratégie d'application, priorisation CVSS/EPSS, outils WSUS/Ivanti/Tanium, SLA de patching et métriques MTTR pour entreprises.
Durcissement Cisco IOS et IOS-XE 2026 : Guide de Sécurisation
Guide complet durcissement Cisco IOS et IOS-XE 2026 — CVE critiques, SSH hardening, ACL management plane, SNMPv3, CIS Benchmark et recommandations ANSSI.
Zabbix 7 en 2026 : Supervision Sécurité et Alertes Avancées
Guide Zabbix 7 pour la supervision de sécurité en 2026 — templates sécurité, alertes comportementales, intégration SIEM, chiffrement PSK/TLS et HA cluster.
Un projet cybersécurité ? Parlons-en.
Pentest, conformité NIS 2, ISO 27001, audit IA, RSSI externalisé… nos experts répondent sous 24h pour évaluer votre besoin et vous proposer un accompagnement sur mesure.
Commentaires
Aucun commentaire pour le moment. Soyez le premier à commenter !
Laisser un commentaire