Sécuriser votre application Symfony : Guide complet contre les vulnérabilités OWASP Top 10

# Introduction La sécurité des applications web est devenue une priorité absolue dans le développement moderne. Symfony, l'un des frameworks PHP les plus robus

Symfony

Sécuriser votre application Symfony : Guide complet contre les vulnérabilités OWASP Top 10

15/01/2024

# Introduction La sécurité des applications web est devenue une priorité absolue dans le développement moderne. Symfony, l'un des frameworks PHP les plus robustes, intègre de nombreux mécanismes de sécurité natifs. Cependant, leur configuration et utilisation correctes restent essentielles pour protéger vos applications contre les menaces du OWASP Top 10. Dans cet article, nous allons explorer les meilleures pratiques pour sécuriser une application Symfony en 2024, avec des exemples concrets et des configurations éprouvées. ## 1. Protection contre les Injections SQL L'injection SQL reste l'une des vulnérabilités les plus critiques. Symfony avec Doctrine ORM offre une excellente protection si utilisé correctement. ### Mauvaise pratique (vulnérable) : ```php // NE JAMAIS FAIRE CECI $query = $entityManager->createQuery( 'SELECT u FROM App\Entity\User u WHERE u.email = ' . $email ); ``` ### Bonne pratique (sécurisée) : ```php // Utilisation de paramètres nommés $query = $entityManager->createQuery( 'SELECT u FROM App\Entity\User u WHERE u.email = :email' )->setParameter('email', $email); // Ou avec QueryBuilder (recommandé) $user = $repository->createQueryBuilder('u') ->where('u.email = :email') ->setParameter('email', $email) ->getQuery() ->getOneOrNullResult(); ``` ## 2. Protection CSRF (Cross-Site Request Forgery) Symfony intègre une protection CSRF native pour les formulaires. ### Configuration dans config/packages/framework.yaml : ```yaml framework: csrf_protection: ~ secret: '%env(APP_SECRET)%' ``` ### Utilisation dans un formulaire : ```php use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; class UserType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('email', TextType::class) ->add('save', SubmitType::class); // Le token CSRF est ajouté automatiquement } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'data_class' => User::class, 'csrf_protection' => true, 'csrf_field_name' => '_token', 'csrf_token_id' => 'user_item', ]); } } ``` ## 3. Gestion sécurisée de l'authentification ### Configuration du Security Bundle (config/packages/security.yaml) : ```yaml security: # Encodage des mots de passe password_hashers: App\Entity\User: algorithm: auto cost: 12 providers: app_user_provider: entity: class: App\Entity\User property: email firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: lazy: true provider: app_user_provider form_login: login_path: app_login check_path: app_login enable_csrf: true logout: path: app_logout target: app_home remember_me: secret: '%kernel.secret%' lifetime: 604800 secure: true httponly: true samesite: 'strict' access_control: - { path: ^/admin, roles: ROLE_ADMIN } - { path: ^/profile, roles: ROLE_USER } ``` ### Implémentation du hachage de mot de passe : ```php use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; class RegistrationController extends AbstractController { #[Route('/register', name: 'app_register')] public function register( Request $request, UserPasswordHasherInterface $passwordHasher, EntityManagerInterface $entityManager ): Response { $user = new User(); $form = $this->createForm(RegistrationFormType::class, $user); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { // Hachage sécurisé du mot de passe $hashedPassword = $passwordHasher->hashPassword( $user, $form->get('plainPassword')->getData() ); $user->setPassword($hashedPassword); $entityManager->persist($user); $entityManager->flush(); return $this->redirectToRoute('app_home'); } return $this->render('registration/register.html.twig', [ 'registrationForm' => $form->createView(), ]); } } ``` ## 4. Protection XSS (Cross-Site Scripting) Twig, le moteur de template de Symfony, échappe automatiquement les variables. ### Échappement automatique : ```twig {# Sécurisé par défaut #}

