Valeurs POST ou GET et rechargement de pages.

Alf
Eléphanteau du PHP | 24 Messages

28 juin 2007, 23:07

Par exemple,

Une page me permet de remplir un formulaire, à la validation de celui-ci une requette (UPDATE, DELETE, INSERT...) alimentée par POST ou GET modifie des infos en base de donnée, le formulaire remet à disposition.

Comment eviter que ces requettes soient rejouées si un utilisateur fait des reload de pages ou des retours arriéres.

Je pense n'avoir rien oublié, merci.
Pas pro du dev, mais pas débutant non plus, je suis attentif à la qualité de mon code dans la mesure de mes connaissances.

ViPHP
ViPHP | 2287 Messages

28 juin 2007, 23:17

Une façon de faire est de rajouter à tes formulaires à protéger un champ caché comme ceci :
echo '<input type="hidden" name="time" value="'.time().'" />';
Le principe est d'associer au formulaire une valeur de temps qui permet de l'identifier : en cas de rechargement de la page de soumission (ou de retour en arrière qui passerait par cette page), cette valeur fera partie des valeurs soumises, et donc ne changera pas, ce qui permettra de tester son existence en base et donc de savoir si le formulaire a déjà été soumis.

Il faudra ajouter un champ de type entier à ta table en base pour stocker cette valeur de temps. Il faudra également ajouter, juste avant l'INSERT ou l'UPDATE que tu réalises en base lors de la soumission du formulaire, un test sous la forme d'un SELECT qui va regarder si la valeur de temps soumise avec le formulaire existe déjà en base et laisse la requête s'éxécuter seulement si ce n'est pas le cas. Bien sûr, dans le cas d'un UPDATE, il faudra au passage mettre à jour ce champ également.

Pour le DELETE (et sans doute l'UPDATE aussi, selon le cas...), le problème ne devrait pas se poser si ta requête est bien faite (en lui fournissant une valeur qui identifie sans ambiguïté l'enregistrement à supprimer, il ne pourra pas être supprimé deux fois...).
if(!@work()){ Nespresso(); } else { what(); }
______________________________

Mammouth du PHP | 804 Messages

29 juin 2007, 02:20

moi je fais une redirection afin de faire annuler les variables et eviter les actualisations, mais je ne suis pas sur de bien faire les choses .

après la requete
header("Location: lapageformulaire.php");
je vais me pencher sur la technique de calimero quand même :wink:

Alf
Eléphanteau du PHP | 24 Messages

29 juin 2007, 20:46

Calimero
dogmongo

Merci pour vos suggestions,

Je pense essayer plus la technique de calimero en me faisant une petite fonction qui me sortira un boolean direct pret au test.
je me demande si l'usage du header eviterait le probléme pour 2 retours en arriéres ou plus, j'ai besoin d'y reflechir, je n'utilise pas souvent les en-têtes HTTP.

Mon appli va gerer l'argent des membres d'une asso, elle peut être lente mais doit être 100% fiable là dessus.
Pas pro du dev, mais pas débutant non plus, je suis attentif à la qualité de mon code dans la mesure de mes connaissances.

ViPHP
AB
ViPHP | 5818 Messages

29 juin 2007, 21:59

La méthode de dogmongo est de loin la plus simple et fonctionne quelque soit le nombre de retour en arrière ou rafraichissement que fait le visiteur.

Administrateur PHPfrance
Administrateur PHPfrance | 3088 Messages

29 juin 2007, 22:24

Hmm, s'il s'agit de gérer de l'argent alors il vaut mieux implémenter quelque chose de sécurisé. Par exemple, pour chaque formulaire créer un enregistrement dans une table SQL avec une clé unique (un INT UNSIGNED AUTO_INCREMENT par exemple) ainsi qu'un "mot de passe" quelconque généré aléatoirement (un autre INT dont la valeur est tirée par mt_rand() par exemple) et un indicateur d'utilisation. Pour chaque formulaire, tu utilises la méthode POST et tu ajoutes des champs "hidden" avec le numéro de jeton et le mot de passe. À réception du formulaire, tu vérifies que le jeton existe, que le mot de passe correspond et que le jeton n'a jamais été utilisé. Et après traitement tu mets à jour le jeton pour indiquer qu'il a été utilisé.

De cette façon tu garantis qu'il est impossible de réexécuter le même formulaire, même par inadvertance. Tu garantis également qu'il est impossible de créer un faux formulaire sur un autre site pour faire du phishing.

ViPHP
AB
ViPHP | 5818 Messages

