lundi 23 juin 2008

Une classe de protection contre le flood

Il est très courant d'être victime de flood pour certaines fonctionnalités proposées par votre site.

Exemples :

Que penseriez vous d'une classe qui permettrait de bloquer (ou plutôt ralentir) les tentatives de flood sur les fonctionnalités sensibles de votre site ? (ex: pas plus d'1 post sur le forum toutes les 30 secondes).

Pour cela, nous allons utiliser memcache. Si vous ne connaissez pas memcache, c'est un système de cache distribué extrèmement rapide. Comme tout système de cache, il offre un mécanisme de stockage, de récupération et d'expiration des données.

Pourquoi memcache ?
C'est un outil pratique aussi bien dans un environnement multiserveur que sur un seul serveur, donc il est accessible à un grand nombre de personnes (désolé ca n'inclu pas les comptes de type "mutualisés" grand public).
Il propose également un mécanisme d'expiration qui correspond parfaitement à nos besoins pour ce système de flood-control.

Principe :
l'extension memcache pour PHP propose une fonction Memcache::add() qui permet d'ajouter une donnee au cache si et seulement si celle-ci n'existe pas déjà.
A chaque action d'un utilisateur que nous voulons limiter, il suffit donc d'ajouter une information dans le cache avec une date d'expiration correspondant au temps d'attente necessaire avant la prochaine action. Si la fonction add() renvoie une erreur, c'est qu'une action a déjà été effectuée durant cette intervalle de temps.

Scénario type : (flood control sur un forum, 1 post toutes les 30 sec)
11:00:00 : jean-craoude much poste un message sur le forum. Le forum ajoute une entrée au cache qui a pour ID "ForumPost:JeanCraoudeMuch" et pour date d'expiration 30 secondes.
11:00:15 : jean-craoude Much s'endort malencontreusement sur la touche F5 de son clavier. Toutes les nanosecondes, une requête est donc envoyée au serveur pour poster à nouveau le même message. Le forum va donc pour chaque requête tenter de remettre en cache une donnée ayant pour ID "ForumPost:JeanCraoudeMuch", mais celle-ci existant déjà, la fonction Memcache::add() va renvoyer une erreur. Le post sera donc refusé.
11:00:29 : jean-craoude Much se réveille et se regarde dans le mirroir. Voyant un F5 rouge se dessiner sur son front, il se précipite sur le forum pour voir l'etendue de ses dégats. Fort heureusement, tout va bien, il est rassuré. Il se rendort donc consciencieusement, mais cette fois-ci à côté du clavier

Bref, si vous êtes séduit par ce système, voici une petite classe qui vous permettra de le mettre en place très facilement : BigOrNot_FloodControl

Exemple d'utilisation :

/* Initialisation d'un objet memcache */
$cache = new Memcache;
$cache->addServer('127.0.0.1');

/* Initialisation de la classe flood control */
$floodControl = new BigOrNot_FloodControl($cache);

/* ... */

/* Utilisation de la classe pour limiter les posts dans un forum */
$userId = Forum::getUserId();
if ($floodControl->isAllowed('posterUnMessageDansLeForum', $userId, 30))
{
    posterMessage();
}
else
{
    messageErreur();
}

Voila, c'est simple et efficace. Inutile de s'armer d'une base de données pour ce genre de choses.

Notes :

  • Avec cette solution, il n'y a pas la possibilité de faire des restrictions avec une intervalle inférieure à 1 seconde. Pour les amateurs de nanosecondes, cela nécessitera quelques modifications.
  • Je n'ai pas utilisé Zend_Cache car je n'ai pas trouvé de mécanisme équivalent au add() de Memcache. Il y a probablement moyen de mettre en place un système de lock qui y ressemblerait, mais ca ne m'enchantait pas trop... Alors si l'envie vous prend, n'hesitez pas ;) Méfiez vous des appels simultanés à votre système

dimanche 15 juin 2008

Zend_Auth et le cookie masqué

Je vais encore vous parler de cookies, mais cette fois-ci à la sauce Zend_Auth.

Je vous ai présenté dans un précédent article une classe permettant de gérer des cookies sécurisés. Maintenant nous allons voir comment l'utiliser dans un cas très courant : l'authentification avec Zend_Auth.

Rappelons les grandes lignes de Zend_Auth :

Si comme moi vous n'utilisez les sessions que pour stocker l'identité des utilisateurs authentifiés (leur identifiant, un simple nombre entier en général), vous allez peut être trouver que c'est du gachi. Les sessions c'est lourd et ennuyeux à gérer quand l'application en question tourne sur une ferme de serveurs web (qui font meuh meuh en core et en core).

