Bug très étrange - Sessions PHP

Petit nouveau ! | 4 Messages

30 nov. 2011, 20:13

Bonjour à tous !

Voilà je suis nouveau sur ce forum et j'espère y trouver l'aide que n'ai pu trouvé auprès de mon hébergeur (planethoster).
J'ai un site hébergé sur un VPS (dofusbook.net) qui traite d'un MMO français (Dofus).
Depuis 1 semaine, j'ai un problème très très embêtant sur ce site: lorsque les utilisateurs s'identifient (login + mdp), je crée une session PHP dans laquelle je stocke l'id et nom de l'utilisateur (jusque là rien de sorcier). Ensuite sur chaque page je fais un session_start() et je teste si l'id utilisateur existe dans la superglobale $_SESSION pour savoir si j'ai affaire à un visiteur ou à un utilisateur identifié (un simple if(isset($_SESSION['id']) en début de chaque page donc...). Cela marchait très bien seulement voilà: depuis 1 semaine, une fois l'utilisateur identifié, lors d'un changement de page il se retrouve connecté... sur une autre session ! Il a du coup accès aux infos personnelles d'un autre utilisateur, etc... ce qui est catastrophique. Sans parler des messages d'insultes sur le forum qui pullulent car certaines personnes savent qu'on ne saura pas les reconnaitre vu qu'elles se retrouvent connectées sur un compte qui n'est pas le leur...

Quelqu'un a t-il déjà eu ce genre de bug ? Vous êtes un peu mon dernier espoir sur ce problème, j'ai déjà tout essayé sans succès :cry:

Pour info ma config PHP pour les sessions est la suivante:
session.auto_start 0
session.bug_compat_42 1
session.bug_compat_warn 1
session.cache_expire 180
session.cache_limiter nocache
session.cookie_domain <empty>
session.cookie_lifetime 0
session.cookie_path /
session.entropy_file <empty>
session.entropy_length 0
session.gc_divisor 100
session.gc_maxlifetime 1440
session.gc_probability 1
session.name PHPSESSID
session.referer_check <empty>
session.save_handler files
session.save_path /home/<mon-vps>/session
session.serialize_handler php
session.use_cookies 1
session.use_only_cookies 1
session.use_trans_sid 0
url_rewriter.tags a=href,area=href,frame=src,input=src,form=,fieldset=

Merci d'avance pour vos réponses.
Modifié en dernier par dofusbook le 01 déc. 2011, 10:18, modifié 1 fois.

Mammouth du PHP | 2278 Messages

30 nov. 2011, 23:28

Je viens de constater que je suis passé de "visiteur inconnu" à Lord-Uly (30/11/2011 à 22h30) si ça peut aider.
je suppose que c'est bien là:
http://dofusbook.net/item/recherche.html
Je n'ai pas la moindre idée de la cause, mais je ferais afficher le contenu de $_SESSION, histoire de voir.
Vanitas vanitatum et omnia vanitas
Mes derniers livres :
Sauvez les Mots chez BoD,
Tous les chemins mènent à ROM chez BoD

Eléphant du PHP | 171 Messages

01 déc. 2011, 00:04

Salut,

Je te conseille vivement de vérifier la sécurité de ton site web en priorité et sur plusieurs points.
Met ton site en maintenance pour stopper tout accès au site et vérifie les points suivants :
- Validité des données dans la base de données
- Modification de tes requêtes SQL de façon à ce quel soit sécuriser (pour mysql_query, tu dois utiliser http://php.net/manual/fr/function.mysql ... string.php avant ta requête, si tu ne veux pas tout modifier tu peux aussi activer les magic_quotes)

Je te dis ca, car en faisant un petit tour sur ton site, j'ai remarqué qu'il y avait des failles SQL partout !
Si ton site fonctionnait très bien au niveau des sessions, je ne vois pas pourquoi subitement il ne fonctionnerait plus mis à part un piratage. (ou une situation non préparée et mal géré par le code que tu as produit)

Ensuite pour tes sessions, vérifie bien où est ce que tu stockes l'ID en session, à quel moment, de quel façon, vérifie bien les isset() et tout ça, de façon à ce que cette donnée ne soit bien définie qu'a la connexion et nulle part ailleurs. (sauf si le besoin est pour ton application)

Mais fait en priorité ce que je t'ai dit plus haut, vérifie bien que les données de ta DB ne sont pas corrompues et sécurise tes requêtes SQL.
Vérifie aussi par hasard si des fichiers qui ne devrait pas être présent sur ton serveur ne s'y serait pas installés.
Le bon jugement s'apprend par l'expérience qui s'acquiert en partie par le mauvais jugement.

Petit nouveau ! | 4 Messages

01 déc. 2011, 00:37

Merci pour vos réponses.
Concernant la sécurité SQL, je ne comprend pas pourquoi il y a des failles partout.
J'utilise pour toutes mes requêtes SQL une classe qui surcharge les méthodes PDO.

Voilà par exemple l'instanciation et les méthodes getLigne (pour faire un select) et query (pour faire un update, delete, insert):
<?php
  class SPDO {
    private $_PDOInstance 		= null;
    private static $_instance 	= null;
    protected $nEntree		= null;
    
    const DEFAULT_SQL_USER 	= "###";
    const DEFAULT_SQL_HOST 	= "###";
    const DEFAULT_SQL_PASS 	= "###";
    const DEFAULT_SQL_DTB 	= "###";
    
    /**
    * constructeur
    */
    private function __construct() {
	  try {
		$this->_PDOInstance = new PDO('mysql:dbname='.self::DEFAULT_SQL_DTB.';host='.self::DEFAULT_SQL_HOST,self::DEFAULT_SQL_USER ,self::DEFAULT_SQL_PASS);
		$this->_PDOInstance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		$this->_PDOInstance->query("SET NAMES utf8");
      } catch (PDOException $e) {
        echo "Connexion échouée : ".$e->getMessage();
        exit();
      }
    }
	
    /**
    * destructeur
    */
    public function __destruct() {
        $this->_PDOInstance = NULL;
    }
    
    public static function getInstance() {
      if(is_null(self::$_instance)) {
        self::$_instance = new SPDO();
      }
      return self::$_instance;
    }
    
    /**
    * @param string rR : requête sql préparé (avec les ?)
    * @param array aArg : données à remplacer dans la requête sql
    * @return array contenant les informations demandé à sql
    * @permet de récupérer une ligne de la base de données
    */
    public function getLigne($rR, $aArg = array()) {
	  try {  
        $oPDOStatement = $this->_PDOInstance->prepare($rR);
        if(is_object($oPDOStatement)) {
            $oPDOStatement->execute($aArg);
            $result = $oPDOStatement->fetch(PDO::FETCH_ASSOC);
            return $result;
        }
      } catch (PDOException $e) {
        echo "Erreur avec la base de données : ".$e->getMessage();
      }  
    }

    /**
    * @param string rR : requête sql préparé (avec les ?)
    * @param array aArg : données à remplacer dans la requête sql
    * @permet de faire une requête sans retour (update,insert,delete...)
    */
    public function query($rR, $aArg = array()) {
	  try {
        $oPDOStatement = $this->_PDOInstance->prepare($rR);
        if(is_object($oPDOStatement)) {
          $result = $oPDOStatement->execute($aArg);
          return ($result);
        }
      } catch (PDOException $e) {
        echo "Erreur avec la base de données : ".$e->getMessage();
      }      
    }
Ai-je fais quelque chose d'incorrect dans cette classe ?

Eléphant du PHP | 171 Messages

01 déc. 2011, 00:47

La classe n'a pas de problème en elle même. Mais tu l'utilises peut être mal ? Peux tu nous montrer la partie de connexion, avec la requête, et ou tu définis la session de l'utilisateur.
Le bon jugement s'apprend par l'expérience qui s'acquiert en partie par le mauvais jugement.

Petit nouveau ! | 4 Messages

01 déc. 2011, 01:35

Oui bien sûr, voilà le code de connexion:
public function checkLogin() {
      // Vérification de la validité des champs
      if (!preg_match("#^[a-zA-Z][a-zA-Z0-9._-]{2,20}$#", $this->nom))
        return 'E110';
      if (!preg_match("#^[A-Za-z0-9._\-+=*/%,?;:!$@&]{6,20}$#", $this->pass))
        return 'E115';

      return 0;
}

public function connecter(User $user) {
      $rc = $user->checkLogin();
      if (!empty($rc)) { return $rc; exit(); }
      
      $dbUser = $this->findOneWhere('nom = ?', array($user->getNom()));
	      
      // Si aucun utilisateur n'a été trouvé
      if (is_null($dbUser)) {
        return 'E100'; exit();
      }
      
      // Si le compte n'a pas été activé
      if($dbUser->getActif() == 0) {
        return 'E101'; exit();
      }  
      
      // Vérification du banisssement
      if($dbUser->getRang() == 0) {
        return 'E102'; exit();
      }
      
      // Vérification du mot de passe
      if ($dbUser->getPass() !== sha1($user->getPass())) {
        return 'E103'; exit();
      }
      
      $this->rememberMe($dbUser);
      
      // Redirection de l'utilisateur
      header('Location: /membre/compte.html');
}

public function rememberMe(User $user) {
      if ($user->getRang() != 0) {
      	$dbh = SPDO::getInstance();
        
        // maj membres connectés
        $ip = ip2long($_SERVER['REMOTE_ADDR']);
        $query = 'DELETE FROM t_online WHERE online_ip = ?';
        $dbh->query($query, array($ip));
        
        // sauvegarde de la session
        $_SESSION['ID_UTILISATEUR'] = $user->getNum();
        $_SESSION['NOM_UTILISATEUR'] = $user->getNom();
      }
}

public function findOneWhere($whereClause, array $values = array()) {
      $query = 'select t_membre.*, guilde_id, guilde_nom, guilde_avatar, online_ip, expiration
                from t_membre
                left join t_guilde on t_membre.guilde = t_guilde.guilde_id
                left join t_online on t_membre.nom = t_online.online_user';
      $query .= ' where '.$whereClause;
      $dbh = SPDO::getInstance();
      $row = $dbh->getLigne($query, $values);
      
      if (empty($row))
        return null;
      
      $user = new User();
      $user->hydrateFromArray($row);

      return $user;
}

Eléphant du PHP | 171 Messages

01 déc. 2011, 23:41

En regardant ton code je vois que les requêtes sont bien faites comme il faut, donc mon injection SQL n'en était pas une ? :-k Pourtant à un moment je me rappelle avoir choisi un nom d'utilisateur, utilisait ' OR '1'='1 comme password et op j'étais connecté mais je me rappelle plus si j'étais sur le compte choisi.

Si ce n'est pas de là que viens le problème alors...

Je te propose de faire des affichages de ton tableau de SESSION dans ta page d'index pour que celui ci soit visible partout. Tu te connectes normalement, tu regardes ce qui en résulte. Ensuite navigue dans ton site normalement.
As tu réussi à capter à quel moment l'utilisateur se retrouvait avec le compte de quelqu'un d'autre ? Si oui reproduis cet action et regarde ce qui en résulte dans ton tableau de session, il te sera facile de débugger à ce moment là. Si tu ne trouves pas tu vas devoir chercher jusqu’à que ça te fasse le bug décrit.. :langue: Essaye ce qu'a dit sirakawa vers la page de recherche à ce qu'il parait il y a un problème. Quand tu as repérer d'où ça vient tu analyses ton code correspondant au chargement de la page ou de l'action ou encore des données que tu as entrées.

Depuis combien de temps ton site est il en ligne ? Est ce que ce bug est apparu subitement ou y était t'il déjà avant ? Si c'est récent, regarde si ton hébergeur n'a pas fait de mise à jours et que tu aurais pas des fonctions défaillantes par hasard.
Je te propose plusieurs choses à faire mais c'est ce que je ferais à ta place.
Le bon jugement s'apprend par l'expérience qui s'acquiert en partie par le mauvais jugement.

Avatar du membre
Modérateur PHPfrance
Modérateur PHPfrance | 8758 Messages

02 déc. 2011, 10:44

perso je viens de suivre le lien de Sirakawa et direct je tombe sur
<br />
<b>Warning</b>: session_start() [<a href='function.session-start'>function.session-start</a>]: open(/home/dofus/sessions/sess_a03a6ca6e0740b4c41eb4504e656c150, O_RDWR) failed: Permission denied (13) in <b>/home/dofus/public_html/_global.php</b> on line <b>24</b><br />
<br />
<b>Warning</b>: session_start() [<a href='function.session-start'>function.session-start</a>]: Cannot send session cookie - headers already sent by (output started at /home/dofus/public_html/_global.php:24) in <b>/home/dofus/public_html/_global.php</b> on line <b>24</b><br />
<br />
<b>Warning</b>: session_start() [<a href='function.session-start'>function.session-start</a>]: Cannot send session cache limiter - headers already sent (output started at /home/dofus/public_html/_global.php:24) in <b>/home/dofus/public_html/_global.php</b> on line <b>24</b><br />
<br />
<b>Warning</b>: Cannot modify header information - headers already sent by (output started at /home/dofus/public_html/_global.php:24) in <b>/home/dofus/public_html/_global.php</b> on line <b>26</b><br />
Donc heu ben y a un gros soucis à l'utilisation des sessions :)

mais il me dit bien que je un "Bonjour visiteur mystérieux !" :mrgreen:

le problème étant sur toutes les pages, vérifie que tu n'a pas un problème de droit sur "/home/<mon-vps>/session" car par défaut il s'agit de l'utilisateur qui à "lancer" apache qui doit avoir droit sur ce répertoire.

Perso je ne te conseil pas du tout ce type de fonctionnement, ce répertoire devrait être le /tmp ou un tmp d'apache mais en aucun cas un répertoire d'un utilisateur ;)


@+
Il en faut peu pour être heureux ......

Petit nouveau ! | 4 Messages

06 déc. 2011, 17:48

Encore merci pour toutes vos réponses.
@moogli : ok j'ai pris note, je vais changer le répertoire des sessions.
En fait tu as accédé au site au moment ou je faisais des tests pour voir si le soucis venait du répertoire partagé...

Sinon un grand merci aussi à Skw33d et sirakawa pour leur aide.
Pour clore ce sujet voilà en fait d'où venait mon problème: l'hébergeur a installé un module de cache sur le VPS, sans me prévenir.
J'avais noté en plus du bug des sessions des comportements bizarre au niveau des rafraichissements des pages (par ex j'étais connecté, puis en changeant de page je me retrouvais en déconnecté mais en forçant un refresh avec la touche MAJ enfoncée, j'apparaissais de nouveau connecté...)
Du coup cela m'a interpellé et j'ai demandé à l'hébergeur si ils avaient installé / mis à jour quelque chose sur le VPS, et ils m'ont dit que la seule modification était l'ajout d'un module de cache. Je leur ai demandé de le désactiver et ... miracle tout est rentré dans l'ordre ...
A priori ce module de cache mettait le bazar au niveau des sessions PHP.

Voilà le sujet est clos, encore merci pour vos réponses.

ViPHP
xTG
ViPHP | 7331 Messages

06 déc. 2011, 17:52

Il met pas le bazar au niveau des sessions mais des réponses aux requêtes.
Vu que la requête est la même il renvoi la dernière réponse mise en cache (donc peut être une réponse générée suite à une session).