29 juin 2007, 23:23

Tu garantis également qu'il est impossible de créer un faux formulaire sur un autre site pour faire du phishing.
Je ne comprends pas cette dernière phrase. Je pensais que les pirates qui utilisent cette méthode faisaient une copie graphique du site cible mais qu'ils maitrisaient totalement le traitement des données puisqu'on est réellement sur leur site. Non?

Administrateur PHPfrance
Administrateur PHPfrance | 3088 Messages

29 juin 2007, 23:38

Le terme phishing n'est peut-être pas le plus approprié en effet. Concrètement, ce à quoi je pense serait de créer un site ressemblant ou non à l'original et y mettre un formulaire à destination du site légitime et qui déclencherait le transfert de fond ou l'action qu'Alf essaie de protéger. Dans le temps il était même possible de mettre un tel formulaire dans une frame invisible et utiliser javascript pour l'envoyer sans intervention humaine, mais je pense que c'est interdit par les mesures anti-phishing aujourd'hui (il doit probablement y avoir une confirmation, je n'ai pas testé).

Code : Tout sélectionner

<form action="http://le_site_original" method="post"> <input type="hidden" name="montant" value="1000000€"> <input type="hidden" name="destination" value="moi"> <input type="submit" value="Cliquez ici si vous aimez la glace au chocolat"> </form>

Alf
Eléphanteau du PHP | 24 Messages

03 juil. 2007, 08:20

hop ! je publie ma solution qui semble bien marcher.

Placer db_veri_form() dans le formulaire et db_veri_form($_POST['entrant']) comme condition de traitement du formulaire dans la page action de celui-ci.
  //Securisation des formulaires
	function db_veri_form($jeton='') 
  {
    if ($jeton=='') 
    {
      $jeton=time()."-".mt_rand(100000000,999999999);
      $this->db_exec("INSERT INTO entrant(ip,jeton) VALUES ('".$_SERVER['REMOTE_ADDR']."','".$jeton."');"); //+- mysqli_query
      print '<input type="hidden" name="entrant" value="'.$jeton.'">';
      return $jeton;
     } else 
    {
      $this->db_select("SELECT ip,jeton FROM entrant WHERE jeton='".$jeton."';"); //+- mysqli_query (rend un tableau)
      if ($this->req[0]==array(ip=>$_SERVER['REMOTE_ADDR'],jeton=>$jeton)) 
      {
        $this->db_exec("UPDATE entrant SET jeton='".time()."' WHERE jeton='".$jeton."';"); //+- mysqli_query
        return true;
      } else 
      {
        return false;
      }
    }
  }
La table correspondante est

Code : Tout sélectionner

CREATE TABLE `entrant` ( `id` int(10) unsigned NOT NULL auto_increment, `jeton` varchar(20) default NULL, `ip` varchar(15) default NULL, PRIMARY KEY (`id`) )
Modifié en dernier par Alf le 03 juil. 2007, 22:07, modifié 3 fois.
Pas pro du dev, mais pas débutant non plus, je suis attentif à la qualité de mon code dans la mesure de mes connaissances.

Administrateur PHPfrance
Administrateur PHPfrance | 3088 Messages

03 juil. 2007, 18:03

As-tu essayé de contourner ta protection du point de vue d'un attaquant ? Je pense que si l'attaquant fournit un jeton égal à 0, au lieu de vérifier le jeton ta fonction en générera un, ce qui équivaudrait à TRUE dans une condition if. Je te recommande donc fortement de séparer les fonctions de génération et vérification.

De plus, d'après le source posté il semblerait que $jeton soit vulnérable à de l'injection SQL.

Alf
Eléphanteau du PHP | 24 Messages

03 juil. 2007, 22:02

Effectivement :o une value="" permettait de valider le formulaire à cause du "return $jeton" qui n'avait de plus pas de raison d'etre (un reste de debug) !!!
Je l'ai donc supprimé. La premiére partie ne renvoi plus rien, j'ai vérifié.
Moi qui pensait n'avoir à vérifier que ce qui est saisi par l'utilisateur et je prend conscience qu'il me faudra vérifier tout ce qui est envoyé par un formulaire saisi ou nom par l'utilisateur.
Je vais adapter ma fonction de vérification de formulaires pour tout valider systématiquement.
  //Securisation des formulaires
	function db_veri_form($jeton='') 
  {
    if ($jeton=='') 
    {
      $jeton=time()."-".mt_rand(100000000,999999999);
      $this->db_exec("INSERT INTO entrant(ip,jeton) VALUES ('".$_SERVER['REMOTE_ADDR']."','".$jeton."');"); //+- mysqli_query
      print '<input type="hidden" name="entrant" value="'.$jeton.'">';

     } else 
    {
      $this->db_select("SELECT ip,jeton FROM entrant WHERE jeton='".$jeton."';"); //+- mysqli_query (rend un tableau)
      if ($this->req[0]==array(ip=>$_SERVER['REMOTE_ADDR'],jeton=>$jeton)) 
      {
        $this->db_exec("UPDATE entrant SET jeton='".time()."' WHERE jeton='".$jeton."';"); //+- mysqli_query
        return true;
      } else 
      {
        return false;
      }
    }
  }