Bref, ne serait-il pas tentant de déléguer cette tâche au client ? Qu'il présente lui même son identité ? C'est risqué vous allez dire... et vous avez raison !
On peut déjà imaginer les pires scénarios :

Serveur> Bien le bonjour ! A qui ai-je l'honneur ?
Client (de type malicieux)> Je suis l'administrateur du site ! Regarde j'ai... ma bonne parole pour te le prouver !
Serveur> Salut chef je t'avais pas reconnu. Voici les clefs de ton royaume.

Techniquement, ca reviendrait a stocker l'user_id dans un cookie et de faire complètement confiance à ce cookie.

En général ce genre de scénarios est très peu apprécié par le public (en particulier celui de votre site). Donc on va tenter d'utiliser quelques ruses cryptographiques pour confier à l'utilisateur le soin de présenter son identité, sans qu'il puisse la modifier : on va la stocker dans un cookie sécurisé grâce à la classe BigOrNot_CookieManager.

J'ai donc écrit une classe qui implémente l'interface Zend_Auth_Storage_Interface pour stocker l'identité des utilisateurs dans un cookie sécurisé.

Vous pouvez la télécharger ici (l'archive contient également la classe BigOrNot_CookieManager).

Voici comment l'utiliser :

$cookieManager = new BigOrNot_CookieManager('SECRET_KEY');
$authStorage = new BigOrNot_Auth_Storage_Cookie($cookieManager);
$auth = Zend_Auth::getInstance();
$auth->setStorage($authStorage);

[...]

Par defaut, le cookie s'appelle "auth" et les paramètres par défaut sont envoyes a setcookie().

Si vous souhaitez modifier ces paramètres, il est possible d'envoyer au constructeur de la classe BigOrNot_Auth_Storage_Cookie un deuxième paramètre : un tableau (ou un objet Zend_Config) de configuration.

Les paramètres de configuration supportés sont : cookieName, cookieExpire, cookiePath, cookieDomain, cookieSecure, cookieHttpOnly. (Les noms sont assez explicites, voir la doc de setcookie() si vous avez des doutes).

Exemple d'utilisation avec configuration :

$cookieManager = new BigOrNot_CookieManager('SECRET_KEY');

$storageConfig = array(
    'cookieName' => 'BigOrNauth',
    'cookieExpire' => (time() + 3600),
    'cookiePath' => '/',
    'cookieDomain' => 'bigornot-fr.blogspot.com'
);

$authStorage = new BigOrNot_Auth_Storage_Cookie($cookieManager, $storageConfig);
$auth = Zend_Auth::getInstance();
$auth->setStorage($authStorage);

L'identité est stockée "serialisée" donc vous pouvez y stocker toute valeur serialisable. Evitez tout de même les gros objets, les cookies trop gros sont vite écoeurants. N'oubliez pas qu'ils sont transmis à chaque requête.

L'identité est également stockée chiffrée, donc vous ne donnez aucune information confidentielle à l'utilisateur en utilisant cette technique.

Pour information :
Comme nous l'avons vu dans le précédent article, pour la methode BigOrNot_CookieManager::setCookie(), il est necessaire d'envoyer en paramètre un nom d'utilisateur (ou n'importe quel identifiant unique).
Dans la classe BigOrNot_Auth_Storage_Cookie, j'envoie un hash md5 de l'identite serialisee.

vendredi 13 juin 2008

Sécurisation des cookies : une implementation en PHP

Note pour les nouveaux arrivants : vous devriez lire avant tout le précedent article : Introduction à la sécurité des cookies.

Donc... où en étions nous ? Il existe un protocole de sécurisation des cookies (voir le papier de Alex X. Liu pour les details) :

Cookie value =
username|expiration time|(data)k|HMAC(username|expiration time|data|SSL session key, k)
Avec
  • username est le nom de l'utilisateur (ou un identifiant unique)
  • expiration time est la data d'expiration du cookie
  • (data)k est le résultat d'une function de chiffrement par bloc (ex: AES) de data avec la clé k.
  • data est la donnée que vous souhaitez stocker dans le cookie
  • k est le résultat de la fonction HMAC(username|expiration time, sk)
  • sk est une clé secrète que seul le serveur connait
  • SSL session key est l'identifiant de la session SSL en cours.

