Sécurité des APIs REST : Les 10 Erreurs les Plus Courantes et Comment les Éviter

Dans le article précédent nous avons construit une API REST complète avec FastAPI et Vue 3. Maintenant, nous allons essayer de la casser.

Cet article est né d'un processus que j'applique dans tous mes projets — y compris DevGuardian AI, ma plateforme de détection de vulnérabilités : une fois que le backend fonctionne, je l'analyse comme si j'étais un attaquant. Quels endpoints restent exposés. Quelles données fuient. Ce qui se passe si quelqu'un manipule les tokens ou force les IDs.

Ce que j'ai découvert a changé ma façon d'écrire du code.

La plupart des vulnérabilités dans les APIs REST ne sont pas des exploits sophistiqués. Ce sont des erreurs de conception que tout développeur commet quand il priorise « que ça marche » sur « que ce soit sécurisé ». Cet article couvre les dix plus fréquentes — avec des exemples de code vulnérable, l'attaque qui les exploite, et la correction.

Référence de base : OWASP API Security Top 10 — le standard de l'industrie pour les vulnérabilités des APIs.

⚠️ Avertissement : Tout le code d'attaque dans cet article est destiné à des environnements contrôlés et à des fins éducatives. Appliquer ces techniques sur des systèmes sans autorisation explicite est illégal.


1. Broken Object Level Authorization (BOLA) — Le plus dangereux

OWASP API1:2023

BOLA est systématiquement le #1 du classement OWASP — et pour cause. Il se produit lorsque l'API expose des endpoints qui opèrent sur des ressources identifiées par ID sans vérifier que l'utilisateur authentifié a la permission sur cette ressource spécifique.

Le code vulnérable

# ❌ VULNERABLE — FastAPI
@router.get("/tasks/{task_id}")
def get_task(
    task_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    # Busca la tarea por ID sin verificar que pertenece al usuario
    task = db.query(Task).filter(Task.id == task_id).first()
    if not task:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
    return task

L'attaque

# Usuario A está autenticado y sabe que su tarea tiene el ID 42
# Simplemente itera IDs para ver tareas de otros usuarios:

for i in {1..1000}; do
  curl -s -H "Authorization: Bearer TOKEN_USUARIO_A" \
    https://api.ejemplo.com/api/v1/tasks/$i | jq '.title, .owner_id'
done

L'attaquant peut lire — et potentiellement modifier ou supprimer — les données de n'importe quel utilisateur du système avec une simple boucle. Il n'a besoin ni de credentials volés ni d'exploits. Juste changer un nombre dans l'URL.

La correction

# ✅ CORRECTO — siempre filtrar por owner_id además del ID del recurso
@router.get("/tasks/{task_id}")
def get_task(
    task_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    task = db.query(Task).filter(
        Task.id == task_id,
        Task.owner_id == current_user.id  # ← esta línea es la diferencia
    ).first()
    if not task:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Task not found",
        )
    return task

Règle : Sur chaque endpoint qui opère sur une ressource spécifique, le filtre doit toujours inclure l'identifiant de l'utilisateur authentifié. Ne fais jamais confiance à l'ID dans l'URL comme garantie d'autorisation.


2. Broken Authentication — JWT mal implémenté

OWASP API2:2023

JWT est le mécanisme d'authentification le plus utilisé dans les APIs modernes — et aussi l'un des plus mal implémentés. Les erreurs les plus courantes ne sont pas dans l'algorithme, mais dans la validation.

Erreur 1 : Accepter l'algorithme none

# ❌ VULNERABLE — no especificar algoritmos permitidos
payload = jwt.decode(token, SECRET_KEY)

# Un atacante puede forjar un token así:
# Header: {"alg": "none", "typ": "JWT"}
# Payload: {"sub": "1", "type": "access"}
# Signature: (vacía)
# El token pasa la validación si no especificas algoritmos
# ✅ CORRECTO — siempre especificar la lista de algoritmos permitidos
payload = jwt.decode(
    token,
    SECRET_KEY,
    algorithms=["HS256"]  # lista explícita, nunca omitir
)

Erreur 2 : Ne pas vérifier le type de token

# ❌ VULNERABLE — usar el refresh token como access token
def decode_token(token: str) -> TokenData:
    payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    return TokenData(user_id=payload.get("sub"))
    # No verifica si es access o refresh — ambos son intercambiables

# Un atacante con un refresh token expirado puede intentar usarlo
# como access token si no verificas el campo "type"
# ✅ CORRECTO — validar el claim "type" explícitamente
def decode_access_token(token: str) -> TokenData:
    payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])

    if payload.get("type") != "access":
        raise ValueError("Token type inválido — se esperaba access token")

    user_id = payload.get("sub")
    if not user_id:
        raise ValueError("Token sin subject")

    return TokenData(user_id=int(user_id))

