1222 votes

Comment puis-je nettoyer les données utilisateur avec PHP ?

Y a-t-il une fonction de capture quelque part qui fonctionne bien pour nettoyer les entrées utilisateur des attaques par injection SQL et XSS, tout en permettant toujours certains types de balises HTML?

48 votes

De nos jours, pour éviter les injections SQL, utilisez PDO ou MySQLi.

93 votes

Utiliser PDO ou MySQLi ne suffit pas. Si vous construisez vos instructions SQL avec des données non fiables, comme select * from users where name='$name', alors peu importe si vous utilisez PDO, MySQLi ou MySQL. Vous êtes toujours en danger. Vous devez utiliser des requêtes paramétrées ou, si vous le devez, utiliser des mécanismes d'échappement sur vos données, mais cela est beaucoup moins préférable.

30 votes

@AndyLester Suggérez-vous que quelqu'un utilise PDO sans instructions préparées? :)

1254voto

troelskn Points 51966

C'est une idée fausse courante que l'entrée de l'utilisateur peut être filtrée. PHP avait même une "fonctionnalité" (maintenant obsolète), appelée magic-quotes, qui repose sur cette idée. C'est absurde. Oubliez le filtrage (ou le nettoyage, ou quoi que les gens l'appellent).

Ce que vous devriez faire, pour éviter les problèmes, est assez simple : chaque fois que vous incorporez des données dans un code étranger, vous devez le formater selon les règles de ce code. Mais vous devez comprendre que ces règles peuvent être trop compliquées pour essayer de les suivre manuellement. Par exemple, en SQL, les règles pour les chaînes de caractères, les nombres et les identifiants sont tous différents. Pour votre commodité, dans la plupart des cas, il existe un outil dédié à une telle incorporation. Par exemple, lorsque des données doivent être utilisées dans la requête SQL, au lieu d'ajouter une variable directement à la chaîne SQL, cela doit être fait à travers un paramètre dans la requête, en utilisant une instruction préparée. Et cela se chargera de tout le formatage approprié.

Un autre exemple est le HTML : Si vous incorporez des chaînes de caractères dans le balisage HTML, vous devez les échapper avec htmlspecialchars. Cela signifie que chaque instruction echo ou print doit utiliser htmlspecialchars.

Un troisième exemple pourrait être les commandes shell : Si vous allez incorporer des chaînes de caractères (telles que des arguments) aux commandes externes, et les appeler avec exec, alors vous devez utiliser escapeshellcmd et escapeshellarg.

Un exemple très convaincant est le JSON. Les règles sont si nombreuses et compliquées que vous ne seriez jamais capable de les suivre manuellement. C'est pourquoi vous ne devriez jamais créer une chaîne JSON manuellement, mais toujours utiliser une fonction dédiée, json_encode() qui formatera correctement chaque bit de données.

Et ainsi de suite ...

Le seul cas où vous devez filtrer activement les données, c'est si vous acceptez une entrée préformatée. Par exemple, si vous permettez à vos utilisateurs de poster du balisage HTML que vous prévoyez d'afficher sur le site. Cependant, vous devriez être sage d'éviter cela à tout prix, car peu importe à quel point vous le filtrez bien, cela restera toujours une faille de sécurité potentielle.

256 votes

"Cela signifie que chaque instruction echo ou print devrait utiliser htmlspecialchars" - bien sûr, vous voulez dire "chaque ... instruction affichant une entrée utilisateur"; htmlspecialchars()-ifier "echo 'Bonjour, le monde!';" serait fou ;)

12 votes

Il y a un cas où je pense que le filtrage est la bonne solution : l'UTF-8. Vous ne voulez pas de séquences UTF-8 invalides partout dans votre application (vous pourriez obtenir une récupération d'erreur différente en fonction du chemin de code), et l'UTF-8 peut être filtré (ou rejeté) facilement.

3 votes

@porneL : Oui, et il peut également être utile de filtrer les caractères de contrôle autres que le saut de ligne à ce stade. Cependant, étant donné que la plupart des applications PHP ne parviennent même pas à bien échapper l'HTML, je ne vais pas insister sur le problème des séquences UTF-8 trop longues (elles ne posent vraiment problème que dans IE6 avant le Service Pack 2 et les anciens Opéras).

