Les attaques CSRF – Cross-Site Request Forgery – sont un vecteur d’attaque puissant et souvent ignoré. Symfony est livré avec les outils pour s’en protéger, mais une approche active est nécessaire pour être totalement protégé. Dans cet article, nous allons présenter cette classe d’attaque, avec une explication et des recommendations appliquées à des cas courants Symfony.
CSRF, qu’est-ce que c’est?
Le mécanisme de base d’une attaque CSRF est très simple: il s’agit de faire envoyer à un utilisateur une requête vers un site, à son insu, pour y déclencher une action. Un exemple très précis va nous aider à y avoir plus clair: imaginons un site www.example.com, qui a un backend d’administration, où GET
/user/delete/2
permet de supprimer l’utilisateur 2. Un attaquant va envoyer un e-mail à un administrateur de example.com, qui contient cette image:
Notre utilisateur, après s’être connecté au backend d’example.com, va lire ses e-mails sur gmail.com. Lorsqu’il va afficher cet e-mail dans le client web, son navigateur va envoyer la requête pour supprimer un utilisateur. Le navigateur va envoyer un cookie d’authentification valide avec la requête, l’user #2 sera donc supprimé!
Cet exemple est je pense effrayant de simplicité.
On peut faire quelques observations et remarques pour bien comprendre la portée du problème:
- Les
<img>
ne sont pas les seuls vecteurs d’attaques, beaucoup de balises peuvent être détournées, ou bien une requête Ajax… - De même, tout ce qui contient du HTML ou du Javascript exécuté dans le contexte du navigateur peut permettre une attaque (page web, e-mails, extensions du navigateur…).
- Dans notre cas, il y a violation importante des bonnes pratiques HTTP,
GET
permet de supprimer un utilisateur. C’est pourtant une pratique très courante, quasi systématique avec les admins generators (Sonata, EasyAdmin…). - Tous les types de requêtes (
GET
, mais aussiPOST
,DELETE
) sont vulnérables, puisqu’une attaque peut être lancée via une requête Ajax. - Cette attaque peut être facilement combinée avec une ingénierie sociale, par exemple pour amener l’utilisateur à visiter une page du site en le redirigeant depuis un site pirate… Les possibilité sont infinies.
- La présence d’une fonctionnalité « se souvenir de moi » ou la longue durée de vie du cookie d’authentification facilite l’attaque, mais leur absence ne permet pas de supprimer tout risque.
- HTTPS n’est pas une protection contre ce genre d’attaques.
Cette présentation n’est qu’une introduction, pour une explication exhaustive avec de nombreux exemples, je vous renvoie à la fiche de l’OWASP.
La protection CSRF des formulaires Symfony
Symfony propose depuis le départ un outil permettant de protéger les formulaires, les fameux CsrfToken
. Concrètement, il s’agit d’ajouter à la fin de chaque formulaire un token aléatoire, aussi stocké dans la session utilisateur, qui sera validé lorsque l’utilisateur submit le formulaire. Ce token n’est ni connu ni devinable par un attaquant, il sera donc incapable de fabriquer une fausse requête.
Sous conditions que le mécanisme soit bien implémenté, cryptographiquement, c’est une mesure efficace, notre formulaire est protégé. Cela est activé par défaut sur tous les formulaires Symfony.
L’option SameSite à la rescousse
Les formulaires Symfony sont donc protégés. Mais notre application a probablement d’autres URL qui permettent de modifier du contenu comme le GET
/user/delete/2
de notre exemple précédent.
Face aux nombreuses possibilités d’attaque, il a été proposé de rajouter un attribut SameSite
aux cookies. Concrètement, il s’agit d’interdire au navigateur de joindre le cookie à une requête initiée depuis un autre contexte, un autre site typiquement. C’est une mesure efficace, mais qui peut être contraignante (« casse » des liens…). Aussi il est possible d’activer un mode strict
ou bien lax
, une explication complète est ici.
La fonctionnalité est supportée par tous les navigateurs récents, et par Symfony depuis la version 3.2 (juillet 2016). La mise en place de cet attribut a été grandement simplifié depuis Symfony 4.2. Vos cookies devraient être configurés sur lax
par défaut si vous avez utilisé Symfony Flex.
Toute attaque sur notre exemple précédent ne sera bloquée que si SameSite
est réglé sur strict
, ce qui peut impacter l’application. Par exemple, les liens directs vers l’administration depuis d’autres sites demanderont à se reconnecter (ou bien à naviguer sur une page intermédiaire avant).
Une autre stratégie
À défaut de pouvoir mettre SameSite
sur strict
, parce que cela gêne trop l’utilisateur ou que l’on utilise une ancienne version de Symfony, il faut chercher une autre stratégie.
Pour les URLs les plus à risque, il est possible d’implémenter soi-même une protection en ré-utilisant le CsrfTokenGenerator
fourni par Symfony. On ajoute un token dans le lien, et le valide dans l’action du contrôleur. Un attaquant ne sera plus capable ainsi de deviner quelle URL appeler pour supprimer un utilisateur.
Voici un exemple de code, commenté:
Bilan
Cet article vous aura j’espère permis de réaliser comment une attaque CSRF peut être réalisée, et quelles sont les pages de votre application à risque. Nous avons vu comment nous protéger de différentes manières.
Si le sujet de la sécurité vous intéresse, nous proposons des formations Symfony, notamment sur la sécurité. Des sessions auront prochainement lieu à Lausanne en Suisse, n’hésitez pas à m’écrire à formation@netinfluence.ch si vous êtes intéressé sur une autre ville ou pour une formation dans votre entreprise 🙂