Erreur 3 : Clé secrète faible ou codée en dur

# ❌ VULNERABLE
SECRET_KEY = "secret"           # trivialmente bruteforceable
SECRET_KEY = "mysupersecret123" # en texto plano en el código

# ✅ CORRECTO — generar con entropía suficiente y leer desde env
# Generar: openssl rand -hex 32
SECRET_KEY = os.environ.get("SECRET_KEY")  # mínimo 32 bytes aleatorios
if not SECRET_KEY:
    raise RuntimeError("SECRET_KEY no configurada")

3. Broken Object Property Level Authorization — Mass Assignment

OWASP API3:2023

Cela se produit lorsque le client peut modifier des propriétés de l'objet qu'il ne devrait pas contrôler — comme son propre rôle, son statut de vérification, ou son solde.

Le code vulnérable

# ❌ VULNERABLE — Laravel ejemplo
public function update(Request $request, User $user)
{
    // Acepta cualquier campo que el cliente envíe
    $user->update($request->all());
    return response()->json($user);
}
# El atacante envía campos que no debería poder modificar:
curl -X PUT https://api.ejemplo.com/api/v1/users/me \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Usuario Normal",
    "role": "admin",
    "is_verified": true,
    "credits": 99999
  }'

La correction

# ✅ CORRECTO — FastAPI con schema estricto
class UserUpdateSchema(BaseModel):
    # Solo los campos que el usuario puede modificar
    name: str | None = Field(default=None, max_length=100)
    bio: str | None = Field(default=None, max_length=500)
    # Campos como 'role', 'is_verified', 'credits' NO están aquí

@router.put("/users/me", response_model=UserResponse)
def update_profile(
    data: UserUpdateSchema,  # Pydantic valida y filtra automáticamente
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db),
):
    for field, value in data.model_dump(exclude_unset=True).items():
        setattr(current_user, field, value)
    db.flush()
    return current_user
// ✅ CORRECTO — Laravel con fillable explícito
class User extends Model
{
    // Solo estos campos son asignables masivamente
    protected $fillable = ['name', 'bio'];

    // Nunca en $fillable: role, is_admin, is_verified, balance
}

public function update(Request $request)
{
    $validated = $request->validate([
        'name' => 'sometimes|string|max:100',
        'bio'  => 'sometimes|string|max:500',
    ]);
    auth()->user()->update($validated);
}

4. Unrestricted Resource Consumption — Sans Rate Limiting

OWASP API4:2023

Sans rate limiting, votre API est vulnérable à la force brute sur la connexion, à l'énumération d'utilisateurs, au spam sur des endpoints coûteux, et aux attaques de déni de service basiques.

L'attaque

# Fuerza bruta en el endpoint de login sin ningún límite:
while true; do
  curl -s -X POST https://api.ejemplo.com/api/v1/auth/login \
    -d "username=victima@email.com&password=$(shuf -n1 wordlist.txt)"
done
# Con una wordlist de 10,000 passwords comunes y sin rate limit,
# esto tarda minutos en ejecutarse

La correction dans FastAPI avec slowapi

# requirements: pip install slowapi
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
from fastapi import Request

limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

# Aplicar límites específicos por endpoint
@router.post("/auth/login")
@limiter.limit("5/minute")          # máximo 5 intentos por minuto por IP
async def login(request: Request, form_data: OAuth2PasswordRequestForm = Depends()):
    ...

@router.post("/auth/register")
@limiter.limit("3/hour")            # registros limitados por hora
async def register(request: Request, user_data: UserCreate):
    ...

@router.get("/tasks")
@limiter.limit("60/minute")         # endpoints normales más permisivos
async def get_tasks(request: Request):
    ...

Rate limiting dans Laravel avec middleware

// routes/api.php
Route::middleware(['throttle:5,1'])->group(function () {
    // 5 requests por minuto
    Route::post('/auth/login', [AuthController::class, 'login']);
});

Route::middleware(['throttle:60,1'])->group(function () {
    // 60 requests por minuto para endpoints normales
    Route::apiResource('tasks', TaskController::class);
});

