Aller au contenu

OWASP Top 10

Les dix vulnérabilités les plus critiques des applications web — comprendre, détecter et corriger avec des exemples concrets.


Vue d'ensemble OWASP Top 10 (2021)

graph TD
    A01["A01 Broken Access Control"] --> X[Impact critique]
    A02["A02 Cryptographic Failures"] --> X
    A03["A03 Injection"] --> X
    A04["A04 Insecure Design"] --> Y[Impact eleve]
    A05["A05 Security Misconfiguration"] --> Y
    A06["A06 Vulnerable Components"] --> Y
    A07["A07 XSS"] --> Z[Impact moyen/eleve]
    A08["A08 Integrity Failures"] --> Z
    A09["A09 Logging Failures"] --> W[Impact moyen]
    A10["A10 SSRF"] --> X
    style X fill:#c0392b,color:#fff
    style Y fill:#e07b39,color:#fff
    style Z fill:#e6a817,color:#fff
    style W fill:#7ab648,color:#fff
Rang Vulnérabilité Fréquence Impact
A01 Broken Access Control Très haute Critique
A02 Cryptographic Failures Haute Critique
A03 Injection Haute Critique
A04 Insecure Design Moyenne Élevé
A05 Security Misconfiguration Très haute Élevé
A06 Vulnerable & Outdated Components Haute Élevé
A07 XSS Haute Moyen
A08 Software & Data Integrity Failures Moyenne Élevé
A09 Logging & Monitoring Failures Haute Moyen
A10 SSRF Croissante Critique

A01 — Broken Access Control

Le contrôle d'accès defaillant est la vulnérabilité numéro 1. Un utilisateur peut accéder à des ressources ou actions auxquelles il ne devrait pas avoir accès.

IDOR (Insecure Direct Object Référence)

# VULNERABLE — l'utilisateur controle l'ID directement
@app.route("/api/orders/<int:order_id>")
def get_order(order_id):
    order = db.query("SELECT * FROM orders WHERE id = ?", order_id)
    return jsonify(order)  # N'importe qui peut lire n'importe quelle commande

# SECURISE — verifier que la ressource appartient a l'utilisateur
@app.route("/api/orders/<int:order_id>")
@login_required
def get_order(order_id):
    order = db.query(
        "SELECT * FROM orders WHERE id = ? AND user_id = ?",
        order_id, current_user.id
    )
    if not order:
        abort(404)
    return jsonify(order)

Erreur courante

Retourner une 403 au lieu d'une 404 révélé l'existence de la ressource. Toujours retourner 404 quand la ressource n'appartient pas à l'utilisateur.

A02 — Cryptographic Failures

Utilisation d'algorithmes deprecies, stockage en clair, transmissions non chiffrees.

Hachage de mots de passe

import hashlib
import bcrypt

# VULNERABLE — MD5 cassable en secondes
def store_password_bad(password):
    return hashlib.md5(password.encode()).hexdigest()

# VULNERABLE — SHA256 sans sel, vulnerable aux rainbow tables
def store_password_bad2(password):
    return hashlib.sha256(password.encode()).hexdigest()

# SECURISE — bcrypt avec facteur de cout adapte
def store_password(password):
    salt = bcrypt.gensalt(rounds=12)
    return bcrypt.hashpw(password.encode(), salt)

def verify_password(password, hashed):
    return bcrypt.checkpw(password.encode(), hashed)

Chiffrement de données sensibles

from cryptography.fernet import Fernet

# VULNERABLE — AES-ECB, patterns identiques produisent des blocs identiques
# Ne jamais utiliser ECB pour des donnees structurees

# SECURISE — Fernet (AES-128-CBC + HMAC-SHA256)
key = Fernet.generate_key()  # stocker en secrets manager, pas dans le code
fernet = Fernet(key)

def encrypt_pii(data: str) -> bytes:
    return fernet.encrypt(data.encode())

def decrypt_pii(token: bytes) -> str:
    return fernet.decrypt(token).decode()

A03 — Injection

Toute donnée non fiable interprétée comme commande ou requête. SQL, OS, LDAP, XPath, NoSQL...

Injection SQL

# VULNERABLE — concatenation directe
def get_user_bad(username):
    query = f"SELECT * FROM users WHERE username = '{username}'"
    return db.execute(query).fetchone()
    # username = "admin' OR '1'='1" => tous les utilisateurs

# SECURISE — requetes parametrees
def get_user(username):
    query = "SELECT * FROM users WHERE username = ?"
    return db.execute(query, (username,)).fetchone()

Injection de commande OS

import subprocess
import shlex

