1/13
🔒

Sécurité des Applications Web avec Symfony 6.4

Formation DWWM - Développeur Web et Web Mobile

6000 Milliards $ de coût annuel des cyberattaques
43% des attaques visent les PME
287 jours pour détecter une faille
🎯 Identifier les vulnérabilités OWASP Top 10
🛡️ Comprendre la sécurité Symfony 6.4
💻 Implémenter des protections XSS, SQL Injection, CSRF
🔐 Sécuriser l'authentification
2/13
🏗️

Architecture de Sécurité Symfony 6.4

🔧 Système modulaire : Symfony organise la sécurité en composants réutilisables et configurables

Configuration de base

# config/packages/security.yaml
security:
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
    
    providers:
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
    
    firewalls:
        main:
            lazy: true
            provider: app_user_provider
            form_login:
                login_path: app_login
                check_path: app_login
            logout:
                path: app_logout
    
    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }
        - { path: ^/profile, roles: ROLE_USER }

🛡️ Les 5 Piliers de la Sécurité Symfony

1. Firewall
Point d'entrée et filtrage
2. Providers
Chargement des utilisateurs
3. Encoders
Hashage des mots de passe
4. Access Control
Gestion des permissions
5. CSRF Protection
Protection des formulaires
3/13
🚨

XSS - Cross-Site Scripting

⚠️ Définition : Injection de code JavaScript malveillant dans une page web, exécuté par le navigateur des victimes

Types d'attaques XSS

1. Reflected XSS
Code malveillant dans l'URL, reflété immédiatement
2. Stored XSS
Code stocké en base de données, exécuté à chaque affichage
3. DOM-based XSS
Manipulation du DOM côté client

Exemple d'Exploitation

❌ Code vulnérable :
<!-- URL malveillante -->
https://site.com/search?q=<script>alert('XSS')</script>

