Protéger une pages en PHP avec masque jetable et authentification HTTP

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 :

  1. Le système doit utiliser des clés jetables, qui ne servent qu’une fois
  2. Il doit s’insérer en plus du système d’authentification actuel de mon site
  3. 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 :

  1. La première qui génère les clés dans un fichier, ce qui me permet de les imprimer
  2. 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 :

vernom1.jpg

Demande de la clé n° 2

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

1. Commentaire de lebourdieu le samedi 3 septembre 2011

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');

2. Commentaire de Ted le samedi 17 septembre 2011

Merci c'est corrigé :)

3. Commentaire de asninfo le mardi 1 novembre 2011

Merci pour ce tuto qui fonctionne nickel.
Mais celui qui accede à vername.k en snifant mon site découvre les passwords.

4. Commentaire de Ted le mardi 8 novembre 2011

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.


About the Author

Ted Marklor est un web designer, un web developer et un génie de la nature. Transcendant le web depuis bientôt 15 ans, Ted est une source d’inspiration et de conseil pour toute une génération de jeunes programmeurs. Le Web 2.0, c’est lui. Dans la vie, il aime aussi faire des avions en papier, s’inventer des pseudonymes et une vie de winner, et surtout parler de lui à la troisième personne. Ça se fait en ce moment sur les blogs…


Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.