En plus du rate limiting par IP, considère :

  • Rate limiting par utilisateur authentifié (plus précis que par IP)
  • CAPTCHA ou Turnstile sur les endpoints d'inscription et de récupération de mot de passe
  • Alertes lorsqu'un utilisateur dépasse des seuils inhabituels de requêtes

  • 5. Broken Function Level Authorization — Endpoints Admin Exposés

    OWASP API5:2023

    L'API expose des endpoints d'administration sans vérifier correctement que l'utilisateur a le rôle nécessaire — ou pire, elle fait confiance au client pour ne pas « découvrir » les routes.

    Le code vulnérable

    # ❌ VULNERABLE — verificación de rol inconsistente
    @router.delete("/admin/users/{user_id}")
    def delete_user(
        user_id: int,
        current_user: User = Depends(get_current_user),
        db: Session = Depends(get_db),
    ):
        # Verifica autenticación pero no el rol
        user = db.query(User).filter(User.id == user_id).first()
        db.delete(user)
        # Cualquier usuario autenticado puede eliminar cualquier cuenta

    L'attaque

    # El atacante descubre rutas admin con fuzzing:
    ffuf -u https://api.ejemplo.com/api/v1/FUZZ \
         -w /usr/share/wordlists/api-endpoints.txt \
         -H "Authorization: Bearer TOKEN_USUARIO_NORMAL" \
         -mc 200,201,204
    
    # Resultado: /admin/users, /admin/stats, /admin/config
    # Todos accesibles con un token de usuario regular

    La correction

    # ✅ CORRECTO — dependency específica para admin
    from fastapi import HTTPException, status
    
    def require_admin(current_user: User = Depends(get_current_user)) -> User:
        if current_user.role != "admin":
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Insufficient permissions",
            )
        return current_user
    
    # Aplicar en todos los endpoints admin
    @router.delete("/admin/users/{user_id}")
    def delete_user(
        user_id: int,
        admin: User = Depends(require_admin),  # ← falla si no es admin
        db: Session = Depends(get_db),
    ):
        ...

    Principe clé : La sécurité par l'obscurité n'existe pas. Supposer que personne ne va « découvrir » /admin/ est naïf. Les rôles doivent être vérifiés côté serveur à chaque requête, pas côté client.


    6. Unrestricted Access to Sensitive Business Flows

    OWASP API6:2023

    Certains flux métier sont coûteux ou sensibles par nature — créer des comptes, appliquer des coupons, générer des rapports, traiter des paiements. Sans protections supplémentaires, ils peuvent être automatisés et abusés.

    Exemples d'abus réels

    # Abuso de sistema de referidos:
    # Si crear una cuenta genera créditos para el referidor,
    # un script puede crear miles de cuentas falsas:
    for i in {1..1000}; do
      curl -X POST https://api.ejemplo.com/api/v1/auth/register \
        -d "{\"email\": \"fake$i@tempmail.com\", \"referral_code\": \"VICTIMA123\"}"
    done
    
    # Abuso de cupones de descuento de un solo uso:
    # Sin validación atómica, race conditions permiten aplicar
    # el mismo cupón múltiples veces en requests simultáneos

    La correction

    # ✅ Validación atómica con bloqueo en base de datos
    # Para cupones de un solo uso — usando SELECT FOR UPDATE
    @router.post("/checkout/apply-coupon")
    def apply_coupon(
        coupon_code: str,
        current_user: User = Depends(get_current_user),
        db: Session = Depends(get_db),
    ):
        # FOR UPDATE previene race conditions — bloquea el registro
        coupon = (
            db.query(Coupon)
            .filter(Coupon.code == coupon_code, Coupon.is_used == False)
            .with_for_update()  # ← bloqueo a nivel de fila
            .first()
        )
        if not coupon:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Coupon invalid or already used",
            )
        coupon.is_used = True
        coupon.used_by = current_user.id
        # get_db() hace el commit al cerrar — atómico
        return {"discount": coupon.discount_percent}

    7. Server Side Request Forgery (SSRF)

    OWASP API7:2023

    Cela se produit lorsque l'API accepte des URLs contrôlées par l'utilisateur et fait des requêtes depuis le serveur — permettant à l'attaquant d'explorer le réseau interne, d'accéder aux métadonnées cloud, ou de faire du pivoting.

    Le code vulnérable

    # ❌ VULNERABLE — acepta URLs sin validación
    @router.post("/import/url")
    async def import_from_url(url: str, current_user: User = Depends(get_current_user)):
        import httpx
        response = await httpx.get(url)  # El servidor hace el request
        return process_data(response.content)
    # El atacante apunta a la metadata de AWS EC2:
    curl -X POST https://api.ejemplo.com/api/v1/import/url \
      -d '{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}'
    
    # O a servicios internos no expuestos:
    curl -X POST https://api.ejemplo.com/api/v1/import/url \
      -d '{"url": "http://interno.empresa.local/admin/config"}'
    
    # O a Redis si está en la red interna sin autenticación:
    curl -X POST https://api.ejemplo.com/api/v1/import/url \
      -d '{"url": "http://redis:6379/"}'

    La correction

    # ✅ CORRECTO — validar y restringir URLs permitidas
    import ipaddress
    from urllib.parse import urlparse
    
    ALLOWED_SCHEMES = {"https"}
    BLOCKED_HOSTS = {
        "localhost", "127.0.0.1", "0.0.0.0",
        "169.254.169.254",  # AWS metadata
        "metadata.google.internal",  # GCP metadata
    }
    
    def validate_url(url: str) -> str:
        parsed = urlparse(url)
    
        if parsed.scheme not in ALLOWED_SCHEMES:
            raise ValueError(f"Scheme no permitido: {parsed.scheme}")
    
        hostname = parsed.hostname or ""
    
        if hostname in BLOCKED_HOSTS:
            raise ValueError("Host bloqueado")
    
        # Bloquear IPs privadas y de loopback
        try:
            ip = ipaddress.ip_address(hostname)
            if ip.is_private or ip.is_loopback or ip.is_link_local:
                raise ValueError("IP privada no permitida")
        except ValueError as e:
            if "not a valid" not in str(e):
                raise
    
        return url
    
    @router.post("/import/url")
    async def import_from_url(
        url: str,
        current_user: User = Depends(get_current_user),
    ):
        try:
            safe_url = validate_url(url)
        except ValueError as e:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=str(e),
            )
        async with httpx.AsyncClient(
            follow_redirects=False,  # no seguir redirects — pueden bypassear la validación
            timeout=5.0,
        ) as client:
            response = await client.get(safe_url)
        return process_data(response.content)

    8. Security Misconfiguration — CORS mal configuré

    OWASP API8:2023

    CORS mal configuré est l'une des erreurs les plus courantes et les plus faciles à commettre. Un wildcard ou une validation laxiste peut permettre à n'importe quel site web d'effectuer des requêtes authentifiées vers ton API depuis le navigateur d'un utilisateur.

    Configurations dangereuses

    # ❌ VULNERABLE — wildcard con credenciales (inválido pero intentado)
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,  # Los browsers bloquean esto, pero el intento es un error de diseño
    )
    
    # ❌ VULNERABLE — validación de origin con startswith (bypasseable)
    def is_allowed_origin(origin: str) -> bool:
        return origin.startswith("https://miapp.com")
        # "https://miapp.com.attacker.com" pasa esta validación

    La correction

    # ✅ CORRECTO — lista blanca explícita y estricta
    import os
    
    ALLOWED_ORIGINS = os.environ.get("ALLOWED_ORIGINS", "").split(",")
    # En producción: ALLOWED_ORIGINS=https://miapp.com,https://www.miapp.com
    
    app.add_middleware(
        CORSMiddleware,
        allow_origins=ALLOWED_ORIGINS,      # lista explícita, nunca "*"
        allow_credentials=True,
        allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
        allow_headers=["Authorization", "Content-Type"],
        max_age=600,                         # cachear preflight 10 minutos
    )

    Autres mauvaises configurations fréquentes :

    # ❌ Exponer stack trace en producción
    app = FastAPI(debug=True)  # Nunca en producción
    
    # ❌ Headers de servidor que revelan tecnología
    # Por defecto FastAPI incluye: server: uvicorn
    # Nginx debería sobreescribir esto:
    # server_tokens off;
    
    # ❌ Endpoints de documentación expuestos en producción
    app = FastAPI(
        docs_url=None,    # ✅ Deshabilitar en producción
        redoc_url=None,
        openapi_url=None,
    )

    9. Improper Inventory Management — Anciennes versions exposées

    OWASP API9:2023

    Les APIs accumulent les versions. Le problème est que /api/v1/ est « retirée » mais continue de fonctionner — avec la sécurité de 2021, sans les correctifs de 2024.

    Le problème

    # La empresa lanzó v2 con autenticación mejorada.
    # v1 sigue activa "por compatibilidad con clientes viejos":
    
    curl https://api.ejemplo.com/api/v1/users/me  # sin auth → funciona
    curl https://api.ejemplo.com/api/v2/users/me  # sin auth → 401
    
    # El atacante simplemente usa v1. Toda la seguridad de v2 es irrelevante.
    
    # También común: endpoints de desarrollo o testing en producción:
    curl https://api.ejemplo.com/api/v1/debug/users  # lista todos los usuarios
    curl https://api.ejemplo.com/api/v1/health/db    # expone info de la DB

    La correction

    # ✅ Deprecación activa con headers de aviso
    from fastapi import Response
    from datetime import datetime, timezone
    
    DEPRECATED_AFTER = datetime(2026, 12, 31, tzinfo=timezone.utc)
    
    def deprecation_warning(response: Response):
        """Dependency que añade headers de deprecación."""
        response.headers["Deprecation"] = DEPRECATED_AFTER.isoformat()
        response.headers["Sunset"] = DEPRECATED_AFTER.strftime("%a, %d %b %Y %H:%M:%S GMT")
        response.headers["Link"] = '</api/v2>; rel="successor-version"'
    
    # Aplicar a todos los endpoints de v1
    v1_router = APIRouter(
        prefix="/api/v1",
        dependencies=[Depends(deprecation_warning)],
    )
    # Checklist de inventario de API — ejecutar regularmente:
    
    # 1. Listar todos los endpoints activos
    curl https://api.ejemplo.com/api/openapi.json | jq '.paths | keys[]'
    
    # 2. Verificar que endpoints de debug no estén en producción
    grep -r "debug\|test\|dev\|internal" routes/
    
    # 3. Documentar qué versiones están activas y cuándo se retiran

    10. Unsafe Consumption of APIs — Faire aveuglément confiance aux APIs externes

    OWASP API10:2023

    Ton API consomme forcément d'autres APIs — de paiement, de cartographie, d'authentification sociale, de fournisseurs de données. Si tu fais aveuglément confiance à ces réponses sans les valider, un fournisseur compromis peut injecter des données malveillantes dans ton système.

    Le code vulnérable

    # ❌ VULNERABLE — confiar sin validar en respuesta de API externa
    @router.post("/auth/google")
    async def google_oauth(token: str):
        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"https://oauth2.googleapis.com/tokeninfo?id_token={token}"
            )
        user_data = response.json()
        # Usa directamente email y name sin validar
        user = get_or_create_user(email=user_data["email"], name=user_data["name"])
        return create_session(user)

    La correction

    # ✅ CORRECTO — validar estructura y contenido de respuestas externas
    from pydantic import BaseModel, EmailStr
    
    class GoogleTokenInfo(BaseModel):
        """Schema estricto para la respuesta de Google."""
        email: EmailStr
        email_verified: bool
        name: str
        sub: str           # Google user ID
        aud: str           # debe coincidir con tu CLIENT_ID
    
        @field_validator("email_verified")
        @classmethod
        def email_must_be_verified(cls, v: bool) -> bool:
            if not v:
                raise ValueError("Email no verificado por Google")
            return v
    
    GOOGLE_CLIENT_ID = os.environ["GOOGLE_CLIENT_ID"]
    
    @router.post("/auth/google")
    async def google_oauth(token: str):
        async with httpx.AsyncClient(timeout=5.0) as client:
            response = await client.get(
                "https://oauth2.googleapis.com/tokeninfo",
                params={"id_token": token},
            )
        if response.status_code != 200:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Token de Google inválido",
            )
    
        try:
            token_info = GoogleTokenInfo.model_validate(response.json())
        except ValidationError:
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail="Respuesta de Google con formato inesperado",
            )
    
        # Verificar que el token fue emitido para NUESTRA aplicación
        if token_info.aud != GOOGLE_CLIENT_ID:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Token no emitido para esta aplicación",
            )
    
        user = get_or_create_user(email=token_info.email, name=token_info.name)
        return create_session(user)

    Checklist de sécurité pour APIs REST

    Avant de déployer une API en production, voici la checklist minimale :

    ### Autenticación y Autorización
    - [ ] Todos los endpoints protegidos requieren token válido
    - [ ] Cada recurso verifica que pertenece al usuario autenticado (anti-BOLA)
    - [ ] Los endpoints admin tienen dependency de rol separada
    - [ ] JWT valida algoritmo, tipo de token y expiración
    - [ ] SECRET_KEY tiene mínimo 32 bytes de entropía aleatoria
    
    ### Inputs y Validación
    - [ ] Todos los inputs pasan por schemas Pydantic / Form Request de Laravel
    - [ ] No hay `request->all()` ni `**request.dict()` sin filtrado explícito
    - [ ] URLs externas se validan contra lista de hosts/IPs privadas
    - [ ] Respuestas de APIs externas se validan con schemas estrictos
    
    ### Rate Limiting y Disponibilidad
    - [ ] Endpoints de auth tienen rate limiting estricto (≤5/min por IP)
    - [ ] Endpoints costosos tienen límites adicionales
    - [ ] Timeouts configurados en todos los clientes HTTP externos
    
    ### Configuración
    - [ ] CORS con lista blanca explícita, nunca "*" con credentials
    - [ ] Debug mode desactivado en producción
    - [ ] Docs (Swagger/ReDoc) deshabilitados en producción o protegidos
    - [ ] Headers de servidor no revelan versiones (server_tokens off)
    - [ ] Variables sensibles en env vars, nunca hardcodeadas
    
    ### Inventario
    - [ ] Versiones de API antiguas tienen fecha de sunset definida
    - [ ] Endpoints de debug/test no existen en producción
    - [ ] Hay un registro actualizado de todos los endpoints públicos

    Outils recommandés

    Pour une analyse automatisée avant chaque release :

    Analyse statique :

  • Bandit — analyse de sécurité pour Python, détecte les secrets codés en dur, l'utilisation non sécurisée de fonctions cryptographiques, et plus
  • Semgrep — règles de sécurité pour Python, PHP, JavaScript et plus. Gratuit pour les projets open source
  • # Bandit — escanear el directorio del proyecto
    pip install bandit
    bandit -r ./app -ll  # -ll solo reporta medium y high severity
    
    # Semgrep — con reglas de OWASP
    semgrep --config=p/owasp-top-ten ./app

    Analyse dynamique (black-box) :

  • OWASP ZAP — proxy d'interception pour analyser les requêtes/réponses
  • Nuclei — scanner avec templates pour les vulnérabilités connues dans les APIs
  • # Nuclei contra tu API local
    nuclei -u http://localhost:8000 -t nuclei-templates/http/

    Pour CI/CD — GitHub Actions :

    # .github/workflows/security.yml
    name: Security Scan
    
    on: [push, pull_request]
    
    jobs:
      bandit:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-python@v5
            with:
              python-version: '3.12'
          - run: pip install bandit
          - run: bandit -r ./app -ll --exit-zero -f json -o bandit-report.json
          - uses: actions/upload-artifact@v4
            with:
              name: bandit-report
              path: bandit-report.json

    Conclusion

    La sécurité des APIs n'est pas une fonctionnalité qu'on ajoute à la fin — c'est une propriété de conception qui se construit dès le début. Les dix erreurs de cet article ne nécessitent pas une connaissance avancée de la cryptographie ni des systèmes d'exploitation. Elles exigent de la discipline : revoir chaque endpoint en se demandant « que se passe-t-il si quelqu'un essaie d'abuser de cela ? »

    Le processus que je suis dans DevGuardian AI est simple : après avoir implémenté un nouvel endpoint, je le traite en tant qu'attaquant pendant 10 minutes. Puis-je accéder aux données d'autres utilisateurs ? Puis-je contourner l'autorisation ? Puis-je faire faire au serveur quelque chose qu'il ne devrait pas faire ?

    Cet exercice mental a trouvé plus de vulnérabilités que n'importe quel outil automatisé.

    La checklist ci-dessus est le point de départ. Elle n'est pas exhaustive — la sécurité ne l'est jamais — mais elle couvre 80 % de ce que les attaquants tentent en premier.


    Ressources

  • OWASP API Security Top 10 : owasp.org/www-project-api-security
  • Bandit (Python) : bandit.readthedocs.io
  • Semgrep : semgrep.dev
  • OWASP ZAP : zaproxy.org
  • Nuclei : nuclei.projectdiscovery.io
  • PortSwigger Web Security Academy : portswigger.net/web-security — laboratoires gratuits pour pratiquer chaque vulnérabilité

  • Tu as trouvé une vulnérabilité dans l'un des exemples de cet article ou tu as quelque chose à ajouter ? La discussion technique est la bienvenue — tu peux me trouver sur GitHub ou depuis mon portfolio.