Protéger une pages en PHP avec masque jetable et authentification HTTP
samedi 20 août 2011
Il est souvent nécessaire de protéger le contenu d’une partie d’un site web par une authentification demandant un mot de passe. Le plus souvent, les CMS ou les blogs possèdent des systèmes pré-installés pour gérer cette problématique. Moi même sur ce site, l’espace d’administration est protégé par un mot de passe. Mais je me suis rendu compte pendant mes voyages que ce n’était pas très sécurisé : les connexions se font pour la plupart dans des cyber cafés un peu douteux, et ça ne m’étonnerai pas que certains récupèrent les mots de passes juste au cas où. Je me suis donc mis en tête de réfléchir à un système d’authentification suivant ces critères :
- Le système doit utiliser des clés jetables, qui ne servent qu’une fois
- Il doit s’insérer en plus du système d’authentification actuel de mon site
- Il doit s’activer uniquement quand je vais en vacances
Partant du bon vieux principe du masque jetable, je me suis lancé dans un module en deux parties :
- La première qui génère les clés dans un fichier, ce qui me permet de les imprimer
- La seconde qui vérifie l’authentification, et qui passe à la clé suivante quand une a été utilisée
Le générateur de clés
Le premier fichier contient le code à utiliser pour générer les clés. Il se nomme vername.generator.php
:
// Nombre de chars des clés $keylength = 4; // Nombre de clés à générer $limit = 252; // Generation des clés $v = array(0,1,2,3,4,5,6,7,8,9,'a','b','c','d','e','f'); $s = sizeof($v); $keys = array(); $txt = " VERNAME\n" $txt .= "=================================================================\n"; for ($i = 1; $i <= $limit; $i++) { $k = ''; $c = $keylength; while ($c-- > 0) { $k .= $v[rand(1, $s) - 1]; } $txt .= str_pad("$i", 4, ' ', STR_PAD_LEFT) . '. ' . $k; $txt .= ($i > 0 && $i % 6 == 0) ? "\n" : ' '; $keys[$i] = sha1($k); } // Création du coffre $lock = array('cursor' => 1, 'keys' => $keys, 'last_connection' => 0); // Enregistrement du coffre @file_put_contents(dirname(__FILE__) . '/vername.k', serialize($lock)); // Affichage des clés header('Content-type: text/plain'); echo $txt; echo '================================================================='; echo "\n";
Deux paramètres sont configurables : $keylength
et $limit
, qui servent respectivement à indiquer le
nombre de caractères des clés et le nombre de clés à générer. Les clés sont affichés en clair directement dans le navigateur, et le script
va générer un fichier vername.k
qui contient les clés chiffrées.
Le fichier d’authentification
Le second fichier va s’occuper de l’authentification proprement dite. Premièrement, il ne s’active que si un fichier vername.k
existe dans le même répertoire. Ensuite, il lit le fichier et récupère la clé actuelle. Si l’authentification est validée, il incrémente le
compteur et enregistre l’heure de la session. Pour bien fonctionner, quand le système se déclenche, il affiche le numéro de la clé demandée :
Par défaut, le temps d'une session est limitée à 1 heure (3600 secondes).
Ce fichier se nomme vername.core.php
:
if (is_file(dirname(__FILE__) . '/vername.k')) { // On définit le TTL (en secondes) if (!defined('VERNAME_TTL')) { define('VERNAME_TTL', 3600); // 1 heure } // On lit le fichier de clés $lock = @file_get_contents(dirname(__FILE__) . '/vername.k'); if (!$lock) { header('HTTP/1.0 503 Service Unavailable', true, 503); exit('Error 503 Service Unavailable'); } // On désérialise l'information $lock = @unserialize($lock); if (!is_array($lock)) { header('HTTP/1.0 500 Internal Server Error', true, 500); exit('Error 500 Internal Server Error'); } // On regarde si la session a expirée if (time() - $lock['last_connection'] > VERNAME_TTL) { // Si oui, on incrémente le curseur $lock['cursor']++; } // On regarde si la clé existe if (!isset($lock['keys'][$lock['cursor']])) { header('HTTP/1.0 520 Expired Key File', true, 520); exit('Error 520 Expired Key File'); } // On recupère la clé actuelle $password = $lock['keys'][$lock['cursor']]; // Patch $matches = array(); if ((!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) && preg_match('/Basic\s+(.*)$/i', $_SERVER['REMOTE_USER'], $matches) > 0) { list($name, $pass) = explode(':', base64_decode($matches[1])); $_SERVER['PHP_AUTH_USER'] = strip_tags($name); $_SERVER['PHP_AUTH_PW'] = strip_tags($pass); } // On force l'authentification if (!isset($_SERVER['PHP_AUTH_USER']) || $_SERVER['PHP_AUTH_USER'] != 'root' || sha1($_SERVER['PHP_AUTH_PW']) != $password) { header('WWW-Authenticate: Basic realm="Key '.$lock['cursor'].'"'); header('HTTP/1.0 401 Unauthorized', true, 401); exit("401 Unauthorized"); } // On enregistre le timestamp de session $lock['last_connection'] = time(); // Enregistrement du coffre @file_put_contents(dirname(__FILE__) . '/vername.k', serialize($lock)); }
Utilisation
Pour l'utilisation c'est très simple. Commencez par créer un fichier vername.k
avec le générateur. Ensuite, envoyez le fichier vername.core.php
sur votre serveur, ainsi que le fichier vername.k
. Dans votre page index.php
(et partout où vous souhaitez cette protection), intégrez
simplement le fichier :
require_once "vernom.core.php";
Le fichier de logout
Ce fichier permet de supprimer l'authentification. Il est à inclure dans votre page de déconnexion actuelle.
if (is_file(dirname(__FILE__) . '/vername.k')) { // On lit le fichier de clés $lock = @file_get_contents(dirname(__FILE__) . '/vername.k'); if ($lock) { // On désérialise l'information $lock = @unserialize($lock); if (is_array($lock)) { // On enregistre un timestamp vide $lock['last_connection'] = 0; // Enregistrement du coffre @file_put_contents(dirname(__FILE__) . '/vername.k', serialize($lock)); } } }
Le fichier htacess
Si vous indiquez la bonne clé mais que le système ne fonctionne pas, cela peut venir du serveur. Parfois, PHP est installé en mode CGI
ce qui provoque la perte des variables Apache. Pour remédier à cela, voici le fichier .htaccess
à ajouter dans le même répertoire :
<IfModule mod_rewrite.c> RewriteEngine on RewriteCond %{HTTP:Authorization} ^(.*) RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization},L] </IfModule> <Files vername.k> Deny from all </Files>
Notez que ce patch se retrouve dans le code PHP du fichier vername.core.php
: les identifiants sont récupérés dans la variable $_SERVER['REMOTE_USER']
. A partir de là, l'identifiant est décodé en base64 comme spécifié par la norme RFC 2617.
Commentaires
Bravo très utile!
Par contre il y a une erreur à la ligne 25 du fichier vername.core :
header('Error 520 Expired Key File');
Ce devrait être :
header('HTTP/1.0 520 Expired Key File');
Merci c'est corrigé
Merci pour ce tuto qui fonctionne nickel.
Mais celui qui accede à vername.k en snifant mon site découvre les passwords.
asninfo > et bien non, le fichier vername.k est strictement protégé par le fichier htaccess.
Enfin, les mots de passes sont cryptés à l'intérieur de ce fichier.