# VULNERABLE — shell=True avec entree utilisateur
def ping_host_bad(host):
    result = subprocess.run(f"ping -c 1 {host}", shell=True, capture_output=True)
    return result.stdout
    # host = "8.8.8.8; cat /etc/passwd" => execution arbitraire

# SECURISE — liste d'arguments, validation stricte
import re

def ping_host(host):
    if not re.match(r'^[a-zA-Z0-9.\-]{1,253}$', host):
        raise ValueError("Hostname invalide")
    result = subprocess.run(["ping", "-c", "1", host], capture_output=True, timeout=5)
    return result.stdout

Injection NoSQL

MongoDB est aussi vulnerable. Ne jamais passer un objet utilisateur directement :

// VULNERABLE
db.users.find({ username: req.body.username, password: req.body.password })
// req.body = { username: "admin", password: { $gt: "" } } => bypass

// SECURISE — valider que ce sont des strings
const { username, password } = req.body;
if (typeof username !== 'string' || typeof password !== 'string') {
  return res.status(400).json({ error: 'Input invalide' });
}

A07 — Cross-Site Scripting (XSS)

Injection de code JavaScript dans des pages affichees par d'autres utilisateurs.

XSS Reflechi

// VULNERABLE — rendu direct de l'input dans le DOM
// URL: /search?q=<script>document.cookie</script>
app.get('/search', (req, res) => {
  res.send(`<h1>Resultats pour: ${req.query.q}</h1>`);
});

// SECURISE — echappement HTML
const escapeHtml = require('escape-html');

app.get('/search', (req, res) => {
  const query = escapeHtml(req.query.q || '');
  res.send(`<h1>Resultats pour: ${query}</h1>`);
});

XSS DOM via React

// VULNERABLE — dangerouslySetInnerHTML avec contenu non filtre
function Comment({ text }) {
  return <div dangerouslySetInnerHTML={{ __html: text }} />;
}

// SECURISE — rendu textuel ou DOMPurify pour le HTML intentionnel
import DOMPurify from 'dompurify';

function Comment({ text }) {
  // Option 1 : texte pur (prefere si pas besoin de HTML)
  return <div>{text}</div>;

  // Option 2 : HTML sanitise si le HTML est necessaire
  // return <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(text) }} />;
}

A10 — Server-Side Request Forgery (SSRF)

L'application fait des requêtes HTTP vers une URL controlee par l'attaquant — accès au réseau interne, metadata cloud, services internes.

import urllib.parse
import ipaddress
import requests

# VULNERABLE — fetch d'URL utilisateur sans validation
def fetch_url_bad(url):
    response = requests.get(url)  # http://169.254.169.254/latest/meta-data/ => AWS metadata
    return response.text

# SECURISE — validation stricte de l'URL
ALLOWED_SCHEMES = {"https"}
BLOCKED_HOSTS = {"localhost", "127.0.0.1", "0.0.0.0"}

def is_safe_url(url: str) -> bool:
    parsed = urllib.parse.urlparse(url)
    if parsed.scheme not in ALLOWED_SCHEMES:
        return False
    host = parsed.hostname or ""
    if host in BLOCKED_HOSTS:
        return False
    try:
        addr = ipaddress.ip_address(host)
        if addr.is_private or addr.is_loopback or addr.is_link_local:
            return False
    except ValueError:
        pass  # hostname, pas une IP — continuer
    return True

def fetch_url(url: str) -> str:
    if not is_safe_url(url):
        raise ValueError("URL non autorisee")
    response = requests.get(url, timeout=5, allow_redirects=False)
    return response.text

SSRF et cloud

En environnement cloud (AWS, GCP, Azure), le endpoint de metadata (169.254.169.254 ou fd00:ec2::254) est accessible depuis toute instance. Une SSRF peut exposer les credentials IAM de l'instance.

Synthèse : vulnérabilité, impact, prevention

Vulnérabilité Impact potentiel Prevention principale
Broken Access Control Accès a toutes les données Vérifier ownership côté serveur
Cryptographic Failures Vol de credentials, données en clair bcrypt/Argon2, TLS 1.3, pas de MD5/SHA1
SQL Injection Dump complet de la DB, RCE Requêtes parametrees, ORM
Command Injection Exécution de code arbitraire Pas de shell=True, whitelist des inputs
XSS Vol de session, phishing, defacement Echappement contextuel, CSP stricte
SSRF Accès réseau interne, credentials cloud Validation URL, blocklist IP privées

Chapitre suivant : Gestion des secrets — stocker et rotater les credentials de façon sécurisée.