{{ user.name }}
{# Pour du contenu HTML validé - À utiliser avec précaution #} {{ content|raw }} {# Échappement JavaScript #} ``` ### Utilisation de HTML Sanitizer : ```php // composer require symfony/html-sanitizer use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface; class ArticleController extends AbstractController { public function create( Request $request, HtmlSanitizerInterface $htmlSanitizer ): Response { $c>request->get('content'); $sanitizedC>sanitize($content); // Utilisation du contenu nettoyé } } ``` ## 3. Protection CSRF (Cross-Site Request Forgery) Symfony intègre une protection CSRF native pour les formulaires. ### Configuration : ```yaml # config/packages/framework.yaml framework: csrf_protection: ~ ``` ### Dans les formulaires : ```php use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\TextType; class UserType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('email', TextType::class) ->add('password', PasswordType::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', ]); } } ``` ### Pour les requêtes AJAX : ```php use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; class ApiController extends AbstractController { #[Route('/api/delete/{id}', methods: ['DELETE'])] public function delete( int $id, Request $request, CsrfTokenManagerInterface $csrfTokenManager ): JsonResponse { $token = $request->headers->get('X-CSRF-TOKEN'); if (!$csrfTokenManager->isTokenValid(new CsrfToken('delete_item', $token))) { return $this->json(['error' => 'Invalid CSRF token'], 403); } // Traitement de la suppression } } ``` ## 4. Sécurisation des En-têtes HTTP Les en-têtes de sécurité sont essentiels pour protéger contre diverses attaques. ### Configuration dans Symfony : ```yaml # config/packages/framework.yaml framework: # Configuration des en-têtes de sécurité http_method_override: false ``` ### Création d'un EventSubscriber pour les en-têtes : ```php // src/EventSubscriber/SecurityHeadersSubscriber.php namespace App\EventSubscriber; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; class SecurityHeadersSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents(): array { return [ KernelEvents::RESP> 'onKernelResponse', ]; } public function onKernelResponse(ResponseEvent $event): void { if (!$event->isMainRequest()) { return; } $resp>getResponse(); // Protection contre le clickjacking $response->headers->set('X-Frame-Options', 'SAMEORIGIN'); // Content Security Policy $response->headers->set( 'Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'" ); // Protection XSS $response->headers->set('X-Content-Type-Options', 'nosniff'); $response->headers->set('X-XSS-Protection', '1; mode=block'); // HSTS pour forcer HTTPS $response->headers->set( 'Strict-Transport-Security', 'max-age=31536000; includeSubDomains' ); // Referrer Policy $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin'); // Permissions Policy $response->headers->set( 'Permissions-Policy', 'geolocation=(), microph camera=()' ); } } ``` ## 5. Gestion sécurisée des mots de passe Symfony 6+ utilise automatiquement l'algorithme bcrypt ou sodium pour hasher les mots de passe. ### Configuration : ```yaml # config/packages/security.yaml security: password_hashers: Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: algorithm: auto cost: 12 # Pour bcrypt time_cost: 3 # Pour argon2 memory_cost: 10 # Pour argon2 ``` ### Hashage et vérification : ```php use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; class RegistrationController extends AbstractController { public function register( Request $request, UserPasswordHasherInterface $passwordHasher ): Response { $user = new User(); $form = $this->createForm(RegistrationFormType::class, $user); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { // Hash du mot de passe $hashedPassword = $passwordHasher->hashPassword( $user, $form->get('plainPassword')->getData() ); $user->setPassword($hashedPassword); // Politique de mot de passe fort // À implémenter avec un validateur personnalisé } } } ``` ### Validateur de mot de passe fort : ```php // src/Validator/StrongPassword.php namespace App\Validator; use Symfony\Component\Validator\Constraint; #[\Attribute] class StrongPassword extends Constraint { public string $message = 'Le mot de passe doit contenir au moins 12 caractères, une majuscule, une minuscule, un chiffre et un caractère spécial.'; } // src/Validator/StrongPasswordValidator.php namespace App\Validator; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; class StrongPasswordValidator extends ConstraintValidator { public function validate($value, Constraint $constraint): void { if (null === $value || '' === $value) { return; } $hasMinLength = strlen($value) >= 12; $hasUpperCase = preg_match('/[A-Z]/', $value); $hasLowerCase = preg_match('/[a-z]/', $value); $hasNumber = preg_match('/\d/', $value); $hasSpecialChar = preg_match('/[^A-Za-z0-9]/', $value); if (!($hasMinLength && $hasUpperCase && $hasLowerCase && $hasNumber && $hasSpecialChar)) { $this->context->buildViolation($constraint->message) ->addViolation(); } } } ``` ## 6. Protection contre les attaques par force brute ### Installation et configuration du Rate Limiter : ```bash composer require symfony/rate-limiter ``` ```yaml # config/packages/rate_limiter.yaml framework: rate_limiter: login: policy: 'sliding_window' limit: 5 interval: '15 minutes' api: policy: 'token_bucket' limit: 100 rate: { interval: '1 hour' } ``` ### Utilisation dans un controller : ```php use Symfony\Component\RateLimiter\RateLimiterFactory; class SecurityController extends AbstractController { #[Route('/login', name: 'app_login')] public function login( Request $request, RateLimiterFactory $loginLimiter ): Response { $limiter = $loginLimiter->create($request->getClientIp()); if (false === $limiter->consume(1)->isAccepted()) { return $this->json([ 'error' => 'Trop de tentatives de connexion. Réessayez plus tard.' ], 429); } // Logique de connexion } } ``` ## 7. Validation et nettoyage des entrées utilisateur ### Validation avec contraintes Symfony : ```php // src/Entity/User.php use Symfony\Component\Validator\Constraints as Assert; class User { #[Assert\NotBlank] #[Assert\Email( message: 'L\'email {{ value }} n\'est pas valide.', mode: 'strict' )] private string $email; #[Assert\NotBlank] #[Assert\Length( min: 2, max: 100, minMessage: 'Le nom doit contenir au moins {{ limit }} caractères', maxMessage: 'Le nom ne peut pas dépasser {{ limit }} caractères' )] #[Assert\Regex( pattern: '/^[a-zA-Z\s-]+$/', message: 'Le nom ne peut contenir que des lettres, espaces et tirets' )] private string $name; #[Assert\NotBlank] #[Assert\Url( protocols: ['https'], message: 'L\'URL doit être valide et utiliser HTTPS' )] private ?string $website = null; } ``` ## 8. Sécurisation des uploads de fichiers ```php // src/Service/FileUploader.php namespace App\Service; use Symfony\Component\HttpFoundation\File\Exception\FileException; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\String\Slugger\SluggerInterface; class FileUploader { private const ALLOWED_MIME_TYPES = [ 'image/jpeg', 'image/png', 'image/gif', 'application/pdf', ]; private const MAX_FILE_SIZE = 5242880; // 5 MB public function __construct( private string $targetDirectory, private SluggerInterface $slugger ) {} public function upload(UploadedFile $file): string { // Vérification du type MIME if (!in_array($file->getMimeType(), self::ALLOWED_MIME_TYPES)) { throw new FileException('Type de fichier non autorisé'); } // Vérification de la taille if ($file->getSize() > self::MAX_FILE_SIZE) { throw new FileException('Fichier trop volumineux'); } // Génération d'un nom sécurisé $originalFilename = pathinfo( $file->getClientOriginalName(), PATHINFO_FILENAME ); $safeFilename = $this->slugger->slug($originalFilename); $fileName = $safeFilename . '-' . uniqid() . '.' . $file->guessExtension(); try { $file->move($this->targetDirectory, $fileName); } catch (FileException $e) { throw new FileException('Erreur lors de l\'upload du fichier'); } return $fileName; } } ``` ## 9. Logging et monitoring de sécurité ```yaml # config/packages/monolog.yaml monolog: channels: ['security'] handlers: security: type: stream path: '%kernel.logs_dir%/security.log' level: info channels: ['security'] failed_login: type: stream path: '%kernel.logs_dir%/failed_login.log' level: info ``` ```php // src/EventSubscriber/SecurityEventsSubscriber.php namespace App\EventSubscriber; use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Security\Http\Event\LoginFailureEvent; use Symfony\Component\Security\Http\Event\LoginSuccessEvent; class SecurityEventsSubscriber implements EventSubscriberInterface { public function __construct(private LoggerInterface $securityLogger) {} public static function getSubscribedEvents(): array { return [ LoginSuccessEvent::class => 'onLoginSuccess', LoginFailureEvent::class => 'onLoginFailure', ]; } public function onLoginSuccess(LoginSuccessEvent $event): void { $user = $event->getUser(); $request = $event->getRequest(); $this->securityLogger->info('Connexion réussie', [ 'username' => $user->getUserIdentifier(), 'ip' => $request->getClientIp(), 'user_agent' => $request->headers->get('User-Agent'), ]); } public function onLoginFailure(LoginFailureEvent $event): void { $request = $event->getRequest(); $this->securityLogger->warning('Échec de connexion', [ 'username' => $request->request->get('_username'), 'ip' => $request->getClientIp(), 'user_agent' => $request->headers->get('User-Agent'), 'exception' => $event->getException()->getMessage(), ]); } } ``` ## 10. Configuration de sécurité complète ```yaml # 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: 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 default_target_path: app_dashboard logout: path: app_logout target: app_home invalidate_session: true csrf_token_generator: security.csrf.token_manager remember_me: secret: '%kernel.secret%' lifetime: 604800 # 1 semaine path: / always_remember_me: false secure: true httponly: true samesite: strict login_throttling: max_attempts: 5 interval: '15 minutes' access_control: - { path: ^/admin, roles: ROLE_ADMIN } - { path: ^/profile, roles: ROLE_USER } - { path: ^/login, roles: PUBLIC_ACCESS } role_hierarchy: ROLE_ADMIN: ROLE_USER ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] ``` ## 11. Variables d'environnement et secrets ```bash # Génération de secrets Symfony php bin/console secrets:set DATABASE_PASSWORD php bin/console secrets:set API_KEY ``` ```yaml # config/packages/doctrine.yaml doctrine: dbal: url: '%env(resolve:DATABASE_URL)%' # Ne jamais stocker les credentials en clair ``` ```php # .env (pour le développement) APP_ENV=dev APP_SECRET=changeme_in_production DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name" # .env.local (ignoré par git) # Stocker ici les vraies valeurs de développement ``` ## 12. Checklist de sécurité pour la production ### Configuration .env.prod : ```bash APP_ENV=prod APP_DEBUG=0 ``` ### Commandes de déploiement sécurisé : ```bash # Vider le cache php bin/console cache:clear --env=prod # Optimiser l'autoloader composer dump-autoload --optimize --no-dev # Vérifier la configuration de sécurité php bin/console debug:config security # Audit de sécurité des dépendances composer audit # Scanner les vulnérabilités symfony security:check ``` ### Configuration serveur web (Nginx) : ```nginx server { listen 443 ssl http2; server_name example.com; # Certificat SSL ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # Headers de sécurité add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; root /var/www/symfony/public; location / { try_files $uri /index.php$is_args$args; } location ~ ^\.php(/|$) { fastcgi_pass unix:/var/run/php/php8.2-fpm.sock; fastcgi_split_path_info ^(.+\.php)(/.*)$; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $realpath_root; internal; } # Bloquer l'accès aux fichiers sensibles location ~ /\. { deny all; } } ``` ## Conclusion La sécurisation d'une application Symfony nécessite une approche multicouche combinant : 1. **Protection des données** : Validation, échappement, sanitization 2. **Authentification robuste** : Mots de passe forts, rate limiting, 2FA 3. **Autorisations strictes** : Contrôle d'accès basé sur les rôles 4. **En-têtes de sécurité** : CSP, HSTS, X-Frame-Options 5. **Monitoring** : Logs de sécurité, alertes 6. **Mises à jour régulières** : Dépendances, framework, serveur Symfony offre d'excellents outils de sécurité natifs, mais leur configuration appropriée et le respect des bonnes pratiques sont essentiels. N'oubliez pas de réaliser des audits de sécurité réguliers et de former votre équipe aux principes de développement sécurisé. ### 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 Checker : https://github.com/fabpot/local-php-security-checker - Guide des en-têtes de sécurité : https://securityheaders.com/ Restez vigilants et sécurisez vos applications !Sécuriser votre application Symfony : Guide complet contre les vulnérabilités OWASP Top 10
# Introduction La sécurité des applications web est devenue un enjeu majeur en 2024. Symfony, bien qu'étant un framework robuste, nécessite une configuration e
Sécuriser votre application Symfony : Guide complet contre les vulnérabilités OWASP Top 10
15/01/2024