Affichage des articles dont le libellé est cookies. Afficher tous les articles
Affichage des articles dont le libellé est cookies. Afficher tous les articles

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 :)