234voto

Andy Lester Points 34051

Ne pas essayer de prévenir l'injection SQL en nettoyant les données d'entrée.

Au lieu de cela, ne permettez pas aux données d'être utilisées pour créer votre code SQL. Utilisez des déclarations préparées (c'est-à-dire en utilisant des paramètres dans une requête modèle) qui utilisent des variables liées. C'est le seul moyen d'être garanti contre l'injection SQL.

Veuillez consulter mon site web http://bobby-tables.com/ pour en savoir plus sur la prévention de l'injection SQL.

20 votes

Ou visitez la documentation officielle et apprenez PDO et les instructions préparées. Petite courbe d'apprentissage, mais si vous connaissez bien le SQL, vous n'aurez aucun mal à vous adapter.

2 votes

Pour le cas spécifique de l'injection SQL, cela est la réponse correcte !

6 votes

Notez que les instructions préparées n'ajoutent pas de sécurité, les requêtes paramétrées le font. Elles se trouvent simplement être très faciles à utiliser ensemble en PHP.

81voto

Daniel Papasian Points 10206

Non. Vous ne pouvez pas filtrer les données de manière générique sans aucun contexte de ce à quoi elles servent. Parfois, vous voudriez prendre une requête SQL en entrée et parfois vous voudriez prendre du HTML en entrée.

Vous devez filtrer les entrées sur une liste blanche - assurez-vous que les données correspondent à certaines spécifications de ce que vous attendez. Ensuite, vous devez les échapper avant de les utiliser, en fonction du contexte dans lequel vous les utilisez.

Le processus d'échappement des données pour SQL - pour éviter les injections SQL - est très différent du processus d'échappement des données pour (X) HTML, pour éviter les XSS.

59voto

SchizoDuckie Points 6420

PHP a le nouveau beau filter_input qui, par exemple, vous libèrent de l'obligation de trouver "l'ultime regex pour les e-mails" maintenant qu'il existe une fonction intégrée FILTER_VALIDATE_EMAIL type


