La gestion de la sécurité est un aspect essentiel du développement d’applications. C’est particulièrement vrai pour les applications web qui centralisent une grande quantité de données, qui plus est potentiellement sensibles, et où peuvent se côtoyer des utilisateurs en très grand nombre. Pour aider le développeur à protéger ses applications, les différents frameworks, quel que soit le langage, proposent des outils performants et une mise en œuvre simplifiée. Il reste cependant nécessaire d’en comprendre et d’en maîtriser les mécanismes sous-jacents pour en tirer le meilleur. Dans l’univers PHP, les dernières versions des frameworks les plus répandus, tels que Zend Framework et Symfony, ne sont pas en reste. Nous donnons dans cet article un aperçu des deux principaux outils de sécurisation proposés par Symfony 2.
Dans un premier temps, nous présentons les protections contre les attaques les plus classiques et les aspects de sécurité liés aux échanges de données entre client et serveur. La seconde partie quand à elle aborde la gestion des utilisateurs et des droits, élément primordial d’une application web.
La version de Symfony utilisée ici est la version 2.1, actuellement en « release candidate ». Cependant, tout ce qui est décrit dans le présent article est déjà disponible dans la version 2.0, la version stable actuelle.
Sécuriser les interactions avec l’utilisateur
Les échanges entre l’utilisateur et le serveur web constituent certainement l’aspect principal d’une application web. Mais ces échanges sont également source de risques qu’il est important de connaître et de maîtriser. Il faut être prudent vis à vis du contenu et de l’origine des données reçues ou envoyées par le serveur de façon à éviter les attaques les plus classiques telles que :
- l’injection SQL, qui consiste à saisir, en lieu et place d’une valeur attendue dans un champ de formulaire, des requêtes SQL. Ces dernières permettent d’obtenir des informations, de passer outre certaines autorisations, voire même de prendre la main sur le serveur web.
- les attaques CSRF, qui ciblent des utilisateurs authentifiés d’une application et leur font exécuter des actions et à leur insu.
- les attaques XSS, qui ont pour but d’injecter du code côté client (HTML, Javascript, …) pour, par exemple, faire exécuter à l’utilisateur des actions à son insu, lui voler sa session ou encore tenter de récupérer ses identifiant et mot de passe en le redirigeant automatiquement vers un site d’hameçonnage.
Si l’application est accessible au grand public, il est également important de vérifier qu’il s’agit bien d’un utilisateur humain derrière le navigateur, et non d’une machine envoyant des requêtes automatisées.
Des sécurités doivent donc être mises en place à différents niveaux de l’application web :
- Tout d’abord au niveau des formulaires, pour s’assurer qu’on reçoit bien la demande d’un utilisateur « consentant ».
- Ensuite au niveau de l’ORM, pour « neutraliser » toutes les données saisies par l’utilisateur avant leur persistance.
- Enfin au niveau des vues, en charge du rendu des pages transmises au navigateur client, qui ne doivent contenir aucun code malicieux.
Les sécurités à mettre en place au niveau du formulaire ont pour but principal la protection contre les attaques de type CSRF. Avec Symfony 2, la sécurisation se situe au niveau du composant « Form ». Elle est d’ailleurs active par défaut, sans que le développeur ait à préciser quoi que ce soit dans le code source ou la configuration de l’application. Le serveur génère un jeton qui est inclus dans le formulaire et qui lui permet de vérifier que ce formulaire a bien été requis par l’utilisateur avant d’en accepter le contenu. Aucun formulaire sans jeton valide n’est accepté.
En cas de besoin, la protection CSRF peut être désactivée de façon ponctuelle dans la classe du formulaire comme présenté ci-dessous ou de façon globale dans la configuration de l’application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
use Symfony\Component\OptionsResolver\OptionsResolverInterface; class MaClasseDeFormulaire extends AbstractType { /* … */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'MonProjet\MonBundle\Entity\MonEntite', // Paramètre permettant de désactiver la protection CSRF 'csrf_protection' => false, /* … */ } /* … */ } |
La protection mise en place au niveau de l’ORM permet de lutter notamment contre les injections SQL. Avec Symfony 2, elle est donc à la charge de « Doctrine », l’ORM utilisé par défaut. Là encore, la protection est active par défaut si on utilise les méthodes adéquates pour générer les requêtes SQL, telles que « setParameter ». Cette méthode, présentée dans l’exemple ci-après, permet de renseigner de façon sécurisée les paramètres d’une requête DQL (Doctrine Query Langage):
1 2 3 |
$query = $em->createQuery( 'SELECT u FROM MonBundle:Utilisateur u WHERE u.nom = :nom' )->setParameter('nom', $nom); |
Quel que soit le contenu de la variable « $nom », il n’y a pas ici de risque d’injection de code. En effet, les caractères spéciaux sont échappés de façon à n’exécuter aucun code frauduleux au moment du requêtage en base. En PHP « nu », la fonction à connaître pour se prémunir contre les injections SQL est la fonction « htmlentities ».
Sécuriser le contenu envoyé au client est également un point important. Cela permet notamment d’éviter des attaques XSS. Avec Symfony 2, c’est « Twig », le moteur de template, qui en a la charge. Pour cela, tous les caractères spéciaux des variables affichées dans la page sont échappés, et encore une fois cette protection est activée par défaut.
Il est cependant parfois nécessaire de désactiver l’échappement, lorsque l’on souhaite afficher du contenu en prenant en compte le markup HTML saisi par l’utilisateur. C’est le cas pour la rédaction d’articles de blog par exemple. Il suffit alors d’ajouter le filtre « raw » au contenu à afficher sans échappement. Ci-dessous, voici les deux exemples du même contenu affiché tout d’abord de façon sécurisée, puis de façon brute, dans une vue utilisant Twig :
1 2 3 4 |
// Affichage du contenu avec échappement des caractères spéciaux. {{ article.contenu }} // Affichage du contenu brut, sans échappement du markup html. {{ article.contenu|raw }} |
Remarque : dans un autre registre concernant les données affichées par l’utilisateur, il est également important de ne pas donner d’informations techniques à un utilisateur en cas d’erreur. Ainsi, si durant le développement de l’application le développeur a besoin de toutes les informations possibles pour identifier l’origine d’une erreur, une fois en production, aucune erreur ne doit remonter jusqu’à l’utilisateur, car il pourrait alors disposer d’informations permettant de mettre à jour certaines failles de l’application. Avec Symfony 2, ce ne sont pas les mêmes « contrôleurs frontaux » qui sont utilisés en développement ou en production. Ainsi, en production, les détails d’une exception ne sont, par défaut, jamais remontés à l’utilisateur qui est redirigé vers une page « erreur 500 » générique, indiquant à l’utilisateur qu’une erreur s’est produite côté serveur, mais sans lui donner d’informations potentiellement sensibles telles que le contenu des variables, la pile d’appel, les requêtes exécutées, … L’interface de développement fournit par contre des informations très utiles pour retrouver la cause d’une exception.
Pour terminer cette partie, parlons un peu de la protection contre les saisies automatisées. Elle peut se faire via l’utilisation de « Captcha ». Il s’agit d’un système très répandu sur internet demandant à l’utilisateur de saisir au clavier les caractères qu’il distingue dans une image. L’image est considérée comme suffisamment complexe pour qu’un processus automatisé ne puisse en extraire les caractères, et suffisamment lisible pour un utilisateur « humain ». C’est utile par exemple si l’application dispose d’un formulaire d’inscription. On souhaite en général éviter que des robots s’inscrivent sur le site pour ensuite y diffuser du contenu non souhaité. Dans Symfony 2, il n’existe pas par défaut de champs de type « Captcha ». Cependant, la création de nouveaux types de champ de formulaire n’est pas difficile. Il est également possible d’utiliser un module (bundle) déjà existant qui apporte cette fonctionnalité.
Sécuriser les accès aux différentes ressources : gestion des utilisateurs et des droits (Authentification, Autorisation, …)
Un élément clef de la sécurisation d’une application web consiste à déterminer si un utilisateur donné a le droit d’accéder à la ressource qu’il demande. Cette sécurisation s’effectue en réalité en deux étapes : une étape d’authentification permettant de vérifier que l’utilisateur est bien celui qu’il prétend, et une étape d’autorisation permettant de vérifier que l’utilisateur a bien le droit d’accéder à la ressource souhaitée. Avec Symfony 2, c’est le même composant qui prend en charge les deux tâches : il s’agit du composant « security ».
Ces différents aspects de sécurisation peuvent s’effectuer simplement, et en grande partie, dans un fichier de configuration. Par défaut, il s’agit du fichier « app/config/security.yml ». L’aspect sécurité dans le fichier de configuration est décomposé en quatre sections principales :
- La section « firewalls » qui permet notamment de déterminer la méthode d’authentification (il peut s’agir de la méthode HTTP classique prise en charge par tous les navigateurs, d’une page d’authentification classique avec saisie de l’identifiant et du mot de passe, d’une authentification par un compte de réseau social, …)
- La section « access_control » qui permet de spécifier les rôles nécessaires pour accéder aux différentes pages de l’application.
- La section « providers » qui permet de préciser comment sont stockés les utilisateurs.
- La section « encoders » qui permet de spécifier la façon de procéder pour protéger le mot de passe des utilisateurs.
Le système dispose de deux utilisateurs définis en dur dans le fichier de configuration,
Voici un exemple basique de fichier de configuration de sécurité :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
security: firewalls: secured_area: pattern: ^/ anonymous: ~ http_basic: realm: "Zone sécurisée d’administration" access_control: - { path: ^/admin, roles: ROLE_ADMIN } providers: in_memory: memory: users: utilisateur: { password: userpass, roles: 'ROLE_USER' } administrateur: { password: adminpass, roles: 'ROLE_ADMIN' } encoders: Symfony\Component\Security\Core\User\User: plaintext |
Remarque : avec Symfony, les fichiers de configuration peuvent prendre plusieurs formats. Le format YML a été ici retenu pour sa concision, mais il est tout à fait possible de configurer son application dans un fichier au format XML, plus répandu.
Cette configuration permet de remplir les fonctions suivantes :
- Le système dispose de deux utilisateurs définis en dur dans le fichier de configuration, avec mot de passe en clair (mauvaise solution pour un déploiement en production, mais pratique pour le développement ou les exemples).
- Les utilisateurs s’authentifient en employant la méthode HTTP classique (petite fenêtre popup gérée par le navigateur demandant identifiant et mot de passe).
- Toute URL respectant le format /admin/* est sécurisée : il faut être authentifié et disposer du droit « ROLE_ADMIN » pour pouvoir y accéder.
- Les autres URL sont accessibles à tous sans authentification.
Si on souhaite par exemple utiliser une page identifiant/mot de passe plutôt que la méthode HTTP classique du navigateur, on modifie donc ce fichier de configuration. Dans la section « firewalls », il faut remplacer « http_basic » par « form_login » et préciser la route vers le formulaire d’identification ainsi que la route permettant de valider l’authentification.
1 2 3 4 5 6 7 8 |
security: firewalls: secured_area: pattern: ^/ anonymous: ~ form_login: login_path: /login check_path: /login_check |
Ceci implique bien sûr d’avoir créé les vues correspondantes, ainsi que les routes.
Bien qu’il soit possible de contrôler la plupart des accès aux ressources depuis le fichier de configuration, certains cas spécifiques nécessitent la vérification des droits directement dans le code source. Ceci est tout à fait possible avec Symfony, que ce soit depuis le « Controller »
($this->get(‘security.context’)->isGranted(‘ROLE_ADMIN’))
ou depuis la vue. Sécuriser un service ou une méthode est également possible. Il peut également être nécessaire de sécuriser certaines actions que peut effectuer un utilisateur sur une instance d’objet donnée (exemple : un simple utilisateur d’un forum peut éditer ses propres messages, mais pas ceux des autres). On peut utiliser dans ce cas les listes de contrôle d’accès (ACL) : http://symfony.com/fr/doc/master/cookbook/security/acl.html
Pour aller plus loin
Les composants présentés ici permettent de répondre aux besoins habituels, mais potentiellement assez complexes et fins, de gestion des droits des applications web tout en offrant une protection efficace et active par défaut contre les attaques classiques. Certaines applications sensibles nécessitent une couche supplémentaire de sécurisation, qui va porter sur le chiffrement des échanges de données entre serveur et client, pour éviter toute interception du « man in the middle », ou encore sur le chiffrement des données secrètes nécessitant un stockage.
Concernant le chiffrement des échanges entre client et serveur, on utilise le protocole « HTTPS », qui consiste à englober « HTTP » dans une liaison « SSL ». C’est ce qu’utilisent par exemple des applications web développées par les banques pour le paiement en ligne. Avec Symfony 2, mettre en place HTTPS n’est pas plus compliqué que d’ajouter une directive dans les contrôles d’accès du fichier de configuration. Ainsi l’exemple ci-dessous permet d’activer le chiffrement SSL pour toute la partie administration :
1 2 3 4 5 6 7 |
security: #... access_control: - { path: ^/admin, roles: ROLE_ADMIN, requires_channel: https } # … |
Concernant le stockage de données secrètes, Symfony 2 ne propose pas de composant par défaut. Cependant, comme pour les captchas évoqués ci-dessus, il existe des bundles pour répondre au besoin. Il n’est par ailleurs pas difficile de créer un service Symfony 2 pour fournir les fonctionnalités OpenSSL de PHP.
Conclusion
Le framework Symfony 2 présente donc de nombreuses possibilités de configuration pour affiner la gestion des utilisateurs et des droits d’une application web. Par ailleurs, ce framework offre par défaut une protection efficace contre les attaques les plus courantes et permet d’activer simplement le chiffrement des échanges entre l’utilisateur et l’application web. Il est intéressant de noter ici que beaucoup des composants de Symfony sont indépendants, et qu’il est donc possible de profiter de certains des composants évoqués ci-dessus sans nécessairement utiliser tout le framework. Symfony 2 est à cet égard beaucoup plus souple que dans sa version 1.
Il est possible de retrouver l’intégralité de cet article dans le numéro 156 du mois de d’Octobre 2012 du magazine « Programmez ».