Aller au contenu principal
Retour au blog

Sécuriser votre app Symfony en 30 minutes : checklist OWASP

Flavien Métivier18 novembre 20256 min

La sécurité n'est pas un luxe réservé aux grandes équipes. Avec Symfony, 80 % de la protection est déjà intégrée au framework. Encore faut-il l'activer correctement. Voici une checklist rapide basée sur l'OWASP Top 10 — chaque point est vérifiable en quelques minutes.

A01 — Broken Access Control : les Voters

Symfony protège les routes avec access_control dans security.yaml. Mais le contrôle d'accès granulaire — « cet utilisateur peut-il modifier cette ressource ? » — nécessite des Voters.

<?php

declare(strict_types=1);

// Dans le controller : une seule ligne
#[Route('/{id}', methods: ['DELETE'])]
public function delete(User $user): Response
{
    $this->denyAccessUnlessGranted('USER_DELETE', $user);
    // Si le Voter refuse, Symfony renvoie automatiquement un 403
}

// Le Voter centralise la logique d'accès
final class UserVoter extends Voter
{
    protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
    {
        $currentUser = $token->getUser();
        return match ($attribute) {
            'USER_DELETE' => $this->security->isGranted('ROLE_ADMIN')
                && !$subject->id()->equals($currentUser->id()),
            default => false,
        };
    }
}

A02 — Cryptographic Failures : password hashing

Symfony gère le hashing automatiquement via le composant Security. Il suffit de configurer l'algorithme :

# config/packages/security.yaml
security:
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
            algorithm: auto    # Utilise bcrypt ou argon2 selon la plateforme
            cost: 15           # Minimum recommandé (bcrypt)

    # Forcer HTTPS partout
    access_control:
        - { path: ^/, roles: PUBLIC_ACCESS, requires_channel: https }

A03 — Injection SQL : Doctrine vous protège (si vous l'utilisez correctement)

Doctrine paramètre automatiquement les requêtes via le QueryBuilder. La seule erreur à éviter : la concaténation de chaînes dans les requêtes DQL ou SQL brutes.

<?php

// INTERDIT — injection SQL possible
$dql = "SELECT u FROM User u WHERE u.email = '$email'";

// OBLIGATOIRE — paramètre sécurisé
$qb = $this->createQueryBuilder('u')
    ->where('u.email = :email')
    ->setParameter('email', $email->toString())
    ->getQuery()
    ->getOneOrNullResult();

A04 — Insecure Design : validation des inputs

Chaque donnée entrante doit être validée avec le composant Validator de Symfony. Utilisez des DTOs avec des contraintes PHP 8 Attributes :

Besoin d'un expert Symfony ?

Réserver un appel
<?php

declare(strict_types=1);

use Symfony\Component\Validator\Constraints as Assert;

final readonly class CreateUserDTO
{
    public function __construct(
        #[Assert\NotBlank]
        #[Assert\Email(mode: 'strict')]
        #[Assert\Length(max: 180)]
        public string $email,

        #[Assert\NotBlank]
        #[Assert\Length(min: 12, max: 4096)]
        #[Assert\NotCompromisedPassword]
        #[Assert\PasswordStrength(minScore: Assert\PasswordStrength::STRENGTH_STRONG)]
        public string $password,
    ) {}
}

A05 — Security Misconfiguration : headers et sessions

Ajoutez un event listener qui injecte les headers de sécurité sur chaque réponse :

<?php

declare(strict_types=1);

use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpKernel\Event\ResponseEvent;

#[AsEventListener(event: 'kernel.response')]
final readonly class SecurityHeadersListener
{
    public function __invoke(ResponseEvent $event): void
    {
        if (!$event->isMainRequest()) { return; }

        $headers = $event->getResponse()->headers;
        $headers->set('X-Frame-Options', 'DENY');
        $headers->set('X-Content-Type-Options', 'nosniff');
        $headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
        $headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
        $headers->set('Content-Security-Policy', "default-src 'self'");
    }
}

A07 — Authentication Failures : rate limiting

Symfony intègre un composant RateLimiter depuis la version 5.2. Configurez-le pour protéger vos endpoints sensibles :

# config/packages/framework.yaml
framework:
    rate_limiter:
        login:
            policy: 'sliding_window'
            limit: 5
            interval: '15 minutes'
        api:
            policy: 'token_bucket'
            limit: 1000
            rate: { interval: '1 hour' }

La checklist complète en 30 minutes

  • Vérifier que tous les endpoints sensibles ont un Voter (5 min)
  • Vérifier security.yaml : password_hashers, HTTPS, CSRF (3 min)
  • Rechercher les requêtes SQL/DQL sans setParameter (5 min)
  • Vérifier que chaque DTO entrant a des contraintes Assert (5 min)
  • Ajouter le SecurityHeadersListener si absent (3 min)
  • Configurer le rate limiting sur login et API (3 min)
  • Lancer composer audit pour les CVE (1 min)
  • Vérifier les sessions : cookie_secure, cookie_httponly, cookie_samesite (2 min)
  • Vérifier le logging sécurité dans Monolog (3 min)

Cette checklist couvre les fondamentaux. Pour un audit de sécurité complet avec scoring et roadmap chiffrée, notre service Bear Scan analyse votre codebase sur les 10 risques OWASP avec des recommandations actionnables. En savoir plus.

Cet article vous a plu ? Partagez-le !

Besoin d'un expert Symfony ?

20 ans d'expérience sur l'écosystème PHP/Symfony.