samedi 5 juillet 2008

CSRF : Sea, Surf and Zend

Note: Si vous n'utilisez pas Zend Framework et que le titre vous a fait peur, ne fuyez pas, je ne parle de ZF qu'à la fin.

Au programme d'aujourd'hui, un peu de prévention ! Comme vous l'avez remarqué, l'été pointe enfin le bout de son nez et il devient difficile de rester concentré devant son écran. On parle souvent d'insolations, de déshydratations, mais n'oubliez pas non plus qu'une seule seconde d'inattention peut mener à une faille de sécurité dans votre code !

C'est pourquoi j'aimerais vous parler des failles de type CSRF. Si cela ne vous évoque rien ou pas grand chose, je vous conseille d'aller vous renseigner sur le sujet, de nombreux articles expliquent le problème.

Comment s'en protéger ? Tout d'abord je vous conseillerais de boire de l'eau (sans aucun additif anisé) lorsque vous codez. Mais ça ne suffit pas ! Les techniques suivantes sont souvent utilisées pour se protéger contre les CSRF :

  • Utiliser la méthode HTTP POST (ou UPDATE ou DELETE) pour toutes les actions qui ne sont pas de la consultation de ressource (insertion, mise à jour ou suppression). D'une part, vous protégera légèrement contre le CSRF, d'autre part ça sera le premier pas vers une architecture RESTful
  • Utiliser des token de sécurité (aussi appelés nonce ou encore bordel pour paranoïaque) Kézako ? En gros lorsqu'un utilisateur affiche un formulaire, on lui génère une clé. Cette clé sera valide pour un certain temps (5 min en général), sera liée uniquement au couple utilisateur/formulaire et devra automatiquement être transmise avec le formulaire pour que celui ci puisse être validé !

En général, cela évite les dégats. Par contre méfiez vous car si vous êtes également vulnérable à une faille de type XSS, ces protections ne changeront absolument rien ! Ce serait aussi efficace que d'installer une porte blindée à la place de la fermeture d'une tente de camping en toile. Un simple cure dent permettrait de désintégrer votre site web.

Donc techniquement, pour se protéger, il faut agir à deux niveaux :

  1. Affichage du formulaire
    • Générer un token valide pour cet utilisateur et ce formulaire [1]
    • Stocker ce token quelque part [2]
    • Ajouter la valeur du token dans un champ caché du formulaire
  2. Validation du formulaire
    • Ne traiter que les données qui ont ete envoyées par POST (ou UPDATE ou DELETE)
    • Récupérer le token valide quelque part [2] et vérifier qu'il n'est pas expiré
    • Comparer ce token à celui qui a été POSTé

Simple, non ? Étudions maintenant deux points :

  1. Le token ne doit pas être prédictible. Je vous propose d'utiliser la formule suivante :

    MD5( identifiant_formulaire . server_secret . assaisonnement)
    identifiant_formulaire est une chaîne représentant le formulaire concerné
    server_secret est une clé connue uniquement par le serveur
    assaisonnement est un nombre aléatoire (entre 1 et beaucoup, par exemple)

  2. Tout est possible, tout est imaginable...
    • Sessions natives PHP
    • Memcache
    • Base de données (MySQL, Postgresql, sqlite, Berkeley DB, Memcachedb)
    • Cookie sécurisé (c'était juste pour les citer mais je vous les déconseille pour cette utilisation. Si vous avez beaucoup de formulaires ça va créer beaucoup de données qui transiteront entre l'utilisateur et le serveur à chaque requête)
    ... à condition bien sur que personne ne puisse altérer les données que vous y avez stocké.

Si sauvegarder les token en session vous convient, le Zend Framework propose une solution qui fait exactement tout ça : Zend_Form_Element_Hash.

Moi je n'aime toujours pas les sessions, donc je vais préférer Memcache pour y stocker les token. Memcache fournit nativement le système d'expiration, c'est rapide, et c'est pratique a utiliser dans un environnement multi-serveur. Note: Il y a un risque que certains token soient perdu, compte tenu du mode de fonctionnement de Memcache. Mais je vais prendre ce risque compte tenu de leur faible durée de vie. De plus, je vais surveiller l'utilisation de mémoire de memcache pour éviter le problème.

J'ai donc créé une classe qui ressemble beaucoup à Zend_Form_Element_Hash, sauf qu'elle implémente un mécanisme de "Storage Adapter" qui permet de stocker les tokens n'importe où. J'ai également créé un Adapter qui permet de stocker les données dans Memcache, mais libre à vous de créer les Adapter de vos rêves (j'étudie actuellement une solution de stockage des token dans un bigorneaux fossilisé, mais c'est pas très scalable).

C'est ici pour télécharger les classes BigOrNot_Form_Element_Token*

Exemple d'utilisation :

// Initialisation du formulaire
$form = new Zend_Form ...

// Initialisation de Memcache
$memcache = new Memcache ...

// Recuperation de l'identifiant de l'utilisateur
$uniqueUserID = Zend_Auth::getIdentity();

// Mise en place du token de protection
$storage = new BigOrNot_Form_Element_Token_Storage_Memcache($memcache, $uniqueUserID);
$tokenElement = new BigOrNot_Form_Element_Token('formulaire_ajout_bigorneaux', array('adapter' => $storage));
$form->addElement($tokenElement);

Enjoy !

Pour conclure, une petite quote d'un papier (Advanced CRSF) de l'Epitech Security Lab :

Tant que le développement web sera fait par des gens non sensibilisés aux plus évidentes règles de sécurité, que des langages à pièges comme PHP seront “maitrisés” entre deux cours de géographie, il va falloir se faire à l’idée de surfer avec netcat et lire ses mails avec gnus.

Une solution ? Faites circuler cet article dans la boîte au lettre de tous vos voisins (sinon votre serveur Web va perdre ses dents et votre système va mourir d'un kill -9 -1)

;-)

5 commentaires:

Ludovic a dit…

Article très très très utile !
Bravo

Mat a dit…

Merci, ça fait plaisir !

Gilles et Isabelle a dit…

C'est un sujet d'actualité pour moi, mais je n'arrive pas à télécharger votre classe (réseau sans firewall ou autre filtre...)

Mat a dit…

Exact merci, je m'en occupe dans le week end.

Mat a dit…

C'est bon tout est en ordre, les archives sont de nouveau disponibles :)