Pas pro du dev, mais pas débutant non plus, je suis attentif à la qualité de mon code dans la mesure de mes connaissances.

ViPHP
AB
ViPHP | 5818 Messages

03 juil. 2007, 22:44

Moi qui pensait n'avoir à vérifier que ce qui est saisi par l'utilisateur et je prend conscience qu'il me faudra vérifier tout ce qui est envoyé par un formulaire saisi ou nom par l'utilisateur.
CQFD pour une sécurité maximum :wink:

Mais bon tous les formulaires n'ont heureusement pas besoin de ce type de vérification.
Cela dit, en complément (ou remplacement), à la dernière étape de la validation d'une commande, tu peux aussi faire afficher pour vérification par l'utilisateur, le résultat (non modifiable) du formulaire qui aura été enregistré dans la bdd.

Eléphanteau du PHP | 33 Messages

08 juil. 2007, 23:56

Concernant ces problème de sécurité, je voulais savoir si je fais un usage abusif de settype() ou si c'est normal? :D

je suis sur qu'un settype ($var, 'integer') est forcé (pour des raisons de sécurité évidente du type liée a "5 est la version de PHP"), mais settype($var, 'string') et autre, je suis moins sur.

Toujours dans le domaine de la sécurité:
si j'ai un champ de sql qui fait une taille de, disons, 100.

est-ce que je dois faire un test strlen avant ou après avoir échappée ma chaine?
(en d'autre terme, si l'utilisateur entre une chaine de 100 caractère qui serons tous échappés, cela fera une taille de 200 caractères au final. Mais est-ce que les caractère sont stockés "échappés", ou est-ce que cela est enlevé a la requète?)

Et concernant les headers, est-ce que leur execution est coté serveur ou coté client?
C'est-a-dire, est-ce qu'il est suffisant de faire un header('location: adresse_ou_tu_degage.php') si je détecte une erreur, ou est-ce que en parametrant son explorateur, le client pourra forcer mon script a continuer de s'exécuter?

Je vous pose ces questions, car elles ne sont pas toujours évidente :D

aussi important: quand vous vérifiez une variable, vérifiez son existence, son contenu, mais aussi son type.

Trop souvent, il suffit de faire un formulaire en remplaçant name="nom_var" par name="nom_var[]" pour obtenir le message d'erreur tant convoités (parce que contenant plein d'informations)

Voila, c'est tout ce que je voulais dire :D

ViPHP
ViPHP | 5924 Messages

09 juil. 2007, 01:14

Toujours dans le domaine de la sécurité:
si j'ai un champ de sql qui fait une taille de, disons, 100.

est-ce que je dois faire un test strlen avant ou après avoir échappée ma chaine?
(en d'autre terme, si l'utilisateur entre une chaine de 100 caractère qui serons tous échappés, cela fera une taille de 200 caractères au final. Mais est-ce que les caractère sont stockés "échappés", ou est-ce que cela est enlevé a la requète?)
Les caractères supplémentaires sont ignorés, seul le compte de caractères entre dans le champ.
Et concernant les headers, est-ce que leur execution est coté serveur ou coté client?
C'est-a-dire, est-ce qu'il est suffisant de faire un header('location: adresse_ou_tu_degage.php') si je détecte une erreur, ou est-ce que en parametrant son explorateur, le client pourra forcer mon script a continuer de s'exécuter?
PHP n'est pas sensé s'arrêter de lui même lorsque l'on commande une redirection par header, les header sont des informations génériques, ils sont envoyés tels quels, sans traitement particulier par php. Et le client n'est pas tenu de couper la connexion lorsqu'il reçoit le header, il a très bien le droit d'attendre le header ET le contenu de la page, et si le client ne coupe pas la connexion, le serveur lui enverra. Ce qui signifie que si on fait une redirection par header, et que l'on veut que le script s'arrête, alors on arrête le script soit même...