Ce protocole est sécurisé :

  • Il protège vos cookies contre les "replay attacks" : L'identifiant de la session SSL en cours est unique. Si un utilisateur malicieux essaie d'envoyer le cookie d'un autre dans une autre session SSL, le cookie sera invalide.
  • Il protège la confidentialité des données grâce à une fonction de chiffrement symmétrique : les données sont stockées sous une forme chiffrées.
  • Il protège l'intégrité des données grâce à une fonction HMAC
  • Il protège la clé secrète du serveur contre les "volume attack" : le fait d'inclure le nom de l'utilisateur et la date d'expiration dans la clé k permet d'éviter qu'un analyste malicieux s'amuse à créer un grand nombre de cookies sécurités pour essayer de retrouvé la clé secrète stockée sur le serveur.

Cependant, ce protocole n'est pas adapté à tous les usages :

  1. SSL c'est chouette, ça protège contre les replay attacks, mais :
    • Votre application n'est pas forcément accessible en HTTPS
    • La durée de vie du cookie est limitée à celle de la session SSL.
  2. Le chiffrement des données stockées dans le cookie, c'est cool, mais ça ne vous interresse pas forcement ! Si vous n'en avez pas l'utilité, ça va augmenter la taille du cookie pour rien. Par exemple : si les données sont chiffrées avec en AES256, la valeur chiffrée fera au moins 32 octets, même si les données en clair ne font d'1 octet !

Donc il serait cool d'avoir une classe de gestion des cookies qui puisse être configurable, c'est à dire qui offrirait la possibilité de :

  • activer/désactiver le support SSL
  • activer/désactiver les chiffrement des données à stocker
  • choisir l'algorithme de chiffrement

J'ai créé une classe qui offre toutes ces fonctionnalités. Vous pouvez la télécharger ici.

Utilisation :

Initialisation

include('BigOrNot_CookieManager.php');

$secretKey = 'Cei4Wai4ohcoo3daeHooFiek5Nah3Eet';
$manager = new BigOrNot_CookieManager($secretKey);

Tout ce que vous avez à faire, c'est fournir une clé secrète en premier paramètre. Si vous n'êtes pas inspiré, pour générer la clé vous pouvez utiliser cette commande :

pwgen -sy 65

La configuration par défaut est :

  • Chiffrement des données : activé option name : high_confidentiality (bool)
  • Algorithme de chiffrement : MCRYPT_RIJNDAEL_256 (Rijndael 256) option name : mcrypt_algorithm (voir la doc mcrypt)
  • Mode de chiffrement par bloc : MCRYPT_MODE_CBC (CBC) option name : mcrypt_mode (voir la doc mcrypt)
  • Utilisation de l'identifiant de session SSL : disabled option name : enable_ssl (bool)

Si vous souhaitez modifier la configuration, vous pouvez passer en second paramètre du constructeur un tableau d'options. Par exemple, si vous souhaitez désactiver le chiffrement des données, vous pouvez faire cela :

include('BigOrNot_CookieManager.php');

$secretKey = 'Cei4Wai4ohcoo3daeHooFiek5Nah3Eet';
$config = array('high_confidentiality' => false);
$manager = new BigOrNot_CookieManager($secretKey, $config);

Envoyer un cookie sécurisé

$expire = time() + 86400;
$value = $manager->setCookie('cookieName', 'value', 'username');
Regardez le code source si vous souhaitez plus de détails sur les arguments qu'il est possible d'envoyer en paramètre (globalement, vous pouvez envoyer tous les paramètres que setcookie() supporte).

Lire/vérifier la valeur d'un cookie

