Version en ligne

Tutoriel : Restreindre l'accès d'un site à certaines adresses IP

Table des matières

Restreindre l'accès d'un site à certaines adresses IP
Détecter l'adresse IP du client
Première solution : MySQL
Seconde solution : un fichier texte
Interdire l'accès aux adresses retenues

Restreindre l'accès d'un site à certaines adresses IP

Détecter l'adresse IP du client

Il nous arrive parfois, lorsque l'on est webmaster d'un site, de se retrouver aux prises avec des gens agissant de manière douteuse sur notre site web. Parfois, le problème peut venir de robots qui postent un peu partout sur le site (dans le livre d'or, sur le forum, etc.).

Dans ces circonstances, il est pratique d'avoir des solutions d'urgence qui peuvent être mises en place rapidement et efficacement.

Détecter l'adresse IP du client

Première solution : MySQL

Détecter une adresse IP est quelque chose de très simple en soi. Lors de l'exécution des pages PHP, la variable superglobale $_SERVER contient des informations sur la page en cours de chargement, et sur les communications effectuées entre le serveur et le client lors de cette requête. L'entrée REMOTE_ADDR nous donne l'IP du client qui a demandé la page.

Image utilisateur

Ainsi, ce code affiche l'IP du visiteur :

<?php
print ('Votre adresse IP est <strong>' . $_SERVER['REMOTE_ADDR'] . '</strong>');
?>

C'est donc quelque chose de très simple en soi, comme je l'ai dit. Par contre, il arrive que des internautes se connectent à ce que l'on appelle des serveurs proxy. Un serveur proxy, c'est un serveur qui sert d'intermédiaire entre un visiteur et un site web. L'avantage pour un internaute, c'est que son adresse IP n'est pas affichée directement sur le serveur qui reçoit sa requête ; c'est celle du serveur proxy qui est affichée, car c'est lui qui transmet la requête.

Image utilisateur

Ce procédé permet de berner les protections de la plupart des sites web. Nous nous créerons donc une fonction qui nous permettra de récupérer l'IP du véritable client. Ce n'est pas difficile, encore une fois.

Lorsque c'est un serveur proxy qui effectue la requête pour un autre client, une nouvelle case est créée dans notre tableau $_SERVER. Cette case a pour clé HTTP_X_FORWARDED_FOR. Elle signifie « effectue la requête pour ». À partir de ça, on peut donc créer notre fonction :

<?php
//Retourne la vraie adresse IP
function get_ip() {
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
                $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
        else
                $ip = $_SERVER['REMOTE_ADDR'];
        return $ip;
}
?>

De manière totalement personnelle, je préfère abréger cela à l'aide d'un ternaire :

<?php
//Retourne la vraie adresse IP
function get_ip() {
        return (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
}
?>

Assurez-vous d'inclure le fichier dans lequel vous placerez la fonction partout où vous en aurez besoin. Personnellement, je dédie un fichier unique à cette fonction, dans un dossier libs/div/. Cette protection n'est pas fiable à 100 %, car il existe plusieurs niveaux de proxy, des « peu anonymes » aux « super anonymes ». Dans le cas des proxy considérés comme très confidentiels, il est pratiquement impossible de détecter le client pour qui ils effectuent les requêtes, voire de détecter qu'il s'agit d'un proxy. ;) Mais on n'aura rien à se reprocher, car on aura fait tout ce qui était possible.

Nous allons maintenant créer une petite interface nous permettant de bannir rapidement des adresses IP. Je vois deux façons de le faire. La première façon utilise une table MySQL et la seconde un simple fichier texte. Il est peut-être plus astucieux d'utiliser la seconde solution, car le premier cas implique que nous exécutions une requête SQL de plus à tous les chargements de page, sans exception.

Par contre, en utilisant MySQL, il vous sera plus facile d'ajouter des options utiles comme l'affichage de raisons personnalisées pour les bannissements, les débannissements automatiques, etc.

Alors là, on va en bannir, des IP ! :D


Première solution : MySQL

Première solution : MySQL

Détecter l'adresse IP du client Seconde solution : un fichier texte

Pour commencer, il vous faut créer une table sur votre base de données (si vous n'en avez pas, passez à la seconde solution ^^ ). Je vous donne une structure d'exemple, changez-la comme vous voulez pour l'adapter à vos besoins :

CREATE TABLE `ban` (
     `ban_id` MEDIUMINT( 9 ) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
     `ban_ip` INT NOT NULL ,
     UNIQUE (`ban_ip`)
);

Cela fait, nous allons créer un nouveau fichier dont le code ne vous sera donné qu'en guise d'exemple. Prenez note que je n'ai pas pris soin de protéger les codes, car vous seuls savez comment les intégrer à votre site web et à votre interface d'administration. Vous devez protéger l'accès à ces sections ! Bien entendu, il va de soi que je ne vous donnerai pas de fichiers complets, seulement les parties concernées. ;)

Bannir une adresse IP

Pour bannir une adresse IP, nous suivrons cette démarche :

Tout d'abord, le formulaire :

<form action="ban.php" method="post">
     <fieldset>
          <legend>Bannir une adresse IP</legend>
          <p><label for="ip">Adresse à bannir : </label></p>
          <input type="text" name="ip" id="ip" />
          <input type="submit" value="Bannir" />
     </fieldset>
</form>

Pensez bien à remplacer l'action du formulaire pour y donner le bon nom de page vers laquelle poster. Pour ceux qui se posent la question, la balise <label></label> sert à joindre un bout de texte à un champ de formulaire. Ainsi, en cliquant sur « Adresse à bannir », le focus sera donné au champ dont l'id est « ip ». Très pratique. :)

Maintenant, on va traiter les données de ce formulaire. Le code ci-dessous suppose que vous ayez une connexion MySQL active. Il reprend le formulaire ci-dessus et l'inclut dans le cas où il n'a pas été posté :

<?php
$isset = isset($_POST['ip']); //true si le formulaire est posté
$erreur = false; //On change cette valeur à la moindre erreur
if ($isset) {
     //Si le formulaire a été posté
     if (!empty($_POST['ip'])) {
          $ip = ip2long($_POST['ip']);
          if ($ip != false && $ip != -1) {
               //Si l'ip est valide
 
               require ('libs/div/is_ban.php');
               if (!is_ban($ip)) {
                    mysql_query('INSERT INTO ban (`ip`) VALUES(\'' . $ip . '\')');
                    print('Cette adresse est désormais non-autorisée.');
               }
               else
                    print ('Cette adresse est déjà bannie.');
          }
          else 
               $erreur = 1; //L'ip est invalide, erreur #1
     }
     else
          $erreur = 0; //Le champ est vide, erreur #0
}
if (!$isset || $erreur !== false) {
     //Si le formulaire n'a pas été posté ou qu'une erreur a été détectée
     $erreurs = array('Vous devez entrer une adresse ip.', 'L\'adresse ip est invalide.');
     
     if ($erreur !== false) {
          //Si on a une erreur, on l'affiche
          print('<p style="color: red; font-weight: bold;">' . $erreurs[$erreur] . '</p>');
     }
     
     //On affiche le formulaire
     print ('<form action="ban.php" method="post">
               <fieldset>
                    <legend>Bannir une adresse IP</legend>
                    <p><label for="ip">Adresse à bannir : </label></p>
                    <input type="text" name="ip" id="ip" />
                    <input type="submit" value="Bannir" />
               </fieldset>
             </form>');
}
?>

Je vous l'ai dit, c'est super simple ! À vous d'inclure ce code à votre panneau d'administration, et je le répète, protégez-le ! ;)

La fonction ip2long() retourne l'adresse IP sous forme d'entité numérique, je l'ai dit. Si l'adresse IP est invalide, elle retourne -1 avec PHP3 et PHP4, tandis qu'avec PHP5, elle retourne false : un caprice. Vous pouvez modifier le script pour n'effectuer que le test logique qui correspond à la version de PHP que vous utilisez, si vous la connaissez (sinon, phpinfo).

Obtenir la liste des adresses bannies

Pour voir la liste des adresses bannies, il nous suffit de faire une simple requête SELECT sur notre table. Lorsque l'on aura écrit le script pour débannir des adresses, il serait astucieux de placer, sur chaque adresse de cette liste, des liens pour débannir. ;) Le code, vous êtes capables de le faire seuls, je l'espère. :p Enfin, pour rassurer les plus inquiets (le code suppose que vous avez une connexion MySQL active) :

<?php
//Quoi de plus bête comme requête :p
$req = mysql_query("SELECT * FROM ban");
if($res = mysql_fetch_assoc($req)) {
        print('<ul>');
        do {
                print ('<li>' . long2ip($res['ban_ip']) . '</li>');
        } while($res = mysql_fetch_assoc($req));
        print ('</ul>');
}
else
        print ('Aucune adresse IP bannie.'); //Personne de banni, on le signale 
?>

C'est très simple et encore une fois, je vous laisse le soin de protéger le tout efficacement.

Débannir une adresse IP

Pour débannir une adresse, nous n'avons qu'à supprimer l'entrée correspondante dans la base de données. Pour rappel, afin de supprimer une entrée dans une table MySQL, nous devons utiliser la requête DELETE. Voici un code tout simple :

<?php
if (isset($_GET['ip'])) {
     mysql_query('DELETE FROM ban WHERE ban_ip=\'' . $_GET['ip'] . '\'');
     if (mysql_affected_rows() == 0) 
          print ('Cette adresse IP n\'était pas bannie.');
     else
          print ('L\'adresse IP a été débannie.');
}
else
     print ('Vous n\'avez spécifié aucune adresse IP.');
?>

Vous pouvez maintenant ajouter les liens à votre liste d'adresses IP, avec cette ligne dans votre boucle par exemple :

print ('<li><a href="supp.php?ip=' . $dselect['ban_ip'] . '" title="Débannir cette adresse">' . long2ip($dselect['ban_ip']) . '</a></li>');

Voilà, c'est tout pour MySQL, vous pouvez passer à la dernière partie qui consiste à restreindre l'accès aux adresses IP bannies (non, ça ne se fait pas tout seul :p ).


Détecter l'adresse IP du client Seconde solution : un fichier texte

Seconde solution : un fichier texte

Première solution : MySQL Interdire l'accès aux adresses retenues

Ici, on va apprendre à utiliser une solution plus légère. Forcément, vu la quantité très mince de ressources consommées par cette alternative, les options possibles en sont réduites. Mais soyez sans crainte, vous pourrez gérer des centaines de bannissements de cette manière : c'est plutôt au niveau des débannissements automatiques et des messages personnalisés que ça se complique. Mais tout ça, c'est facultatif. ;)

Tout d'abord, créez un fichier ips.txt, puis placez-le où vous en avez envie. N'oubliez pas de modifier les chemins d'accès de mes exemples. ;) Ce fichier contiendra une adresse IP par ligne. Vous l'aurez deviné, les adresses figurant dans ce fichier seront interdites d'accès au site.

Bannir une adresse ip

Pour bannir une adresse ip, nous devons ajouter une ligne contenant l'adresse en question au fichier ips.txt. Nous allons élaborer notre script en suivant les étapes suivantes :

Le formulaire demandant l'adresse IP est le même que celui que vous auriez écrit si vous aviez opté pour la solution MySQL :

<form action="ban.php" method="post">
     <fieldset>
          <legend>Bannir une adresse IP</legend>
          <p><label for="ip">Adresse à bannir : </label></p>
          <input type="text" name="ip" id="ip" />
          <input type="submit" value="Bannir" />
     </fieldset>
</form>

Maintenant, nous allons traiter les données de ce formulaire. Pour ouvrir notre fichier, nous utiliserons la fonction fopen() et nous ouvrirons le fichier en mode 'a', un mode d'écriture qui place le pointeur à la fin du fichier. Pour ouvrir votre fichier, le dossier qui le contient et lui-même doivent avoir un chmod de 777. Voici le code, dont la structure est la même que celui de la première solution :

<?php
$isset = isset($_POST['ip']); //true si le formulaire est posté
$erreur = false; //On change cette valeur à la moindre erreur
if ($isset) {
     //Si le formulaire a été posté
     if (!empty($_POST['ip'])) {
          $ip = ip2long($_POST['ip']);
          if ($ip != false && $ip != -1) {
               //Si l'IP est valide
               
               require ('libs/div/is_ban.php');
               if (!is_ban(long2ip($ip))) {
                    $fichier = fopen('ips.txt', 'a'); //On ouvre en mode 'a'
                    fwrite($fichier, $ip . "\n"); //On ajoute la ligne avec l'IP
                    fclose($fichier); //On ferme le fichier    
                    print('Cette adresse est désormais non-autorisée.');
               }
               else
                    print ('Cette adresse est déjà bannie.');
          }
          else 
               $erreur = 1; //L'IP est invalide, erreur #1
     }
     else
          $erreur = 0; //Le champ est vide, erreur #0
}
if (!$isset || $erreur !== false) {
     //Si le formulaire n'a pas été posté ou qu'une erreur a été détectée
     $erreurs = array('Vous devez entrer une adresse IP.', 'L\'adresse IP est invalide.');
     
     if ($erreur !== false) {
          //Si on a une erreur, on l'affiche
          print('<p style="color: red; font-weight: bold;">' . $erreurs[$erreur] . '</p>');
     }
     
     //On affiche le formulaire
     print ('<form action="ban.php" method="post">
               <fieldset>
                    <legend>Bannir une adresse IP</legend>
                    <p><label for="ip">Adresse à bannir : </label></p>
                    <input type="text" name="ip" id="ip" />
                    <input type="submit" value="Bannir" />
               </fieldset>
             </form>');
}
?>

Maintenant que nous pouvons bannir des adresses IP, nous allons voir comment récupérer la liste des adresses bannies. ;)

Obtenir la liste des adresses bannies

Pour récupérer cette liste, nous devrons tout d'abord lire le contenu de notre fichier et le stocker dans une variable. Pour ce faire, nous utiliserons la fonction file() qui nous retourne un array contenant les différentes lignes du fichier. Si ce n'est déjà fait, créez un fichier ips.txt que vous laissez vide. Voici un exemple très simple :

<?php
//On récupère les adresses dans un tableau
$adresses = explode("\n", file_get_contents('ips.txt'));
$nbr = count($adresses);
if ($nbr != 0)
     print ('<ul>'); //S'il y a des adresses, on ouvre une liste
foreach ($adresses as $key=>$value) {
        if ($value != '')
             print ('<li>' . long2ip($value) . '</li>'); //On affiche l'ip
}
if ($nbr != 0) 
     print ('</ul>'); //On ferme la liste s'il y a lieu
else
     print ('Aucune adresse IP bannie.'); //Personne de banni, on le signale
?>

Voilà pour la liste ! Intégrez-la à votre panneau d'administration et surtout, protégez-la. ;)

Débannir une adresse IP

Oui, c'est pratique de pouvoir débannir des adresses :p . Si pour en bannir nous devions ajouter une ligne au fichier ips.txt, pour en débannir nous devrons en retirer une. Nous allons utiliser la fonction str_replace() qui sert à remplacer un bout de chaîne par un autre dans un texte. Nous allons supposer que l'adresse IP est contenue dans la variable $_GET['ip'] sous sa forme numérique. C'est parti !

<?php
if (isset($_GET['ip'])) {
     $contenu_debut = file_get_contents('ips.txt');
     $contenu = str_replace($_GET['ip'] . "\n", '', $contenu_debut);
     
     $fichier = fopen('ips.txt', 'w');
     fwrite($fichier, $contenu);
     fclose($fichier);
     if ($contenu_debut == $contenu) 
          print ('Cette adresse IP n\'était pas bannie.');
     else
          print ('L\'adresse IP a été débannie.');
}
else
     print ('Vous n\'avez spécifié aucune adresse IP.');
?>

C'est maintenant terminé pour la gestion des adresses bannies. Dans la dernière partie, nous verrons comment interdire l'accès à ceux dont l'IP est sur la liste. :pirate:


Première solution : MySQL Interdire l'accès aux adresses retenues

Interdire l'accès aux adresses retenues

Seconde solution : un fichier texte

Jusqu'à présent, les adresses IP que nous avons relevées peuvent toujours accéder au site. Nous créerons donc une fonction is_ban($ip) qui retournera true dans le cas où l'adresse est bannie, et false si elle ne l'est pas. Pour commencer, on va créer un simple fichier HTML qui sera affiché en cas d'interdiction. Je vous en propose un, faites comme vous voulez :

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
        <head>
          <title>Vous n'êtes pas autorisé</title>
          <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
          <style type="text/css">
          #main {
               font-family: 'Trebuchet Ms', Serif;
               font-size: 13px;
               position: absolute;
               top: 50%;
               margin-top: -150px;
               height: 150px;
               left: 50%;
               margin-left: -300px;
               width: 600px;
          }
          .message {
               border: 1px solid #c1c1c1;
               padding: 10px;
               margin-bottom: 10px;
               height: 150px;
          }
          h1 {
               font-family: Arial, Serif;
               font-size: 30px;
               text-align: center;
          }
          </style>
        </head>
        
        <body>
            <div id="main">              
                 <div class="message">
                 <h1>Vous n'êtes plus autorisé</h1>
                 <p>L'accès à ce site vous est désormais interdit. Pour plus d'informations, contactez le webmaster.</p>
                 </div>
         </div>
        </body>