Ma propre classe de filtre (qui utilise JavaScript pour mettre en évidence les champs défectueux) peut être lancée par une requête ajax ou un formulaire normal. (voir l'exemple ci-dessous) < ? /** * Pork Formvalidator. valide les champs par regex et peut les sanitiser. Utilise les fonctions intégrées de PHP filter_var et des regex supplémentaires. * @package pork */

/**
 *  Pork.FormValidator
 *  Validates arrays or properties by setting up simple arrays. 
 *  Note that some of the regexes are for dutch input!
 *  Example:
 * 
 *  $validations = array('name' => 'anything','email' => 'email','alias' => 'anything','pwd'=>'anything','gsm' => 'phone','birthdate' => 'date');
 *  $required = array('name', 'email', 'alias', 'pwd');
 *  $sanitize = array('alias');
 *
 *  $validator = new FormValidator($validations, $required, $sanitize);
 *                  
 *  if($validator->validate($_POST))
 *  {
 *      $_POST = $validator->sanitize($_POST);
 *      // now do your saving, $_POST has been sanitized.
 *      die($validator->getScript()."<script type='text/javascript'>alert('saved changes');</script>");
 *  }
 *  else
 *  {
 *      die($validator->getScript());
 *  }   
 *  
 * To validate just one element:
 * $validated = new FormValidator()->validate('blah@bla.', 'email');
 * 
 * To sanitize just one element:
 * $sanitized = new FormValidator()->sanitize('<b>blah</b>', 'string');
 * 
 * @package pork
 * @author SchizoDuckie
 * @copyright SchizoDuckie 2008
 * @version 1.0
 * @access public
 */
class FormValidator
{
    public static $regexes = Array(
            'date' => "^[0-9]{1,2}[-/][0-9]{1,2}[-/][0-9]{4}\$",
            'amount' => "^[-]?[0-9]+\$",
            'number' => "^[-]?[0-9,]+\$",
            'alfanum' => "^[0-9a-zA-Z ,.-_\\s\?\!]+\$",
            'not_empty' => "[a-z0-9A-Z]+",
            'words' => "^[A-Za-z]+[A-Za-z \\s]*\$",
            'phone' => "^[0-9]{10,11}\$",
            'zipcode' => "^[1-9][0-9]{3}[a-zA-Z]{2}\$",
            'plate' => "^([0-9a-zA-Z]{2}[-]){2}[0-9a-zA-Z]{2}\$",
            'price' => "^[0-9.,]*(([.,][-])|([.,][0-9]{2}))?\$",
            '2digitopt' => "^\d+(\,\d{2})?\$",
            '2digitforce' => "^\d+\,\d\d\$",
            'anything' => "^[\d\D]{1,}\$"
    );
    private $validations, $sanatations, $mandatories, $errors, $corrects, $fields;

    public function __construct($validations=array(), $mandatories = array(), $sanatations = array())
    {
        $this->validations = $validations;
        $this->sanitations = $sanitations;
        $this->mandatories = $mandatories;
        $this->errors = array();
        $this->corrects = array();
    }

    /**
     * Validates an array of items (if needed) and returns true or false
     *
     */
    public function validate($items)
    {
        $this->fields = $items;
        $havefailures = false;
        foreach($items as $key=>$val)
        {
            if((strlen($val) == 0 || array_search($key, $this->validations) === false) && array_search($key, $this->mandatories) === false) 
            {
                $this->corrects[] = $key;
                continue;
            }
            $result = self::validateItem($val, $this->validations[$key]);
            if($result === false) {
                $havefailures = true;
                $this->addError($key, $this->validations[$key]);
            }
            else
            {
                $this->corrects[] = $key;
            }
        }

        return(!$havefailures);
    }

    /**
     *
     *  Adds unvalidated class to thos elements that are not validated. Removes them from classes that are.
     */
    public function getScript() {
        if(!empty($this->errors))
        {
            $errors = array();
            foreach($this->errors as $key=>$val) { $errors[] = "'INPUT[name={$key}]'"; }

            $output = '$$('.implode(',', $errors).').addClass("unvalidated");'; 
            $output .= "new FormValidator().showMessage();";
        }
        if(!empty($this->corrects))
        {
            $corrects = array();
            foreach($this->corrects as $key) { $corrects[] = "'INPUT[name={$key}]'"; }
            $output .= '$$('.implode(',', $corrects).').removeClass("unvalidated");';   
        }
        $output = "<script type='text/javascript'>{$output} </script>";
        return($output);
    }

    /**
     *
     * Sanitizes an array of items according to the $this->sanitations
     * sanitations will be standard of type string, but can also be specified.
     * For ease of use, this syntax is accepted:
     * $sanitations = array('fieldname', 'otherfieldname'=>'float');
     */
    public function sanitize($items)
    {
        foreach($items as $key=>$val)
        {
            if(array_search($key, $this->sanitations) === false && !array_key_exists($key, $this->sanitations)) continue;
            $items[$key] = self::sanitizeItem($val, $this->validations[$key]);
        }
        return($items);
    }

    /**
     *
     * Adds an error to the errors array.
     */ 
    private function addError($field, $type='string')
    {
        $this->errors[$field] = $type;
    }

    /**
     *
     * Sanitize a single var according to $type.
     * Allows for static calling to allow simple sanitization
     */
    public static function sanitizeItem($var, $type)
    {
        $flags = NULL;
        switch($type)
        {
            case 'url':
                $filter = FILTER_SANITIZE_URL;
            break;
            case 'int':
                $filter = FILTER_SANITIZE_NUMBER_INT;
            break;
            case 'float':
                $filter = FILTER_SANITIZE_NUMBER_FLOAT;
                $flags = FILTER_FLAG_ALLOW_FRACTION | FILTER_FLAG_ALLOW_THOUSAND;
            break;
            case 'email':
                $var = substr($var, 0, 254);
                $filter = FILTER_SANITIZE_EMAIL;
            break;
            case 'string':
            default:
                $filter = FILTER_SANITIZE_STRING;
                $flags = FILTER_FLAG_NO_ENCODE_QUOTES;
            break;

        }
        $output = filter_var($var, $filter, $flags);        
        return($output);
    }

    /** 
     *
     * Validates a single var according to $type.
     * Allows for static calling to allow simple validation.
     *
     */
    public static function validateItem($var, $type)
    {
        if(array_key_exists($type, self::$regexes))
        {
            $returnval =  filter_var($var, FILTER_VALIDATE_REGEXP, array("options"=> array("regexp"=>'!'.self::$regexes[$type].'!i'))) !== false;
            return($returnval);
        }
        $filter = false;
        switch($type)
        {
            case 'email':
                $var = substr($var, 0, 254);
                $filter = FILTER_VALIDATE_EMAIL;    
            break;
            case 'int':
                $filter = FILTER_VALIDATE_INT;
            break;
            case 'boolean':
                $filter = FILTER_VALIDATE_BOOLEAN;
            break;
            case 'ip':
                $filter = FILTER_VALIDATE_IP;
            break;
            case 'url':
                $filter = FILTER_VALIDATE_URL;
            break;
        }
        return ($filter === false) ? false : filter_var($var, $filter) !== false ? true : false;
    }       

}

Bien sûr, gardez à l'esprit que vous devez aussi faire l'échappement de vos requêtes sql en fonction du type de base de données que vous utilisez (mysql_real_escape_string() est inutile pour un serveur sql par exemple). Vous voudrez probablement gérer cela automatiquement au niveau de votre couche d'application appropriée, comme un ORM. De plus, comme mentionné ci-dessus : pour la sortie en html, utilisez les autres fonctions dédiées de php comme htmlspecialchars ;)

Pour autoriser réellement la saisie HTML avec des classes et/ou des balises dépouillées, utilisez l'un des paquets de validation xss dédiés. N'ÉCRIVEZ PAS VOS PROPRES REGEX POUR ANALYSER LE HTML !

25 votes

Cela ressemble à un script pratique pour valider les entrées, mais il est totalement hors sujet.

0 votes

Je ne suis pas d'accord avec l'utilisation de l'ORM, c'est de l'ingénierie excessive à mon avis.

0 votes

@PHP >= 8.0 donne une erreur Erreur d'analyse : erreur de syntaxe, inattendu '->' (T_OBJECT_OPERATOR)

50voto

Peter Bailey Points 62125

Non, il n'y en a pas.

Tout d'abord, l'injection SQL est un problème de filtrage des entrées, et XSS est un problème d'échappement des sorties - donc vous n'exécuteriez même pas ces deux opérations en même temps dans le cycle de vie du code.

Règles de base à suivre

  • Pour une requête SQL, liez les paramètres
  • Utilisez strip_tags() pour filtrer le HTML indésirable
  • Échappez toutes les autres sorties avec htmlspecialchars() et soyez attentif aux 2e et 3e paramètres ici.

1 votes

Alors vous n'utilisez strip_tags() ou htmlspecialchars() que lorsque vous savez que l'entrée contient du HTML que vous souhaitez supprimer ou échapper respectivement - vous ne l'utilisez pas dans un but de sécurité, n'est-ce pas? De plus, lors de la liaison, que faites-vous pour des choses comme les Tables de Bobby? "Robert'); DROP TABLE Students;--" Est-ce que cela échappe simplement aux guillemets?

2 votes

Si vous avez des données utilisateur qui iront dans une base de données et seront plus tard affichées sur des pages Web, n'est-il pas généralement lu beaucoup plus que ce qu'il est écrit? À mon sens, il est plus logique de le filtrer une fois (en tant qu'entrée) avant de le stocker, plutôt que de devoir le filtrer à chaque fois que vous l'affichez. Est-ce que je rate quelque chose ou est-ce que plusieurs personnes ont voté pour une surcharge de performance inutile dans ceci et dans la réponse acceptée?

2 votes

Meilleure réponse pour moi. C'est court et répond bien à la question selon moi. Est-il possible d'attaquer PHP d'une manière ou d'une autre via $_POST ou $_GET avec une injection ou est-ce impossible ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X