<!-- Affichage dangereux -->
<p>Résultats : <?php echo $_GET['q']; ?></p>
✅ Code sécurisé :
{# Template Twig - Échappement automatique #}
<p>Résultats : {{ query }}</p>

{# Échappement explicite #}
<p>Résultats : {{ query|e }}</p>
🛡️ Protection Symfony : Twig échappe automatiquement toutes les variables par défaut !
4/13
🛡️

Protection XSS Avancée

⚠️ Rappel XSS : Cross-Site Scripting - Injection de code JavaScript malveillant dans les pages web pour voler des données, détourner des sessions ou rediriger les utilisateurs

🛡️ Content Security Policy (CSP)

🎯 Principe : Header HTTP qui définit les sources autorisées pour chaque type de contenu (scripts, styles, images, etc.)

Implémentation avec Symfony

// src/EventListener/SecurityHeadersListener.php
class SecurityHeadersListener
{
    public function onKernelResponse(ResponseEvent $event): void
    {
        $response = $event->getResponse();
        
        // CSP stricte avec nonce pour les scripts
        $nonce = base64_encode(random_bytes(16));
        $csp = "default-src 'self'; " .
               "script-src 'self' 'nonce-{$nonce}'; " .
               "style-src 'self' 'unsafe-inline'; " .
               "img-src 'self' data: https:; " .
               "connect-src 'self'; " .
               "font-src 'self' https://fonts.gstatic.com; " .
               "object-src 'none'; " .
               "base-uri 'self'; " .
               "form-action 'self'; " .
               "frame-ancestors 'none'";
        
        $response->headers->set('Content-Security-Policy', $csp);
        
        // Headers de sécurité additionnels
        $response->headers->set('X-XSS-Protection', '1; mode=block');
        $response->headers->set('X-Content-Type-Options', 'nosniff');
        $response->headers->set('X-Frame-Options', 'DENY');
        $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
        
        // Stocker le nonce pour les templates
        $event->getRequest()->attributes->set('csp_nonce', $nonce);
    }
}

Validation et Sanitisation

#[Route('/search')]
public function search(Request $request, ValidatorInterface $validator): Response
{
    $query = $request->query->get('q', '');
    
    // Validation avec contraintes Symfony
    $violations = $validator->validate($query, [
        new Assert\Length(['max' => 100]),
        new Assert\NotBlank(),
        new Assert\Regex([
            'pattern' => '/^[a-zA-Z0-9\s\-_]*$/',
            'message' => 'Caractères non autorisés'
        ])
    ]);
    
    if (count($violations) > 0) {
        throw new BadRequestException('Données invalides');
    }
    
    // Sanitisation supplémentaire
    $query = strip_tags($query);
    $query = htmlspecialchars($query, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    
    return $this->render('search.html.twig', [
        'query' => $query  // Échappement automatique Twig
    ]);
}
💡 À retenir : CSP = Whitelist des sources autorisées • Nonce pour scripts inline • Échappement Twig automatique • Validation stricte
5/13
💉

Injection SQL

⚠️ Définition : Manipulation des requêtes SQL en injectant du code malveillant dans les paramètres utilisateur, permettant d'accéder, modifier ou supprimer des données

Exemple d'Exploitation

❌ Code vulnérable :
// Concaténation directe - DANGEREUX !
$email = $_POST['email'];
$password = $_POST['password'];

$query = "SELECT * FROM users 
          WHERE email = '".$email."' 
          AND password = '".$password."'";

$result = mysqli_query($connection, $query);
✅ Protection Doctrine :
// Doctrine utilise AUTOMATIQUEMENT les requêtes préparées
public function findByCredentials(string $email): ?User
{
    return $this->createQueryBuilder('u')
        ->where('u.email = :email')
        ->setParameter('email', $email)  // Paramètre préparé
        ->getQuery()
        ->getOneOrNullResult();
}

Implémentation avec Symfony & Doctrine

// Doctrine DBAL - Requêtes préparées manuelles
public function findUsersByCity(string $city): array
{
    $conn = $this->getEntityManager()->getConnection();
    
    // 1. Préparation : structure SQL avec placeholders
    $sql = 'SELECT * FROM users WHERE city = :city AND active = :active';
    $stmt = $conn->prepare($sql);
    
    // 2. Exécution : paramètres liés séparément
    $stmt->bindValue('city', $city, \PDO::PARAM_STR);
    $stmt->bindValue('active', true, \PDO::PARAM_BOOL);
    
    return $stmt->executeQuery()->fetchAllAssociative();
}
💡 À retenir : Requêtes préparées = Protection fondamentale • Doctrine les utilise automatiquement • Séparation code/données • Jamais de concaténation
6/13
🔄

CSRF - Cross-Site Request Forgery

⚠️ Définition : Forcer un utilisateur authentifié à exécuter des actions non désirées à son insu via des requêtes malveillantes

Scénario d'Attaque

1. Utilisateur connecté
L'utilisateur est authentifié sur banque.com
2. Piège activé
L'utilisateur visite un site malveillant
3. Attaque exécutée
Virement automatique sans consentement

Protection avec Symfony

// FormType avec protection CSRF
class TransferType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('amount', MoneyType::class)
            ->add('toAccount', TextType::class)
            ->add('submit', SubmitType::class);
    }
    
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'csrf_protection' => true,        // Activé par défaut
            'csrf_field_name' => '_token',
            'csrf_token_id' => 'transfer_form',
        ]);
    }
}
{# Génération automatique du token #}
{{ form_start(form) }}
    {{ form_widget(form) }}
{{ form_end(form) }}

{# Génération manuelle #}
<form method="post">
    <input type="hidden" name="_token" value="{{ csrf_token('delete-item') }}">
    <button type="submit">Supprimer</button>
</form>
💡 À retenir : CSRF activé par défaut • Tokens uniques par formulaire • Validation automatique • Protection transparente
7/13
🔐

Hashage des Mots de Passe

⚠️ Jamais en clair : Les mots de passe ne doivent JAMAIS être stockés en clair

Mauvaises Pratiques

❌ À éviter :
// Stockage en clair - DANGEREUX !
$password = 'monMotDePasse123';

// Hash MD5/SHA1 - OBSOLÈTE !
$password = md5('monMotDePasse123');

// Sans salt - FAIBLE !
$password = sha1('monMotDePasse123');
✅ Avec Symfony :
// Service de hashage sécurisé
public function __construct(
    private UserPasswordHasherInterface $hasher
) {}

public function hashPassword(User $user, string $plain): string
{
    return $this->hasher->hashPassword($user, $plain);
}

Configuration Avancée

# config/packages/security.yaml
security:
    password_hashers:
        App\Entity\User:
            algorithm: auto      # Utilise le meilleur algorithme
            cost: 12            # Coût de calcul
            memory_cost: 65536  # Mémoire pour Argon2
            time_cost: 4        # Temps pour Argon2
🔒 Algorithmes supportés : bcrypt, Argon2i, Argon2id (recommandé)
8/13
✅

Validation Côté Serveur

⚠️ Règle d'or : Ne JAMAIS faire confiance aux données utilisateur

Validation avec Annotations

// Entity avec contraintes
class User
{
    #[Assert\NotBlank(message: 'Email obligatoire')]
    #[Assert\Email(message: 'Email invalide')]
    private string $email;
    
    #[Assert\Length(
        min: 8,
        max: 255,
        minMessage: 'Mot de passe trop court ({{ limit }} min)',
        maxMessage: 'Mot de passe trop long ({{ limit }} max)'
    )]
    #[Assert\Regex(
        pattern: '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/',
        message: 'Doit contenir majuscule, minuscule et chiffre'
    )]
    private string $password;
    
    #[Assert\Choice(choices: ['draft', 'published', 'archived'])]
    private string $status = 'draft';
}

