Nos armes de défense
Pour mettre au point notre contre-attaque, je vous conseille fortement de maîtriser les points suivants en PHP :
- les sessions ;
- les cookies ;
- les bases de MySQL.
Si vous êtes prêt(e)s, alors
let's script !!
Partie SQL
Réfléchissons ensemble à l'intégration de notre dispositif. Il nous faut tout d'abord une table qui stocke les personnes ayant un accès à la partie administration.
Code : SQL | --
-- Structure de la table `administration`
--
CREATE TABLE IF NOT EXISTS `administration` (
`id` tinyint(2) NOT NULL auto_increment,
`pseudo` varchar(50) collate utf8_unicode_ci NOT NULL,
`passe` varchar(32) collate utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;
|
Cryptage des données
Quelques explications s'imposent : on met un
VARCHAR 32 pour le
passe car nous allons le hacher avec la fonction
md5() de PHP qui retourne une chaîne de 32 caractères. Je ne m'étendrai pas sur le principe du hachage dans ce tutoriel. Sachez simplement que si un pirate venait à se procurer le contenu de votre BDD, il n'aurait pas directement accès à vos mots de passe car ils sont hachés, et il est impossible de faire marche arrière (le dé-hachage n'existe pas

).
Pour les petits curieux :
informations sur le principe de hachage de md5.
Attention ! Aujourd'hui, il existe des sites qui recensent les équivalents md5 de beaucoup, beaucoup de mots (un petit exemple
ici ou encore
ici). Les pirates peuvent donc les utiliser pour "déhacher" vos mots de passe s'ils parviennent à mettre la main sur votre BDD. Vous pouvez donc utiliser une autre fonction de hachage qui est plus recommandée aujourd'hui :
sha1(). À vous de savoir vous adapter aux techniques des pirates pour défendre vos applications

.
Si vous décidez de hacher en
sha1(), sachez que l'empreinte prend 40 caractères et non 32.
Petit script qui vous hache votre mot de passe :
Code : PHP | <?php
$passe = 'anticonstitutionnellement'; // Passe à hacher
echo md5($passe); // Renvoie : 99c1c137d8d85917f632a0e34a35a5f7
?>
|
Remarque : pour ceux qui veulent accroître la sécurisation de leurs mots de passe, sachez qu'il aurait fallu rajouter un "salt" ou grain de sel : un grain de sel est un mot ou un passe que vous seuls connaissez et que vous allez ajouter aux mots de passe entrés par les membres. À quoi ça sert ? Si un pirate met la main sur votre BDD, il lui sera beaucoup plus difficile de "déhacher" ces mots md5 avec l'utilisation d'un site qui les recense comme je vous en ai montré un plus tôt.
Pour plus d'informations :
salage.
Petit exemple :
Code : PHP 1
2
3
4
5
6
7
8
9
10
11
12 | <?php
// Salage du mot de passe
define("PREFIXE", "zer0");
define("SUFFIXE", "forever");
$passe = 'anticonstitutionnellement'; // Passe à hacher
// Assemblage de notre passe
$passe = PREFIXE . $passe . SUFFIXE;
echo md5($passe);
?>
|
Et pour les plus paranos d'entre nous, je propose une petite fonction de hachage très performante :
Code : PHP 1
2
3
4
5
6
7
8
9
10
11
12
13 | <?php
function hacher($passe)
{
// Nos grains de sel
define("PREFIXE", "zer0");
define("SUFFIXE", "forever");
// Faites tournez le Hachage ^^
$passe = md5( sha1(PREFIXE) . $passe . sha1(SUFFIXE) );
return $passe;
}
?>
|
Mais je ne comprends pas à quoi elle sert, ta fonction. Comment on l'utilise, concrètement ?
Je m'attendais à cette question, ce n'est pas pour rien qu'on est sur le Site du Zér0 : c'est donc mon devoir de tout vous expliquer depuis zéro.
Une utilisation possible de cette fonction :
Code : PHP 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | <?php
// On hache le passe récupéré à partir d'un formulaire
$passe = hacher($_POST['passe']);
// Formatage de notre requête sql
$requete = "SELECT passe FROM administration WHERE pseudo = 'admin'";
// Envoi de la requête au serveur
$query = mysql_query($requete) or exit(mysql_error());
$resultat = mysql_fetch_assoc($query);
// On compare les deux empreintes
if($resultat['passe'] == $passe)
{
echo 'le mot de passe est correct';
}
else
{
echo 'Erreur : mauvais mot de passe !';
}
?>
|
Bref, fermons la parenthèse et revenons à notre script.
Ensuite, on entre toutes nos données dans la BDD dans la table fraîchement créée

