Skip to Content
SecuritySécurité applicative

Sécurité applicative — Patterns de prévention opérationnels

Guides pratiques pour coder des endpoints sécurisés : validation, échappement, CSRF, injection, hashing.

Ce document couvre les patterns de prévention concrets à appliquer par endpoint. Pour les vulnérabilités OWASP dans leur ensemble, voir OWASP Top 10. Pour les headers HTTP, voir Headers HTTP sécurisés.

Validation d’entrée

Toujours valider côté serveur, même si le front-end valide déjà. Privilégier les listes blanches (typage strict, regex bornées) aux listes noires (blocage de motifs malveillants).

// ✅ Java / Spring Boot — validation par annotations @Valid public record CreateUserRequest( @NotBlank @Size(max = 100) String email, @Pattern(regexp = "^[A-Z]{2}\\d{6}$") String countrycode, @Min(0) @Max(150) int age ) {}
# ✅ Python / Flask — validation par fonction ou dépendance import re def validate_email(email: str) -> str: if not re.fullmatch(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", email): raise ValueError("Email invalide") return email.strip().lower()

Pièges courants

  • Ne pas faire confiance à Content-Type : l’attaquant peut forcer n’importe quel type.
  • trim() et strip() seul ne protège pas contre les données valides mais malveillantes (ex. +1 dans un champ booléen).
  • Toujours invalider, jamais ignorer silencieusement.

Échappement de sortie (XSS)

L’échappement dépend du contexte dans lequel la donnée est insérée : HTML body, attribut, JavaScript, URL.

// ✅ Java — échappement HTML systématique import org.springframework.util.HtmlUtils; String safe = HtmlUtils.htmlEscape(userInput); // ↛ Ne jamais retourner userInput brut dans une vue HTML
# ✅ Python — escaping Jinja2 (défaut) ou html.escape from markupsafe import escape return escape(user_input)
// ✅ Échappement URL pour un redirect contrôlé import java.net.URLEncoder; String url = URLEncoder.encode(rawRedirect, StandardCharsets.UTF_8); // ↛ Ne jamais utiliser rawRedirect directement dans un redirect

Contextes XSS

ContexteType d’échappementRisque si omis
HTML bodyhtmlEscape / escapeXSS stocké
Attribut HTMLhtmlEscape + quotesXSS dans onclick, href
JavaScript (innerHTML)Sanitisation JS stricteXSS DOM-based
URL (attribut href)URLEncoderRedirection malveillante

Règle d’or : échapper à la sortie (contexte), pas à l’entrée (données multiples).

CSRF — Cross-Site Request Forgery

Protéger les actions modifiant un état via un token de liaison à la session.

// ✅ Spring Security — CSRF activé par défaut @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) ); return http.build(); }
# ✅ Python / Flask — CSRFProtect (WTForms) from flask_wtf.csrf import CSRFProtect app.config['SECRET_KEY'] = os.environ['SECRET_KEY'] CSRFProtect(app) # ↛ Par défaut, exige un token `_csrf_token` dans tous les formulaires POST

Alternatives / complémentaires

  • SameSite=Strict sur les cookies de session : le navigateur refuse d’envoyer le cookie dans un contexte cross-site.
  • Double-submit cookie : le serveur exige que le token CSRF soit identique dans le cookie et le header.

Règle : utiliser SameSite Strict ou Lax et un token CSRF pour les APIs soumises à des attaques forgées depuis d’autres origines.

Injection (SQL, LFI, commande)

Le remède universel est le paramétrage : envoyer les données comme paramètres, jamais comme fragments de code.

// ✅ SQL — PreparedStatement String sql = "SELECT * FROM users WHERE email = ?"; PreparedStatement ps = connection.prepareStatement(sql); ps.setString(1, userInput);
// ✅ Commande — whitelist d'exécutables, jamais Runtime.exec(userInput) private static final Set<String> VALID_COMMANDS = Set.of("git", "ping", "ls"); public String run(String cmd, String... args) throws IOException { if (!VALID_COMMANDS.contains(cmd)) { throw new SecurityException("Commande interdite : " + cmd); } ProcessBuilder pb = new ProcessBuilder(cmd, args); return new String(pb.start().getInputStream().readAllBytes()); }
# ✅ Python — paramétrage SQLAlchemy from sqlalchemy import select, text stmt = text("SELECT * FROM users WHERE email = :email") result = session.execute(stmt, {"email": user_input})

Pièges LFI

// ❌ Jamais — concaténation de chemin utilisateur new File("/uploads/" + userInput).read(); // ✅ Whitelist de répertoires autorisés Set<Path> ALLOWED = Set.of(Paths.get("/uploads"), Paths.get("/templates")); if (!path.startsWith(ALLOWED.stream().toList())) { throw new SecurityException("Chemin invalide"); }

Hashing de mots de passe

Toujours utiliser un algorithme à coût ajustable (bcrypt, argon2id). Jamais MD5, SHA-1, SHA-256 ou tout hasher sans sel.

// ✅ Java / Spring Security — BCrypt (défaut) @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
# ✅ Python — argon2 via passlib from passlib.context import Argon2Context argon2 = Argon2Context(time_cost=2, memory_cost=65536, parallelism=4) hash_ = argon2.hash(password) argon2.verify(password, hash_)

Pourquoi pas SHA-256 ?

SHA-256 est rapide (des milliards de hashes/seconde sur un GPU moderne) et sans sel par défaut. Un dictionnaire de 10^9 mots courants tient en ~20 Mo et peut être parcouru en quelques heures.

Checklist de revue de sécurité — Un endpoint API

Vérifier chaque nouvel endpoint de contrôleur avec cette liste :

  • Entrées : tous les champs @RequestParam / @RequestBody sont typés et validés (@Valid, regex, bornes)
  • Sorties : aucune donnée sensible (mot de passe, token, champ PII) n’est incluse dans la réponse
  • Injection : aucune requête SQL / commande système n’est construite par concaténation
  • CSRF : l’endpoint n’est pas accessible via une requête cross-site forgée (token ou SameSite)
  • Auth : @PreAuthorize ou filtre équivalent protège l’accès selon le rôle de l’utilisateur
  • Logs : l’endpoint logge les événements de sécurité sans jamais journaliser de secret ou de PII
  • Rate limit : l’endpoint est couvert par le rate limiting global

Pages associées