Validation dans le Controller

#[Route('/user/create', methods: ['POST'])]
public function create(Request $request, ValidatorInterface $validator): Response
{
    $user = new User();
    $user->setEmail($request->request->get('email'));
    $user->setPassword($request->request->get('password'));
    
    // Validation
    $errors = $validator->validate($user);
    
    if (count($errors) > 0) {
        $errorMessages = [];
        foreach ($errors as $error) {
            $errorMessages[] = $error->getMessage();
        }
        
        return $this->json(['errors' => $errorMessages], 400);
    }
    
    // Traitement sécurisé...
}
🔒 Double validation : Toujours valider côté client (UX) ET serveur (sécurité)
9/13
🔒

HTTPS et Headers de Sécurité

🔐 HTTPS - HyperText Transfer Protocol Secure

📡 Principe : Extension sécurisée du protocole HTTP utilisant TLS/SSL pour chiffrer les communications entre client et serveur

❌ HTTP (Port 80)

  • 🔓 Données en clair
  • 👀 Espionnage possible
  • ✂️ Modification des données
  • 🚫 Bloqué par navigateurs modernes

✅ HTTPS (Port 443)

  • 🔒 Chiffrement TLS/SSL
  • 🛡️ Protection contre espionnage
  • 🔐 Intégrité des données
  • 🏆 Requis par navigateurs/SEO

Headers de Sécurité Essentiels

// EventListener pour headers automatiques
public function onKernelResponse(ResponseEvent $event): void
{
    $response = $event->getResponse();
    
    // Force HTTPS pour les futures requêtes
    $response->headers->set('Strict-Transport-Security', 
        'max-age=31536000; includeSubDomains; preload');
    
    // Politique de sécurité du contenu
    $response->headers->set('Content-Security-Policy', 
        "default-src 'self'; script-src 'self'");
    
    // Protection contre le clickjacking
    $response->headers->set('X-Frame-Options', 'DENY');
    
    // Empêcher le sniffing MIME
    $response->headers->set('X-Content-Type-Options', 'nosniff');
    
    // Référer policy
    $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
}

Configuration des Cookies Sécurisés

# config/packages/framework.yaml
framework:
    session:
        cookie_secure: auto
        cookie_httponly: true
        cookie_samesite: lax
        cookie_lifetime: 3600
🛡️ Headers de Sécurité
  • HSTS: Force HTTPS
  • CSP: Contrôle les ressources autorisées
  • X-Frame-Options: Empêche le clickjacking
  • X-Content-Type-Options: Évite le MIME sniffing
10/13
🏆

OWASP Top 10 - 2021

🎯 OWASP : Open Web Application Security Project - Le référentiel mondial des vulnérabilités web les plus critiques
1
Broken Access Control

Contrôle d'accès défaillant - Accès non autorisé aux données/fonctions

⚠️ 94% des applications testées
2
Cryptographic Failures

Défaillances cryptographiques - Données sensibles non protégées

⚠️ Exposition de données personnelles
3
Injection

Injections SQL, NoSQL, LDAP, OS... - Exécution de code malveillant

💉 Classic mais toujours présent
4
Insecure Design

Conception non sécurisée - Failles architecturales

🏗️ Nouveau dans le Top 10
5
Security Misconfiguration

Mauvaise configuration - Permissions par défaut, erreurs exposées

⚙️ 90% des applications
6
Vulnerable Components

Composants vulnérables - Librairies non mises à jour

📦 Dépendances tierces
7
Identification & Auth Failures

Défaillances d'identification et d'authentification

🔐 Sessions, mots de passe
8
Software & Data Integrity

Défaillances d'intégrité des logiciels et données

🔒 Nouveau dans le Top 10
9
Logging & Monitoring

Défaillances de logging et de monitoring

📊 Détection des attaques
10
Server-Side Request Forgery

