Chinese (People's Republic of China)  English  Français


Supinfo-Projects.com
Tous les projets des élèves ingénieurs de Supinfo



Projets
  Dernier Projet
  Les plus populaires
  Tous les Projets

151 Visiteurs
3168 Projets


My Supinfo-Projects

   Connectez-vous
   Créez un Compte


Synopsis

   27 Visites
   Note INTERNET : 20
    (2 Votants)
   0 Commentaires

   Lire l'article

Evaluez cet article

20
18
16
14
12
10
8
6
4
2
0


Commentez cet article

Auteur :

Email :

Votre commentaire :



 
2006 - Note de Synthèse Stage
Backoffice sécurisée en PHP
[30 mn de lecture - paru le 11/5/2006 6:13:45 PM - Public : Confirmé]

Auteur

45925Fabien AGRANIER
Elève-Ingénieur Supinfo Bourgogne
Promotion SUPINFO 2008

   Lui écrire
   Tous les projets de cet auteur
   Le mini-CV de cet auteur

1. Sécurisation de la session

1.1. Avant propos

Toutes les backoffices permettent de réaliser un certain nombre d'opérations sur des données personnelles ou partagées. Toutes ces opérations sont basées sur la confiance que porte l'application envers son utilisateur. En effet, une fois un utilisateur identifié, il acquiert auprès de l'application un certain degré de confiance en fonction de son niveau de privilège (administrateur, modérateur, membre, etc..)

Si, au cours de la session de l'utilisateur identifié, son identité est usurpée ou si par certains procédés, l'utilisateur se voit conduit a initier des opérations à son insu, la confiance que l'application porte à l'utilisateur met alors en péril l'intégrité de ses données.

De plus, l'arrivée massive du concept Web 2.0 conduit à l'augmentation des fonctionnalités des applications et de leur interactivité avec les utilisateurs, cela accroît donc dans la foulée le nombre potentiel de failles et d'exploitations possibles.

1.2. Table de la base de données

Dans cet article, nous allons utiliser la table suivante pour gérer nos utilisateurs.
Le champ id est un numéro automatique généré à la création d'utilisateur.
Le champ login est une chaîne de moins de 30 caractères.
Le champ password est une chaîne de 40 caractères fixes car il contiendra les hash SHA1 des passwords des utilisateurs.
Le champ profile définit le niveau de privilège de l'utilisateur et prend les valeurs 'admin' ou 'member'.

CREATE TABLE Users(
	id INT NOT NULL PRIMARY KEY AUTO_INCREMENT ,
	username VARCHAR( 30 ) ,
	password CHAR( 40 ),
	email VARCHAR( 100 ),
	profile ENUM('admin','member')
);

1.3. Mécanismes de base de la backoffice/session

Nous allons utiliser pour cette application la classe suivante. Toutes les fonctionnalités additionnelles de sécurité viendront se greffer sur cette base :

/*
 *    Classe Backoffice
 *    Englobe la gestion des sessions et de l'authentification
 */
class Backoffice
{        
    /*
     *    Méthode publique init()
     *    Vérifie que les entêtes HTTP ne sont pas déjà envoyée et qu'une connexion mysql est ouverte
     *    Initialise la session PHP
     *    Prend en charge la soumission du formulaire de login
     */
    public function init()

    {
        if(headers_sent()) throw new Exception("Les entêtes HTTP ont déjà été envoyées"1);
        if(!@mysql_real_escape_string("1")) throw new Exception("Aucune connexion MySQL n'a été ouverte");
        

        // Initialise la session php
        session_start();
        
        // Authentifie l'utilisateur si le formulaire a été soumis

        if(isset($_POST['login_user']) && isset($_POST['login_pass'])) {
            if(!$this->login($_POST['login_user'], $_POST['login_pass'])) throw new Exception("Le nom d'utilisateur ou le mot de passe est incorrect."2);
        } else if(isset($_GET['deco'])) $this->close();
    }
    

    /*
     *    Méthode privée login()
     *    Authentifie une utilisateur dans la backoffice à partir du couple username/password
     *    Valeur de retour : booléen
     */
    private function login($username$password)
    {
        $req "SELECT id, email, profile FROM Users WHERE username = '".mysql_real_escape_string($username)."' AND password = '".mysql_real_escape_string(sha1($password))."'";

        $sql mysql_query($req) or die(mysql_error());
        $userdata mysql_fetch_array($sql);
        if($userdata === false) return false;

        $_SESSION['backoffice_user'] = new User($userdata['id'], $login$userdata['email'], $userdata['profile']);

        return true;
    }

    /*    
     *    Méthode privée close()
     *    Ferme la session active
     */
    
private function close() {
        @
session_unset();
        @
session_destroy();
    }          
/*      *    Méthode publique restrictAccess()      *    Restreint l'accès aux utilisateurs identifiés dont le rôle fait partie de la liste passée en paramètre      *    Argument facultatif $role : chaine ou tableau de chaines contenant les noms des rôles autorisés      *    Renvoit des exceptions      */     public function restrictAccess($profile null)     {         // Si la variable currentUser de la session est vide, l'utilisateur n'est pas authentifié         if(is_null($_SESSION['backoffice_user'])) throw new Exception("Vous n'êtes pas authentifié"11);         // Si $role est null, aucun profil spécial n'est requis, on s'arrête là         if(is_null($profile)) return;         // On transforme le role en tableau si ce n'est pas déjà le cas         if(!is_array($profile)) $profile = array($profile);         // Si le role de l'utilisateur authentifié ne fait pas partie de la liste, on renvoit une erreur         if(!in_array($_SESSION['backoffice_user']->profile$profile)) throw new Exception("Vous n'avez pas les privilèges suffisants"12);     }          /*      *    Méthode publique getUser      *    Renvoit un objet User représentant l'utilisateur authentifié (ou null)      */     public function getUser()     {          return !isset($_SESSION['backoffice_user']) ? null : clone $_SESSION['backoffice_user'];     } } ?>

Nous utiliserons aussi une class User pour encapsuler les données de nos utilisateurs :

/*
 *    Classe User
 *    Encapsule les différentes informations d'un utilisateur
 */
class User
{
    public 
$id;
    public 
$name;
    public 
$email;
    public 
$profile;
    
    public function 
__construct($id$name$email$profile) {
        
$this->id        $id;
        
$this->username    $name;
        
$this->email    $email;
        
$this->profile    $profile;
    }
}

1.4. Connexion sécurisée

La connexion sécurisée permet de rendre impossible l'interception des informations transmises entre le navigateurs des utilisateurs et le serveur de la backoffice. Les informations sensibles sont les mots de passe transférés lors de l'identification et les identifiants de sesssions transférés tout au long de la session.

Voici le code permettant de refuser l'accès si la connexion n'est pas sécurisée ou trop peu sécurisée:

if(!isset($_SERVER['SSL_PROTOCOL']) || !in_array($_SERVER['SSL_PROTOCOL'], Array('SSLv3','TLSv1'))) 
throw new 
Exception("La connexion n'est pas ou pas assez sécurisée");

1.5. Limitation du nombre de tentatives de connexions

Limiter le nombre de tentatives de connexion, permet d'éviter les attaques par "brut force". Lorsque le nombre de tentatives est atteint il est possible de bloquer soit l'IP soit le compte de l'utilisateur. Dans cet exemple, nous allons bloquer les adresses IP.
Afin de ne prendre en considération le fait que de nombreuses personnes peuvent être derrière le même proxy, c'est la couple adresse IP (proxy) et adresse IP "forwardée" qui va pris en compte.

Pour gérer ce module, nous allons ajouter une nouvelle table dans la base de données pour garder une trace des accès et de leur nombre.

CREATE TABLE ipaccess (
	access_ip VARCHAR(15) NOT NULL,
	access_proxy VARCHAR(15) NOT NULL,
	access_date DATETIME,
	access_nb TINYINT,
	PRIMARY KEY (access_ip, access_proxy)
);

Nous créons aussi une nouvelle classe gérant cette fonctionnalité, afin de simplifier son intégration dans notre classe de backoffice :

class Ipaccess
{
    private   $ip;
    private   $proxy;
    public    $nbtry;
    public    $maxtry;
    public    $blocktime;
    
    public function __construct($maxtry 5$blocktime 5)
    {
        $this->proxy        mysql_real_escape_string(isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : '0.0.0.0');
        $this->ip            mysql_real_escape_string($_SERVER['REMOTE_ADDR']);
        $this->maxtry        $maxtry;
        $this->blocktime    $blocktime;
        
        // Nettoie la base des vieux enregistrements
        mysql_query('DELETE FROM ipaccess WHERE access_date < (NOW() - INTERVAL '.$this->blocktime.' MINUTE)');
        // Récupère le nombre d'accès
        $sql mysql_query("SELECT access_nb FROM ipaccess WHERE access_ip = '{$this->ip}' AND access_proxy = '{$this->proxy}'");

        $this->nbtry = ($res mysql_fetch_array($sql)) ? $res[0] : 0;
    }
    
    public function isInRange() {
        return $this->nbtry $this->maxtry;
    }
    

    public function increment() {
        if($this->nbtry 0)
            mysql_query("UPDATE ipaccess SET access_nb = access_nb + 1, access_date = NOW() WHERE access_ip = '{$this->ip}' AND access_proxy = '{$this->proxy}'");
        else
            mysql_query("INSERT INTO ipaccess (access_ip, access_proxy, access_date, access_nb)
                        VALUES ('{$this->ip}', '{$this->proxy}', NOW(), 1)");
        $this->nbtry++;
    }
    
    public function clean() {
        mysql_query("DELETE FROM ipaccess WHERE access_ip = '{$this->ip}' AND access_proxy = '{$this->proxy}'");
    }
}

Ainsi, nous pouvons réécrire la portion de code de la méthode Backoffice::init() prenant en charge le login :

if(isset($_POST['login_user']) && isset($_POST['login_pass'])) {
    
$ipacc = new Ipaccess();
    if(!
$ipacc->isInRange()) throw new Exception("Toutes vos tentatives d'identification ont échouées, vous ne pouvez plus vous identifier pendant ".$ipacc->blocktime." minutes.");
    if(!
$this->login($_POST['login_user'], $_POST['login_pass'])) {
        
$ipacc->increment();
        throw new 
Exception("Le nom d'utilisateur ou le mot de passe est incorrect.<br/>Tentative {$ipacc->nbtry}/{$ipacc->maxtry}"3);
    }
    
$ipacc->clean();
}

1.6. Durée de validité de la session

Afin de garantir la sécurité de vos utilisateurs, il convient de limiter la durée des sessions sur les backoffices. PHP ne garde en mémoire les sessions que lors d'un certain laps de temps définit par la directive de configuration session.gc_maxlifetime qui est par défaut de 1440 secondes soit 24 minutes. Ce temps est relativement grand, nous allons donc gérer en interne une limitation plus courte sans modifier la configuration de PHP.

Pour gérer ce paramètre, nous allons utiliser une variable de session stockant le timestamp du dernier accès à l'ouverture de session, dans la méthode Backoffice::login

$_SESSION['backoffice_lastaccess'] = time();

Enfin, dans la méthode Backoffice::restrictAccess, après la vérification de l'existence de la variable de session backoffice_user, nous allons rajouter les lignes suivantes qui vont cloturer la session si celle-ci a expirée.

// Vérifie que la session n'a pas plus de 10 minutes
if(time() - $_SESSION['backoffice_lastaccess'] > 600) {
    
$this->close();
    throw new 
Exception("Votre session a expiré"13);
}

1.7. Contrôle du client (ip, navigateur)

Afin d'éviter tout vol de session, nous allons enregistrer à l'ouverture de la session quelques informations sur le client qui ne sont pas censées changer au cours de la connexion. Nous prendrons pour base l'adresse IP, l'adresse du proxy, et le navigateur utilisé.

Pour cela nous allons créer une nouvelle classe qui va nous permettre de stocker puis comparer facilement ces informations.

/*
 *    Classe Client
 *    Encapsule les différentes informations du client
 */
class Client
{
    public 
$ip;
    public 
$proxy;
    public 
$browser;
    public function 
__construct() {
        
$this->browser    $_SERVER['HTTP_USER_AGENT'];
        
$this->ip        $_SERVER['REMOTE_ADDR'];
        
$this->proxy    = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : '0.0.0.0';
    }
}

Nous allons enregistrer cet objet dans une variable de session dans la méthode Backoffice::login() afin de pouvoir s'y référer tout au long de la session.

$_SESSION['backoffice_client'] = new Client();

Ensuite, nous allons comparer cet objet à celui généré lors de chaque appel de Backoffice::restrictAccess. En cas d'incohérence, nous cloturons la sessions et refusons l'accès au document.

// Vérifie que le client est toujours le même
if($_SESSION['backoffice_client'] != new Client()) {
    $this->close();
    throw new Exception("Pour des raisons de sécurité, votre session a été cloturée, merci de vous ré-identifier"14);
}

1.8 Changement de l'identifiant de session

Enfin, nous allons changer l'identifiant de session après l'authentification de l'utilisateur dans le but de rendre inutilisable l'ancien identifiant de session dans le cas ou il ait été intercepté.

Pour cela nous rajoutons le code suivant à la fin de la méthode Backoffice::login()

// Regénération du numéro de session et effacement de la session précédente
session_regenerate_id(true);


Articles de la même catégorie

 Pages : Top


18 Visites
0 Commentaires
8 days - Jeu Flash et PHP
[10 mn de lecture - paru le 11/5/2006 6:09:24 PM - Public : Débutant]

En savoir plus


7 Visites
0 Commentaires
Build Excel documents with XSLT
[20 mn de lecture - paru le 11/5/2006 5:54:29 PM - Public : Confirmé]

En savoir plus


13 Visites
0 Commentaires
Générer des documents Excel avec XSLT
[20 mn de lecture - paru le 11/5/2006 5:53:31 PM - Public : Confirmé]

En savoir plus

   Tous les Articles


SUPINFO Training Center peut vous proposer une formation ...

   Devenez Ingénieur Système Microsoft en 35 jours avec SUPINFO Training Center
   Devenez Certifiés Cisco en 13 jours avec SUPINFO Training Center
   Devenez Administrateur Système Microsoft avec SUPINFO Training Center
   Devenez Développeur Microsoft .NET en 13 jours avec SUPINFO Training Center



Powered by Campus-Booster Technology
Conditions d'utilisation & Copyright | Respect de la vie privée
© Copyright 1965-2006 Supinfo Paris, Paris Academy of Computer Science
Supinfo, Ecole Supérieure d'Informatique et Paris Academy Of Computer Science are trade marks.
23, rue de Château LANDON - 75010 PARIS - Phone : +33 (0) 153359 700 Fax : +33 (0) 153359 701

Web site autided by :