Question Quelle est la meilleure méthode pour désinfecter l'entrée de l'utilisateur avec PHP?


Y at-il une fonction catchall quelque part qui fonctionne bien pour désinfecter les entrées utilisateur pour l'injection SQL et les attaques XSS, tout en permettant certains types de balises html?


975
2017-09-24 20:20


origine


Réponses:


C'est une idée fausse commune que l'entrée de l'utilisateur peut être filtrée. PHP a même une "caractéristique" (maintenant obsolète), appelée magic-quotes, qui s'appuie sur cette idée. C'est n'importe quoi. Oubliez le filtrage (Ou le nettoyage, ou ce que les gens appellent).

Ce que vous devriez faire, pour éviter les problèmes, est assez simple: chaque fois que vous incorporez une chaîne dans un code étranger, vous devez l'échapper, selon les règles de cette langue. Par exemple, si vous intégrez une chaîne dans un ciblage SQL MySql, vous devez échapper la chaîne avec la fonction MySql à cette fin (mysqli_real_escape_string). (Ou, dans le cas des bases de données, l'utilisation d'instructions préparées est une meilleure approche, lorsque cela est possible)

Un autre exemple est HTML: Si vous intégrez des chaînes dans le balisage HTML, vous devez l'échapper avec htmlspecialchars. Cela signifie que chaque echo ou print déclaration devrait utiliser htmlspecialchars.

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

Et ainsi de suite ...

le seulement cas où vous avez besoin de filtrer activement les données, c'est si vous acceptez une entrée préformatée. Par exemple. si vous laissez vos utilisateurs poster un balisage HTML, que vous prévoyez d'afficher sur le site. Cependant, vous devriez être prudent d'éviter cela à tout prix, car peu importe à quel point vous le filtrez, ce sera toujours un trou de sécurité potentiel.


1078
2017-09-24 22:30



N'essayez pas d'empêcher l'injection SQL en désinfectant les données d'entrée.

Au lieu, n'autorise pas l'utilisation de données dans la création de votre code SQL. Utilisez des instructions préparées (c'est-à-dire en utilisant des paramètres dans une requête de modèle) qui utilise des variables liées. C'est le seul moyen d'être garanti contre l'injection SQL.

S'il vous plaît voir mon site web http://bobby-tables.com/ pour en savoir plus sur la prévention de l'injection SQL.


184
2017-10-09 06:28



Non. Vous ne pouvez pas filtrer les données de manière générique sans aucun contexte. Parfois, vous voudriez prendre une requête SQL comme entrée et parfois vous voudriez prendre le HTML comme 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 l'échapper avant de l'utiliser, selon le contexte dans lequel vous l'utilisez.

Le processus d'échappement des données pour SQL - pour empêcher l'injection SQL - est très différent du processus d'échappement des données pour (X) HTML, pour empêcher XSS.


72
2017-09-24 20:24



PHP a maintenant les nouvelles fonctions nice filter_input, qui par exemple vous libèrent de trouver 'l'ultime regex e-mail' maintenant qu'il y a un type intégré FILTER_VALIDATE_EMAIL

Ma propre classe de filtre (utilise javascript pour mettre en évidence les champs défectueux) peut être initiée par une requête ajax ou une publication normale. (voir l'exemple ci-dessous)     

/**
 *  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');
 *  $sanatize = array('alias');
 *
 *  $validator = new FormValidator($validations, $required, $sanatize);
 *                  
 *  if($validator->validate($_POST))
 *  {
 *      $_POST = $validator->sanatize($_POST);
 *      // now do your saving, $_POST has been sanatized.
 *      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 sanatize just one element:
 * $sanatized = new FormValidator()->sanatize('<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->sanatations = $sanatations;
        $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);
    }


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


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

    /**
     *
     * Sanatize a single var according to $type.
     * Allows for static calling to allow simple sanatization
     */
    public static function sanatizeItem($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 faire s'échapper votre requête sql en fonction du type de db que vous utilisez (mysql_real_escape_string () est inutile pour un serveur sql par exemple). Vous voulez probablement gérer cela automatiquement à votre couche d'application appropriée comme un ORM. Aussi, comme mentionné ci-dessus: pour la sortie en HTML utiliser les autres fonctions dédiées php comme htmlspecialchars;)

Pour vraiment permettre l'entrée HTML avec des classes et / ou des étiquettes dépouillées similaires dépendent de l'un des paquets de validation xss dédiés. NE PAS ÉCRIRE VOS PROPRES REGEXES AU PARSE HTML!


43
2017-09-24 23:12



Non, il n'y en a pas.

Tout d'abord, l'injection SQL est un problème de filtrage d'entrée, et XSS est une sortie qui échappe - 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 de base

  • Pour une requête SQL, liez les paramètres (comme avec PDO) ou utilisez une fonction d'échappement natif du pilote pour les variables de requête (telles que mysql_real_escape_string())
  • Utilisation strip_tags() filtrer le code HTML indésirable
  • Échapper à toutes les autres sorties avec htmlspecialchars() et soyez conscient des 2e et 3e paramètres ici.

39
2017-09-24 20:30



Pour résoudre le problème XSS, jetez un oeil à Purificateur HTML. Il est assez configurable et a un bon historique.

En ce qui concerne les attaques par injection SQL, assurez-vous de vérifier l'entrée de l'utilisateur, puis exécutez-le si mysql_real_escape_string (). Cependant, la fonction ne vaincra pas toutes les attaques par injection, il est donc important de vérifier les données avant de les jeter dans votre chaîne de requête.

Une meilleure solution consiste à utiliser des instructions préparées. le Bibliothèque PDO et l'extension mysqli les supporte.


20
2017-09-24 20:29



PHP 5.2 a introduit le filter_var fonction.

Il supporte beaucoup de filtres SANITIZE, VALIDATE.

http://php.net/manual/fr/function.filter-var.php


18
2017-10-15 08:40



Une astuce qui peut aider dans les circonstances spécifiques où vous avez une page comme /mypage?id=53 et vous utilisez l'id dans une clause WHERE est de s'assurer que l'ID est certainement un nombre entier, comme ceci:

if (isset($_GET['id'])) {
  $id = $_GET['id'];
  settype($id, 'integer');
  $result = mysql_query("SELECT * FROM mytable WHERE id = '$id'");
  # now use the result
}

Mais bien sûr, cela ne coupe qu'une attaque spécifique, alors lisez toutes les autres réponses. (Et oui je sais que le code ci-dessus n'est pas génial, mais il montre la défense spécifique.)


15
2018-03-08 23:14



Ce que vous décrivez ici est deux questions distinctes:

  1. Désinfection / filtrage des données d'entrée utilisateur.
  2. Sortie d'échappement

1) L'entrée de l'utilisateur doit toujours être considérée comme mauvaise.

Utiliser des instructions préparées, et / ou filtrer avec mysql_real_escape_string est certainement un must. PHP a aussi filter_input construit dans lequel est un bon endroit pour commencer.

2) Ce sujet est vaste et dépend du contexte de la sortie des données. Pour HTML, il existe des solutions telles que htmlpurifier. En règle générale, évitez toujours tout ce que vous produisez.

Les deux problèmes sont beaucoup trop importants pour être traités dans un seul article, mais il y a beaucoup de messages qui vont dans le détail:

Méthodes de sortie PHP

Sortie PHP plus sûre


10
2017-07-16 10:44