SSRF - Falsification de requêtes côté serveur

🌐 Nouveau dans le Top 10
🛡️ Protection Symfony
Access Control : Security Bundle, Voters, Firewalls
Cryptographie : Password hashing, Encryption components
Injection : Doctrine ORM, Twig auto-escaping, Validation
Configuration : Sécurité par défaut, Environment variables
💡 Points clés : Sécurité dès la conception • Mise à jour régulière • Tests de sécurité • Formation équipes
11/13
✅

Checklist de Sécurité Interactive

Progression : 0/12 éléments consultés

🔐 Authentification & Autorisation

  • 🔒 Utiliser le système de sécurité Symfony ▶
    💡 Configuration du Security Bundle
    Configuration complète du système de sécurité
    # config/packages/security.yaml
    security:
        providers:
            app_user_provider:
                entity:
                    class: App\Entity\User
                    property: email
        firewalls:
            main:
                lazy: true
                provider: app_user_provider
                form_login:
                    login_path: app_login
                    check_path: app_login
  • 🔐 Hasher les mots de passe avec Argon2id ▶
    💡 Hashage sécurisé des mots de passe
    Utilisation du hasher Symfony
    public function register(UserPasswordHasherInterface $hasher): Response
    {
        $user = new User();
        $plainPassword = $request->request->get('password');
        
        $hashedPassword = $hasher->hashPassword($user, $plainPassword);
        $user->setPassword($hashedPassword);
    }
  • ✅ Valider TOUTES les entrées utilisateur ▶
    💡 Validation complète des entrées
    Utilisation des contraintes Symfony
    class User
    {
        #[Assert\NotBlank(message: 'Email obligatoire')]
        #[Assert\Email(message: 'Email invalide')]
        private string $email;
        
        #[Assert\Length(min: 8, max: 255)]
        #[Assert\Regex(pattern: '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/')]
        private string $password;
    }
  • 🔐 Utiliser HTTPS partout ▶
    💡 Configuration HTTPS complète
    Force HTTPS sur toute l'application
    # config/packages/security.yaml
    security:
        firewalls:
            main:
                requires_channel: https
    
    # Headers de sécurité HTTPS
    $response->headers->set('Strict-Transport-Security', 
        'max-age=31536000; includeSubDomains');