{{ user.name }}

{# Désactiver l'échappement UNIQUEMENT si nécessaire #}

{{ user.bio|raw }}

{# Utiliser des filtres appropriés #}

{{ user.description|striptags }}

``` ### Configuration des Content Security Policy Headers : ```php // src/EventListener/SecurityHeadersListener.php namespace App\EventListener; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; #[AsEventListener(event: KernelEvents::RESPONSE)] class SecurityHeadersListener { public function onKernelResponse(ResponseEvent $event): void { $resp>getResponse(); // Content Security Policy $response->headers->set( 'Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" ); // Headers de sécurité supplémentaires $response->headers->set('X-Content-Type-Options', 'nosniff'); $response->headers->set('X-Frame-Options', 'SAMEORIGIN'); $response->headers->set('X-XSS-Protection', '1; mode=block'); $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin'); } } ``` ## 5. Validation et Sanitisation des données ### Utilisation des contraintes de validation : ```php use Symfony\Component\Validator\Constraints as Assert; class User { #[Assert\NotBlank(message: 'L\'email ne peut pas être vide')] #[Assert\Email(message: 'L\'email {{ value }} n\'est pas valide')] #[Assert\Length(max: 180)] private ?string $email = null; #[Assert\NotBlank] #[Assert\Length( min: 8, max: 4096, minMessage: 'Le mot de passe doit contenir au moins {{ limit }} caractères' )] #[Assert\Regex( pattern: '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/', message: 'Le mot de passe doit contenir au moins une majuscule, une minuscule et un chiffre' )] private ?string $password = null; #[Assert\NotBlank] #[Assert\Length(min: 2, max: 100)] #[Assert\Regex( pattern: '/^[a-zA-Z\s]+$/', message: 'Le nom ne peut contenir que des lettres' )] private ?string $name = null; } ``` ## 6. Gestion sécurisée des fichiers uploadés ```php use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\Validator\Constraints as Assert; class DocumentUploadController extends AbstractController { #[Route('/upload', name: 'app_upload')] public function upload(Request $request): Response { $uploadedFile = $request->files->get('document'); // Validation du fichier $violati>validator->validate($uploadedFile, [ new Assert\File([ 'maxSize' => '5M', 'mimeTypes' => [ 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', ], 'mimeTypesMessage' => 'Seuls les fichiers PDF et Word sont acceptés', ]) ]); if (count($violations) > 0) { throw new \Exception('Fichier invalide'); } // Génération d'un nom de fichier sécurisé $originalFilename = pathinfo($uploadedFile->getClientOriginalName(), PATHINFO_FILENAME); $safeFilename = transliterator_transliterate( 'Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename ); $newFilename = $safeFilename . '-' . uniqid() . '.' . $uploadedFile->guessExtension(); // Déplacement du fichier $uploadedFile->move( $this->getParameter('upload_directory'), $newFilename ); return new JsonResponse(['filename' => $newFilename]); } } ``` ## 7. Protection contre les attaques de limitation de débit (Rate Limiting) ### Installation du composant : ```bash composer require symfony/rate-limiter ``` ### Configuration (config/packages/rate_limiter.yaml) : ```yaml framework: rate_limiter: login: policy: 'sliding_window' limit: 5 interval: '15 minutes' api: policy: 'token_bucket' limit: 100 rate: { interval: '1 hour', amount: 100 } ``` ### Implémentation : ```php use Symfony\Component\RateLimiter\RateLimiterFactory; class LoginController extends AbstractController { #[Route('/login', name: 'app_login')] public function login( Request $request, RateLimiterFactory $loginLimiter ): Response { // Création d'un limiteur basé sur l'IP $limiter = $loginLimiter->create($request->getClientIp()); // Vérification de la limite if (false === $limiter->consume(1)->isAccepted()) { throw new TooManyRequestsHttpException( 'Trop de tentatives de connexion. Veuillez réessayer dans 15 minutes.' ); } // Logique de connexion... } } ``` ## 8. Sécurisation des variables d'environnement ### Utilisation de secrets chiffrés : ```bash # Générer une clé de chiffrement php bin/console secrets:generate-keys # Définir un secret php bin/console secrets:set DATABASE_PASSWORD # Lister les secrets php bin/console secrets:list ``` ### Fichier .env pour le développement uniquement : ```env # .env (non commité en production) APP_ENV=dev APP_SECRET=changeme # .env.local (ignoré par Git) DATABASE_URL="postgresql://user:password@127.0.0.1:5432/dbname" ``` ## 9. Audit de sécurité et dépendances ### Vérification des vulnérabilités : ```bash # Vérifier les dépendances vulnérables composer audit # Avec Symfony CLI symfony security:check # Mise à jour des dépendances composer update --with-all-dependencies ``` ## 10. Logging et Monitoring de sécurité ```php use Psr\Log\LoggerInterface; class SecurityEventSubscriber implements EventSubscriberInterface { public function __construct( private LoggerInterface $securityLogger ) {} public static function getSubscribedEvents(): array { return [ AuthenticationSuccessEvent::class => 'onAuthenticationSuccess', AuthenticationFailureEvent::class => 'onAuthenticationFailure', LoginFailureEvent::class => 'onLoginFailure', ]; } public function onAuthenticationSuccess(AuthenticationSuccessEvent $event): void { $this->securityLogger->info('Connexion réussie', [ 'user' => $event->getAuthenticationToken()->getUserIdentifier(), 'ip' => $event->getRequest()?->getClientIp(), 'user_agent' => $event->getRequest()?->headers->get('User-Agent'), ]); } public function onAuthenticationFailure(AuthenticationFailureEvent $event): void { $this->securityLogger->warning('Échec d\'authentification', [ 'exception' => $event->getAuthenticationException()->getMessage(), 'ip' => $event->getRequest()?->getClientIp(), ]); } } ``` ## Checklist de sécurité Symfony - [ ] Activer HTTPS en production (HSTS) - [ ] Configurer les headers de sécurité (CSP, X-Frame-Options, etc.) - [ ] Utiliser des paramètres préparés pour toutes les requêtes SQL - [ ] Activer la protection CSRF sur tous les formulaires - [ ] Hacher les mots de passe avec des algorithmes modernes (bcrypt, argon2) - [ ] Valider et sanitiser toutes les entrées utilisateur - [ ] Implémenter le rate limiting sur les endpoints sensibles - [ ] Sécuriser les uploads de fichiers (validation MIME type, taille) - [ ] Utiliser les secrets Symfony pour les données sensibles - [ ] Configurer correctement les cookies (Secure, HttpOnly, SameSite) - [ ] Mettre en place des logs de sécurité - [ ] Effectuer des audits réguliers des dépendances - [ ] Implémenter l'authentification à deux facteurs pour les comptes sensibles - [ ] Définir des politiques de contrôle d'accès strictes - [ ] Configurer les CORS correctement si API ## Conclusion La sécurité d'une application Symfony nécessite une approche multi-couches. Bien que le framework offre d'excellents outils natifs, leur configuration correcte et leur utilisation appropriée restent la responsabilité du développeur. En suivant ces bonnes pratiques et en restant informé des dernières vulnérabilités (OWASP Top 10, CVE), vous pouvez considérablement renforcer la posture de sécurité de vos applications. N'oubliez pas : la sécurité n'est pas un état final mais un processus continu d'amélioration et de vigilance. ## Ressources supplémentaires - [Documentation officielle Symfony Security](https://symfony.com/doc/current/security.html) - [OWASP Top 10](https://owasp.org/www-project-top-ten/) - [Symfony Security Advisories](https://symfony.com/security) - [Guide de sécurité PHP](https://www.php.net/manual/fr/security.php)