.
Code : SQL | INSERT INTO administration(id, pseudo, passe)
VALUES('', 'admin', '99c1c137d8d85917f632a0e34a35a5f7')
|
Partie XHTML
Passons à présent à la partie XHTML, créons un petit formulaire pour accéder à la partie administration.
Code : HTML 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Administration</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<form method="post" action="">
<p>
<label>Pseudo </label>
<input type="text" name="pseudo" /><br />
<label>Passe </label>
<input type="text" name="passe" /><br />
<input type="submit" name="connexion" value="connexion" />
</p>
</form>
</body>
</html>
|
Vous pouvez bien entendu rajouter des champs supplémentaires et des fichiers CSS, après c'est à vous de voir

.
Algorithme défensif
Allez : on entre vraiment dans le vif du sujet maintenant que les bases sont posées.
Codons l'algorithme de base de notre système de défense.
Quoi ? Vous pensiez vraiment que j'allais tout vous donner sur un plateau d'argent

?
Maintenant c'est à vous de jouer (enfin, de coder).
Essayez de le faire par vous-mêmes, ça sera un très bon entraînement car c'est en "forgeant qu'on devient forgeron".
Voilà un petit récapitulatif de l'algorithme de base que nous développerons ensuite :
Code : Autre1
2
3
| Si le formulaire est correctement validé (champs correctement remplis)
Sécurisation des variables transmises
Formatage de la requête SQL |
Bon, vous avez réussi j'espère, ce n'est pas encore trop compliqué. Voilà une solution possible :
Code : PHP 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | <?php
// Si le formulaire est correctement rempli
if(isset($_POST['connexion'])
and !empty($_POST['pseudo'])
and !empty($_POST['passe']))
{
// Sécurisation des variables
$pseudo = mysql_real_escape_string($_POST['pseudo']);
$passe = mysql_real_escape_string(md5($_POST['passe'])); // Hachage
// Formatage de la requête
$requete = "SELECT id
FROM administration
WHERE pseudo = '$pseudo'
AND passe = '$passe'";
}
?>
|
Utilisation des sessions
Rappelez-vous, nous devons empêcher le hackeur de faire plus de
n tentatives par minute.
Nous allons donc créer des variables qui vont "compter" le nombre de fois que le formulaire a été soumis.
Allez : creusez vous les méninges, je suis sûr que vous allez trouver...
Bien sûr, vous avez raison, nous utiliserons les sessions : c'est l'outil le mieux adapté à nos besoins. Complétez donc le script précédent.
Voilà une solution possible :
Code : PHP | <?php
// Si la variable de session qui compte le nombre de soumissions n'existe pas
if(!isset($_SESSION['nombre']))
{
// Initialisation de la variable
$_SESSION['nombre'] = 0;
}
?>
|
Très bien, maintenant nous allons effectuer la requête SQL sous certaines conditions seulement : il faut que la variable de session soit inférieure à
n (nous prendrons ici n = 10, mais vous pouvez très bien modifier cette valeur par la suite).
Voilà ce que j'obtiens :
Code : PHP 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | <?php
// Si on n'essaye pas de nous attaquer par force brute
if($_SESSION['nombre'] < 10)
{
// Connexion à notre base de données
mysql_connect("localhost", "root", "");
mysql_select_db("table");
// Envoie de la requête au serveur
$query = mysql_query($requete) or exit(mysql_error());
// Incrémentation de notre variable de session
$_SESSION['nombre']++;
}
// Si on a dépassé les 10 tentatives, on quitte le script
else
{
exit();
}
?>
|
N'oubliez pas de rajouter la fonction
session_start() en haut de votre code, sinon la session ne fonctionnera pas correctement