$value = $manager->getCookieValue('cookieName');
Si le cookie est invalide (utilisation malicieuse, date d'expiration passée), il est supprimé automatiquement. Si vous n'aimez pas ce comportement, vous pouvez le desactiver en envoyant "false" en 2ème paramètre.

Supprimer un cookie

$manager->deleteCookie('cookieName');

N'hésitez pas à regarder le code source pour plus d'infos, il est commenté. Si vous avez des questions, des remarques, des rapports de bugs, n'hésitez pas à poster un commentaire :)

Note: Il existe une classe qui permet de faire à peu près la même chose avec Django (Python) ici. Cependant elle ne gère ni le chiffrement ni SSL.

Note2: La fonctionnalité SSL ne devrait marcher qu'avec le mod_ssl d'Apache (qui créé généreusement une variable d'environnement SSL_SESSION_ID).

Introduction à la sécurité des cookies

J'ai vu beaucoup de gens tenter de créer un protocole de sécurisation des cookies. Nombre d'entre eux sont peu fiables car leur sécurité est basée sur le secret de l'algorithme employé. Ca peut marcher un certain temps, ça peut rassurer psychologiquement, mais au final, c'est pas fiable. Dans un premier temps, pour que ce soit clair, on va définir ce qu'est un cookie "sécurisé" :
  1. Pour certains, c'est un moyen de garantir l'authenticité et l'intégrité des données qui sont stockées dans un cookie. Ils ne veulent pas qu'un utilisateur malicieux puisse modifier la valeur du cookie pour les duper.
  2. Pour d'autres, un cookie sécurisé garantit la confidentialité des données stockées sur la machine du client. C'est à dire que les données ne peuvent être lues/comprises ni par l'utilisateur, ni par une personne malicieuse qui aurait volé un coookie. Seule l'application web (donc coté serveur) à le droit de lire les valeurs stockées dans le cookie.
  3. Pour d'autres, un cookie sécurisé garantit qu'il ne peut être ni intercepté, ni "rejoué" (replay attack) par une personne malicieuse (dans un environnement non sécurisé : machine passeoire, cybercafé, wifi ouvert ou mal sécurisé...etc)
Vous allez peut être me demander : "Mais pourquoi stocker des données sensibles/confidentielles dans des cookies ? Un peu après l'age de la préhistoire on a inventé un mécanisme de sessions pour stocker ce genre de données ! Les cookies, c'est aussi fiable que le réseau Wifi de ma grand mère ! Et elle n'a jamais entendu parler de WPA ou de wardriving de toute sa vie ! T'aimes vraiment jouer avec le feu ?"
Ca fait beaucoup de questions, mais je vais tenter d'y répondre. Pour commencer, oui j'aime jouer avec le feu !

Ensuite, pour cette histoire de cookies, oui il est possible de stocker des données sensibles en session sur le serveur. Mais je n'aime pas les sessions (et je ne suis pas le seul) : c'est une vraie galère à gérer dans un environnement multiserveur. Cela dit, on est pas là pour parler de sessions.

Quoi qu'il arrive, même avec une session, vous aurez à stocker l'identifiant de session dans un cookie. Et ce cookie est TRES IMPORTANT... il doit être sécurisé :) La plupart des applications sont concernées par la sécurité des coookies.

Donc poursuivons : suivant la nature des données que vous aurez à stocker dans des cookies, vous allez devoir considérer les différents points évoqués plus haut (authenticité, integrité, confidentialité, "replay attack" et interception de cookie) et choisir ceux que vous voudrez prendre en compte dans votre politique de sécurité.

Pour chacun des problèmes évoqués, il existe une solution (cryptographique) :
  • Le problème d'intégrité et d'authenticité peut être résolu avec des fonctions de hachage. Nous allons utiliser une solution basée sur ces fonctions de hachage : HMAC.
  • Le problème de confidentialité peut être résolu en utilisant des fonctions de chiffrement par bloc (par exemple AES).
  • Le problème de "replay attack" peut être résolu en combinant l'utilisation d'une fonction HMAC et en utilisant le protocole de transport sécurisé SSL/TLS.
  • Le risque d'interception de cookies peut être réduit en utilisant SSL. (réduit seulement : un cookie peut toujours être volé d'une autre façon car il est stocké de sur la machine du client, il peut donc être volé). Cependant, l'interception d'un cookie sécurisé ne devient plus très interressante si la valeur est chiffrée, non rejouable dans une "replay attack" et pas modifiable à cause des contrôles d'intégrité. Cela devient relativement inutile d'intercepter ce genre de cookies.

Bref, si vous avez tout suivi jusqu'ici, vous devriez être interressé par une solution concrête pour sécuriser des cookies, avec plein de détails croustillants ! Je vous invite donc à lire ce papier : "A Secure Cookie Protocol" écrit par Alex X. Liu.

Dans le prochain article, nous verrons comment implémenter ce protocole en PHP. Et peut être plus tard comment l'intégrer au Zend Framework.

Les commentaires sont les bienvenus :)

Encore un blog !

Salut

Voici une version française de mon blog : http://bigornot.blogspot.com

Vous pourrez y trouver des articles sur le Developpement Web (PHP en particulier), la sécurité, linux, la "scalability", les bonnes pratiques du calibrage de bigorneaux, et tout ce qui me passe par la tête.

J'espère que ça pourra être utile à quelqu'un :)

Bonne lecture
Mat