</html>

Vous avez le CSS en prime. :p

La fonction avec MySQL

Dans le cas de MySQL, nous allons simplement sélectionner les entrées où l'adresse IP correspond à celle du visiteur. Si on récupère plus de 0 entrées, on retourne true. Sinon on retourne false.

<?php
//Indique si une adresse est bannie
function is_ban($ip) {
     $ip = ip2long($ip);
     $query = mysql_query('SELECT * FROM ban WHERE ban_ip=\'' . $ip . '\'');
     $nbr = mysql_num_rows($query);
     
     if ($nbr == 0)
          return (false);
     else
          return (true);
}
?>

Dédiez un fichier entier à cette fonction et enregistrez-la où vous en avez envie. ;)

La fonction pour un fichier texte

Dans le cas d'un fichier texte, nous allons procéder d'une manière qui peut être discutée, car il en existe des tonnes. Nous allons utiliser la fonction strpos() qui nous retourne la position d'une chaîne dans une autre. Dans le cas où la première chaîne n'apparaît pas dans la seconde, la fonction retourne false. Prenez note qu'il est très important d'utiliser la comparaison de valeur et de type (===) car strpos retourne 0 si la position est 0.

<?php
//Indique si une adresse est bannie
function is_ban($ip) {
     return strpos(ip2long($ip) . "\n", file_get_contents('ips.txt')) === false ? false : true;
 
}
?>