🛡️ Protection des Données

  • 🔒 Échapper les sorties (Twig automatique) ▶
    💡 Échappement automatique avec Twig
    Protection XSS automatique
    {# Échappement automatique (par défaut) #}
    {{ user.name }}  {# Échappé automatiquement #}
    {{ user.bio }}   {# Échappé automatiquement #}
    
    {# Contrôle manuel si nécessaire #}
    {{ user.description|e }}          {# Échappement explicite #}
    {{ user.richContent|raw }}        {# Contenu HTML non échappé #}
  • 🛡️ Configurer les headers de sécurité ▶
    💡 Headers de sécurité complets
    Configuration de tous les headers essentiels
    // EventListener pour headers de sécurité
    public function onKernelResponse(ResponseEvent $event): void
    {
        $response = $event->getResponse();
        
        // Content Security Policy
        $response->headers->set('Content-Security-Policy', 
            "default-src 'self'; script-src 'self' 'unsafe-inline'");
        
        // Protection XSS
        $response->headers->set('X-XSS-Protection', '1; mode=block');
        
        // Empêcher le sniffing MIME
        $response->headers->set('X-Content-Type-Options', 'nosniff');
        
        // Empêcher l'embedding
        $response->headers->set('X-Frame-Options', 'DENY');
    }
  • 📝 Logger les tentatives d'authentification ▶
    💡 Logging des événements de sécurité
    Surveillance des tentatives de connexion
    // EventListener pour surveiller l'authentification
    public function onAuthenticationSuccess(AuthenticationEvent $event): void
    {
        $user = $event->getAuthenticationToken()->getUser();
        $this->logger->info('Connexion réussie', [
            'user' => $user->getUserIdentifier(),
            'ip' => $event->getRequest()->getClientIp(),
            'user_agent' => $event->getRequest()->headers->get('User-Agent')
        ]);
    }
  • ⏱️ Implémenter un rate limiting ▶
    💡 Limitation du taux de requêtes
    Protection contre les attaques par force brute
    // Installation: composer require symfony/rate-limiter
    public function login(Request $request, RateLimiterFactory $loginLimiter): Response
    {
        $limiter = $loginLimiter->create($request->getClientIp());
        
        if (!$limiter->consume(1)->isAccepted()) {
            throw new TooManyRequestsHttpException(
                'Trop de tentatives de connexion. Réessayez plus tard.'
            );
        }
        
        // Logique de connexion...
    }
⚠️ Rappel : La sécurité est un processus continu, pas un état final ! Restez informé des nouvelles vulnérabilités et mettez régulièrement à jour vos dépendances.
12/13
🎯

Quiz de Validation des Connaissances

📝 Instructions : Cliquez sur chaque question pour révéler la réponse et l'explication
1
Qu'est-ce que l'attaque XSS et comment Symfony la prévient-elle ?
▼
✅ Réponse : XSS (Cross-Site Scripting) est l'injection de code JavaScript malveillant dans une page web. Symfony prévient automatiquement les attaques XSS grâce à :
  • • L'échappement automatique des variables dans Twig
  • • Les filtres d'échappement (|e, |escape)
  • • La possibilité de configurer CSP (Content Security Policy)
2
Pourquoi ne faut-il jamais utiliser MD5 ou SHA1 pour hasher les mots de passe ?
▼
✅ Réponse : MD5 et SHA1 sont obsolètes car :
  • • Ils sont trop rapides (vulnérables aux attaques par force brute)
  • • Des collisions ont été trouvées
  • • Pas de salt intégré
  • • Symfony recommande Argon2id qui est lent par design et résistant aux attaques
3
Comment Doctrine protège-t-il contre les injections SQL ?
▼
✅ Réponse : Doctrine utilise plusieurs mécanismes :
  • • Requêtes préparées (prepared statements) par défaut
  • • Paramètres liés (setParameter) qui séparent données et code SQL
  • • QueryBuilder qui construit des requêtes sécurisées
  • • Échappement automatique des valeurs
4
Qu'est-ce qu'une attaque CSRF et comment s'en protéger ?
▼
✅ Réponse : CSRF (Cross-Site Request Forgery) force un utilisateur authentifié à exécuter des actions non désirées. Protection Symfony :
  • • Génération automatique de tokens CSRF uniques
  • • Validation des tokens à chaque soumission
  • • Configuration dans FormType avec csrf_protection: true
  • • Token inclus automatiquement dans {{ form_end() }}
5
Quels sont les headers de sécurité essentiels à configurer ?
▼
✅ Réponse : Les headers de sécurité indispensables :
  • • Strict-Transport-Security : Force HTTPS
  • • Content-Security-Policy : Contrôle les ressources autorisées
  • • X-Frame-Options : Empêche le clickjacking
  • • X-Content-Type-Options : Évite le MIME sniffing
  • • Referrer-Policy : Contrôle les informations de référence
6
Pourquoi la validation côté client n'est-elle pas suffisante ?
▼
✅ Réponse : La validation côté client peut être contournée car :
  • • JavaScript peut être désactivé
  • • Les outils de développement permettent de modifier le HTML/JS
  • • Les requêtes peuvent être envoyées directement (curl, Postman)
  • • Règle d'or : Ne JAMAIS faire confiance aux données client
  • • Toujours valider côté serveur avec Symfony Validator

Score : 0 / 6

13/13
🎓

Conclusion & Bonnes Pratiques

🔄 Récapitulatif de la Formation

🛡️
Fondamentaux XSS
Échappement Twig, validation, CSP
💉
Injection SQL
Requêtes préparées, Doctrine ORM
🔄
Protection CSRF
Tokens, validation automatique
🔐
Authentification
Hashage, sessions, 2FA
🏆
OWASP Top 10
Vulnérabilités critiques à connaître
✅
Checklist Sécurité
Bonnes pratiques à appliquer

🎯 Points Clés à Retenir

🔒 Sécurité by Design
Intégrer la sécurité dès la conception, pas après coup
🛡️ Défense en Profondeur
Multiplier les couches de protection pour une sécurité robuste
📝 Formation Continue
Rester à jour sur les nouvelles menaces et techniques
🔍 Tests Réguliers
Auditer et tester continuellement la sécurité

🚀 Plan d'Action Concret

1
Audit Immédiat
Vérifier la checklist sur vos projets
2
Prioriser les Risques
Focus sur OWASP Top 3 en premier
3
Implémentation
Déployer les protections Symfony
4
Monitoring
Surveiller et maintenir en continu
🎉 Félicitations ! Vous avez terminé cette formation
La sécurité n'est pas une destination, mais un voyage continu. Continuez à apprendre, pratiquer et rester vigilant !

💬 Questions ? Discussion ? Échangeons !