.
Problèmes de sécurité
À ce stade, le script fonctionne : il nous est impossible de lancer plus de 10 tentatives. Cependant, il reste quelques problèmes à régler : si le membre reste sur le site, il conserve ses variables de session et ne peut donc pas se reconnecter.
De plus, si on ferme le navigateur et qu'on le relance, il est possible de renvoyer des tentatives...
Comment faire pour parer à ces problèmes ?
Pour le premier problème, nous allons simplement rajouter une variable de session qui enregistre le temps au bout duquel le formulaire sera de nouveau opérationnel.
Voilà ce que je vous propose de rajouter :
Code : PHP | <?php
// Si la variable de session qui compte le nombre de soumissions n'existe pas
if(!isset($_SESSION['nombre']))
{
// Initialisation de la variable
$_SESSION['nombre'] = 0;
// Blocage pendant 10 min
$_SESSION['timestamp_limite'] = time() + 60*10;
}
?>
|
Puis, tout au début du script, rajoutez :
Code : PHP | <?php
// Si on a dépassé le temps de blocage
if(isset($_SESSION['nombre'])
and $_SESSION['timestamp_limite'] < time())
{
// Destruction des variables de session
unset($_SESSION['nombre']);
unset($_SESSION['timestamp_limite']);
}
?>
|
Pour surmonter le second problème, nous allons à présent utiliser les cookies qui constitueront une sécurité complémentaire et nécessaire.
Utilisation des cookies
Principe de la protection : si le membre a entré dix tentatives, on crée un cookie qui nous permettra de le marquer lorsqu'il reviendra sur le site. Ce cookie devra le bloquer pendant X minutes (nous prendrons X = 1). Allez, réfléchissez à la manière de réaliser tout ça : ce n'est pas très compliqué

(le plus dur est déjà passé, ne vous en faites pas).
Voilà une solution :
Code : PHP 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | <?php
// Si on a dépassé les 10 tentatives
else
{
// Si le cookie marqueur n'existe pas, on le crée
if(!isset($_COOKIE['marqueur']))
{
$timestamp_marque = time() + 60; // On le marque pendant une minute
setcookie("marqueur", "marque", $timestamp_marque);
}
// on quitte le script
exit();
}
?>
|
Remarque : le code que je viens de vous montrer comporte en fait un autre problème de sécurité : il provient du décalage horaire qui peut exister entre votre serveur et la localisation géographique de l'internaute. Par exemple, si vous habitez en Chine et que vous exécutez ce script depuis un serveur français, alors le cookie sera "mort-né". Autrement dit, dès qu'il sera créé, il sera détruit à cause du décalage horaire

.
Pour se conformer à ce petit problème supplémentaire, nous allons tout simplement donner une durée de vie plus importante au cookie et stocker le timestamp limite dans sa valeur.
Voilà ce que ça donne :
Code : PHP 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | <?php
// Si on a dépassé les 10 tentatives
else
{
// Si le cookie marqueur n'existe pas on le crée
if(!isset($_COOKIE['marqueur']))
{
$timestamp_marque = time() + 60; // On le marque pendant une minute
$cookie_vie = time() + 60*60*24; // Durée de vie de 24 heures pour le décalage horaire
setcookie("marqueur", $timestamp_marque, $cookie_vie);
}
// on quitte le script
exit();
}
?>
|
Parfait ! À présent, il ne nous reste plus qu'à vérifier si le cookie existe avant de lancer quoi que ce soit.
Voilà ce qu'il faut rajouter :
Code : PHP 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | <?php
// Si le cookie n'existe pas
if(!isset($_COOKIE['marqueur']))
{
// Tout notre code
}
// Si le cookie existe
else
{
// Si le temps de blocage a été dépassé
if($_COOKIE['marqueur'] < time())
{
// Destruction du cookie
setcookie("marqueur", "", 0);
}
}
?>
|
Le pirate ou le robot qui tentera d'accéder à votre script de connexion se bannira lui-même à la condition qu'il prenne en charge les sessions et les cookies

.
Voilà : le script est terminé.
Certes, il est basique mais terriblement efficace contre ce type d'attaques !