Voilà pour le fichier texte, il ne nous reste plus qu'à utiliser cette fonction. :)

Traiter la fonction et afficher la page d'interdiction

Le code qui suit devra être placé sur toutes les pages dont vous désirez interdire l'accès aux adresses IP bannies. Vous pouvez aussi le placer dans un include que vous placerez sur toutes les pages de votre site. Dans le cas de MySQL, assurez-vous que votre connexion soit active au moment où vous exécutez la fonction. Bien entendu, si vous utilisez un système qui divise votre système en modules et que tout est basé sur la page index.php, vous êtes avantagés ici, car vous n'avez qu'à inclure le fichier sur cette page !

N'oubliez pas de placer le code qui suit avant tout code HTML, car sinon vous pourriez vous retrouver avec deux Doctype par exemple ;) :

<?php
//On inclut notre fonction pour avoir l'IP (modifiez le chemin) ;)
require ('libs/div/get_ip.php');
$ip = get_ip();
 
require ('libs/div/is_ban.php');
if (is_ban($ip)) {
     //L'IP est bannie, on affiche la page et on arrête le script
     readfile('interdiction.html');
     die();
}
?>

Je le répète, n'oubliez pas de modifier les chemins d'accès aux fichiers à inclure. ;)

Maintenant que vous avez un moyen efficace de bloquer l'accès à votre site, n'en abusez pas ! Par ailleurs, je tiens à vous préciser que la plus mauvaise idée qui pourrait vous traverser l'esprit, c'est de bloquer l'adresse IP du robot de Google. :-°

Ah ! J'allais oublier : il existe des solutions encore plus efficaces pour bloquer l'accès à un site web ou à un seul dossier du serveur. Vous pouvez notamment le faire à l'aide des fichiers .htaccess mais ça devient un peu plus difficile à gérer dynamiquement.


Seconde solution : un fichier texte