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

1124

Existe-t-il une fonction catchall quelque part qui fonctionne bien pour nettoyer les entrées utilisateur pour l'injection SQL et les attaques XSS, tout en autorisant certains types de balises HTML?

Brent
la source
42
De nos jours, pour éviter l'injection SQL, utilisez PDO ou MySQLi.
Francisco Presencia du
76
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 ou MySQLi ou MySQL. Tu es toujours en danger. Vous devez utiliser des requêtes paramétrées ou, si vous devez, utiliser des mécanismes d'échappement sur vos données, mais c'est beaucoup moins préférable.
Andy Lester
26
@AndyLester Voulez-vous dire que quelqu'un utilise PDO sans instructions préparées? :)
64
Je dis que "Utiliser PDO ou MySQLi" n'est pas une information suffisante pour expliquer aux novices comment les utiliser en toute sécurité. Vous et moi savons que les déclarations préparées sont importantes, mais je ne suppose pas que tous ceux qui liront cette question le sauront. C'est pourquoi j'ai ajouté les instructions explicites.
Andy Lester
30
Le commentaire d'Andy est tout à fait valable. J'ai récemment converti mon site mysql en PDO en pensant que j'étais maintenant en quelque sorte à l'abri des attaques par injection. Ce n'est qu'au cours du processus que j'ai réalisé que certaines de mes instructions SQL étaient toujours construites à l'aide de la saisie utilisateur. J'ai ensuite corrigé cela en utilisant des déclarations préparées. Pour un novice complet, il n'est pas tout à fait clair qu'il existe une distinction car de nombreux experts jettent le commentaire sur l'utilisation de PDO mais ne précisent pas la nécessité de déclarations préparées. L'hypothèse étant que cela est évident. Mais pas à un novice.
GhostRider

Réponses:

1184

C'est une idée fausse commune que l'entrée d'utilisateur peut être filtrée. PHP a même une "fonctionnalité" (désormais 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 devez faire, pour éviter les problèmes, est assez simple: chaque fois que vous intégrez une chaîne dans un code étranger, vous devez y échapper, selon les règles de cette langue. Par exemple, si vous intégrez une chaîne dans du SQL ciblant MySQL, vous devez échapper la chaîne avec la fonction MySQL à cet effet ( mysqli_real_escape_string). (Ou, dans le cas de bases de données, l'utilisation de déclarations préparées est une meilleure approche, si possible.)

Un autre exemple est HTML: si vous incorporez des chaînes dans le balisage HTML, vous devez l'échapper avec htmlspecialchars. Cela signifie que chaque single echoou printinstruction doit utiliser htmlspecialchars.

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

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 autorisez vos utilisateurs à publier du balisage HTML, que vous prévoyez d'afficher sur le site. Cependant, vous devriez être prudent pour éviter cela à tout prix, car peu importe la façon dont vous le filtrez, ce sera toujours un trou de sécurité potentiel.

troelskn
la source
245
"Cela signifie que chaque instruction echo ou print doit utiliser htmlspecialchars" - bien sûr, vous voulez dire "chaque ... instruction produisant une entrée utilisateur"; htmlspecialchars () - ifying "echo 'Hello, world!';" serait fou;)
Bobby Jack
10
Il y a un cas où je pense que le filtrage est la bonne solution: UTF-8. Vous ne voulez pas de séquences UTF-8 invalides partout dans votre application (vous pouvez obtenir une récupération d'erreur différente selon le chemin du code), et UTF-8 peut être filtré (ou rejeté) facilement.
Kornel
6
@jbyrd - non, LIKE utilise un langage d'expression rationnelle spécialisé. Vous devrez échapper votre chaîne d'entrée deux fois - une fois pour l'expression régulière et une fois pour l'encodage de la chaîne mysql. C'est du code dans le code dans le code.
troelskn
6
En ce moment mysql_real_escape_stringest obsolète. Il est de nos jours considéré comme une bonne pratique d'utiliser des instructions préparées pour empêcher l'injection SQL. Passez donc à MySQLi ou PDO.
Marcel Korpel
4
Parce que vous limitez la surface d'attaque. Si vous désinfectez tôt (lors de la saisie), vous devez être sûr qu'il n'y a pas d'autres trous dans l'application par lesquels de mauvaises données pourraient entrer. Alors que si vous le faites tard, votre fonction de sortie n'a pas à "faire confiance" à ce qu'elle reçoive des données sûres - elle suppose simplement que tout n'est pas sûr.
troelskn
217

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

Au lieu de cela, n'autorisez pas les données à utiliser 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 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.

Andy Lester
la source
18
Ou visitez la documentation officielle et découvrez l'AOP et les déclarations préparées. Petite courbe d'apprentissage, mais si vous connaissez assez bien SQL, vous n'aurez aucun mal à vous adapter.
un codeur
2
Pour le cas spécifique d'injection SQL, c'est la bonne réponse!
Scott Arciszewski
4
Notez que les instructions préparées n'ajoutent aucune sécurité, contrairement aux requêtes paramétrées. Il se trouve qu'ils sont très faciles à utiliser ensemble en PHP.
Basique
Ce n'est pas le seul moyen garanti. Hex l'entrée et unhex dans la requête empêcheront également. De plus, les attaques hexagonales ne sont pas possibles si vous utilisez hexagone à droite.
Ramon Bakker
Et si vous saisissez quelque chose de spécialisé, comme des adresses e-mail ou des noms d'utilisateur?
Abraham Brookes
79

Non. Vous ne pouvez pas filtrer les données de manière générique sans aucun contexte de leur utilisation. Parfois, vous voudriez prendre une requête SQL en entrée et parfois vous voudriez prendre 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 y é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.

Daniel Papasian
la source
52

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

Ma propre classe de filtre (utilise JavaScript pour mettre en évidence les champs défectueux) peut être lancée soit par une demande ajax, soit par un post de formulaire normal. (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');
 *  $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 également échapper votre requête 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 souhaiterez probablement gérer cela automatiquement au niveau de votre couche d'application appropriée, comme un ORM. Aussi, comme mentionné ci-dessus: pour la sortie en html, utilisez les autres fonctions dédiées à php comme htmlspecialchars;)

Pour vraiment autoriser l'entrée HTML avec des classes et / ou balises supprimées, dépendez de l'un des packages de validation xss dédiés. N'ÉCRIVEZ PAS VOS PROPRES REGEXES POUR ANALYSER LE HTML!

SchizoDuckie
la source
18
Il semble que ce soit un script pratique pour valider les entrées, mais cela n'a absolument rien à voir avec la question.
rjmunro
43

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 une sortie qui échappe à un - donc vous ne pourriez même pas exécuter ces deux opérations en même temps dans le cycle de vie du code.

Règles de base

  • Pour la requête SQL, liez les paramètres (comme avec PDO) ou utilisez une fonction d'échappement native du pilote pour les variables de requête (telles que mysql_real_escape_string())
  • Utilisez strip_tags()pour filtrer le HTML indésirable
  • Échappez à toutes les autres sorties avec htmlspecialchars()et faites attention aux 2e et 3e paramètres ici.
Peter Bailey
la source
1
Donc, vous n'utilisez strip_tags () ou htmlspecialchars () que lorsque vous savez que l'entrée contient du code HTML dont vous voulez vous débarrasser ou vous échapper respectivement - vous ne l'utilisez pas à des fins de sécurité, n'est-ce pas? De plus, lorsque vous effectuez la liaison, que fait-il pour des choses comme les tables Bobby? "Robert '); DROP TABLE Students; -" Est-ce que ça échappe juste aux citations?
Robert Mark Bram
2
Si vous avez des données utilisateur qui iront dans une base de données et seront ensuite affichées sur des pages Web, ne sont-elles généralement pas lues beaucoup plus qu'elles ne sont écrites? Pour moi, il est plus logique de le filtrer une fois (en entrée) avant de le stocker, au lieu d'avoir à le filtrer chaque fois que vous l'affichez. Suis-je en train de manquer quelque chose ou un groupe de personnes a-t-il voté pour des frais généraux inutiles dans ce domaine et la réponse acceptée?
jbo5112
2
Meilleure réponse pour moi. Il est court et répond bien à la question si vous me le demandez. 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?
Jo Smo
oh oui, les tableaux $ post et $ get acceptent tous les caractères, mais certains de ces caractères peuvent être utilisés contre vous si le caractère peut être énuméré dans la page php publiée. donc si vous n'échappez pas à l'encapsulation de caractères (comme "," et "), cela pourrait ouvrir un vecteur d'attaque. le caractère" est souvent manqué et peut être utilisé pour former des hacks d'exécution de ligne de commande. L'assainissement empêchera le piratage des entrées utilisateur, mais ne vous aidera pas avec les hacks du pare-feu des applications Web
drtechno
22

Pour résoudre le problème XSS, jetez un œil à HTML Purifier . Il est assez configurable et a une expérience professionnelle décente.

En ce qui concerne les attaques par injection SQL, assurez-vous de vérifier l'entrée utilisateur, puis exécutez-la via 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 vider dans votre chaîne de requête.

Une meilleure solution consiste à utiliser des instructions préparées. La bibliothèque PDO et l'extension mysqli les prennent en charge.

jasonbar
la source
il n'y a pas de "meilleure façon" de faire quelque chose comme la désinfection d'entrée. Utilisez une bibliothèque, le purificateur html est bon. Ces bibliothèques ont été pilonnées à plusieurs reprises. C'est donc beaucoup plus à l'épreuve des balles que tout ce que vous pouvez trouver vous
paan
Voir aussi bioinformatics.org/phplabware/internal_utilities/htmLawed . D'après ma compréhension, WordPress utilise une version plus ancienne, core.trac.wordpress.org/browser/tags/2.9.2/wp-includes/kses.php
Steve Clay
Le problème avec wordpress est que ce n'est pas nécessairement une attaque par injection php-sql qui provoque des violations de base de données. Les plugins programmés manquants qui stockent des données qu'une requête xml révèle des secrets est plus problématique.
drtechno
17

Une astuce qui peut aider dans le cas spécifique où vous avez une page comme /mypage?id=53et que vous utilisez l'id dans une clause WHERE est de vous assurer que l'id est définitivement un 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 seule 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.)

Hamish Downer
la source
11
J'utilise $ id = intval ($ id) à la place :)
Duc Tran
La conversion d'un entier est un bon moyen de s'assurer que seules des données numériques sont insérées.
test
1
$id = (int)$_GET['id']et $que = sprintf('SELECT ... WHERE id="%d"', $id)c'est bien aussi
vladkras
16

Méthodes de nettoyage des entrées utilisateur avec PHP:

  • Utilisez des versions modernes de MySQL et PHP.

  • Définissez explicitement le jeu de caractères:

    • $ mysqli-> set_charset ("utf8");
      Manuel
    • $ pdo = new PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ user, $ password);
      Manuel
    • $ pdo-> exec ("set names utf8");
      Manuel
    • $ pdo = nouveau PDO (
      "mysql: host = $ host; dbname = $ db", $ user, $ pass, 
      tableau (
      PDO :: ATTR_ERRMODE => PDO :: ERRMODE_EXCEPTION,
      PDO :: MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
      )
      );
      Manuel
    • mysql_set_charset ('utf8')
      [obsolète en PHP 5.5.0, supprimé en PHP 7.0.0].
  • Utilisez des jeux de caractères sécurisés:

    • Sélectionnez utf8, latin1, ascii .., n'utilisez pas les jeux de caractères vulnérables big5, cp932, gb2312, gbk, sjis.
  • Utiliser la fonction spatialisée:

    • MySQLi a préparé des déclarations:
      $ stmt = $ mysqli-> prepare ('SELECT * FROM test WHERE name =? LIMIT 1'); 
      $ param = "'OR 1 = 1 / *";
      $ stmt-> bind_param ('s', $ param);
      $ stmt-> execute ();
    • PDO :: quote () - place des guillemets autour de la chaîne d'entrée (si nécessaire) et échappe les caractères spéciaux dans la chaîne d'entrée, en utilisant un style de guillemets approprié au pilote sous-jacent:

      $ pdo = new PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ user, $ password); définir explicitement le jeu de caractères
      $ pdo-> setAttribute (PDO :: ATTR_EMULATE_PREPARES, false); désactiver l'émulation des instructions préparées pour éviter le repli sur les instructions d'émulation que MySQL ne peut pas préparer nativement (pour empêcher l'injection)
      $ var = $ pdo-> quote ("'OR 1 = 1 / *"); échappe non seulement au littéral, mais le cite également (en guillemets simples) $ stmt = $ pdo-> query ("SELECT * FROM test WHERE name = $ var LIMIT 1");

    • Instructions préparées par PDO : vs les instructions préparées par MySQLi prennent en charge davantage de pilotes de base de données et de paramètres nommés:

      $ pdo = new PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ user, $ password); définir explicitement le jeu de caractères
      $ pdo-> setAttribute (PDO :: ATTR_EMULATE_PREPARES, false); désactiver l'émulation des instructions préparées pour éviter le repli sur les instructions d'émulation que MySQL ne peut pas préparer nativement (pour empêcher l'injection) $ stmt = $ pdo-> prepare ('SELECT * FROM test WHERE name =? LIMIT 1'); $ stmt-> execute (["'OR 1 = 1 / *"]);

    • mysql_real_escape_string [déconseillé en PHP 5.5.0, supprimé en PHP 7.0.0].
    • mysqli_real_escape_string Échappe les caractères spéciaux d'une chaîne pour les utiliser dans une instruction SQL, en tenant compte du jeu de caractères actuel de la connexion. Mais il est recommandé d'utiliser les instructions préparées car ce ne sont pas simplement des chaînes d'échappement, une instruction fournit un plan d'exécution de requête complet, y compris les tables et les index qu'elle utiliserait, c'est une manière optimisée.
    • Utilisez des guillemets simples ('') autour de vos variables dans votre requête.
  • Vérifiez que la variable contient ce que vous attendez:

    • Si vous attendez un entier, utilisez:
      ctype_digit - Vérifie les caractères numériques; 
      $ value = (int) $ value;
      $ value = intval ($ value);
      $ var = filter_var ('0755', FILTER_VALIDATE_INT, $ options);
    • Pour les cordes, utilisez:
      is_string () - Détermine si le type d'une variable est une chaîne

      Utiliser la fonction de filtre filter_var () - filtre une variable avec un filtre spécifié:
      $ email = filter_var ($ email, FILTER_SANITIZE_EMAIL); 
      $ newstr = filter_var ($ str, FILTER_SANITIZE_STRING);
      filtres plus prédéfinis
    • filter_input () - Récupère une variable externe spécifique par son nom et éventuellement la filtre:
      $ search_html = filter_input (INPUT_GET, 'search', FILTER_SANITIZE_SPECIAL_CHARS);
    • preg_match () - Effectue une correspondance d'expression régulière;
    • Écrivez votre propre fonction de validation.
Mark Martin
la source
11

Vous décrivez ici deux problèmes distincts:

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

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

Utiliser des instructions préparées ou / et filtrer avec mysql_real_escape_string est définitivement un must. PHP a également intégré filter_input, ce qui est un bon point de départ.

2) Il s'agit d'un grand sujet qui dépend du contexte des données en cours de sortie. Pour HTML, il existe des solutions telles que htmlpurifier. en règle générale, échappez toujours à tout ce que vous produisez.

Les deux problèmes sont beaucoup trop importants pour être abordés dans un seul article, mais il existe de nombreux articles plus détaillés:

Méthodes de sortie PHP

Sortie PHP plus sûre

Andrew
la source
9

Si vous utilisez PostgreSQL, l'entrée de PHP peut être échappée avec pg_escape_string ()

 $username = pg_escape_string($_POST['username']);

Depuis la documentation ( http://php.net/manual/es/function.pg-escape-string.php ):

pg_escape_string () échappe une chaîne pour interroger la base de données. Il renvoie une chaîne échappée au format PostgreSQL sans guillemets.

Alejandro Silva
la source
1
pg_escape_literal () est la fonction recommandée à utiliser pour PostgreSQL.
cryptique
8

Il n'y a pas de fonction de fourre-tout, car il existe de multiples préoccupations à résoudre.

  1. Injection SQL - Aujourd'hui, généralement, chaque projet PHP devrait utiliser des instructions préparées via PHP Data Objects (PDO) comme meilleure pratique, empêchant une erreur d'une citation erronée ainsi qu'une solution complète contre l'injection . C'est également le moyen le plus flexible et le plus sécurisé d'accéder à votre base de données.

    Consultez (le seul bon) tutoriel PDO pour à peu près tout ce que vous devez savoir sur PDO. (Merci sincèrement au principal contributeur SO, @YourCommonSense, pour cette excellente ressource sur le sujet.)

  2. XSS - Assainissez les données en chemin ...

    • HTML Purifier existe depuis longtemps et est toujours activement mis à jour. Vous pouvez l'utiliser pour nettoyer les entrées malveillantes, tout en permettant une liste blanche généreuse et configurable de balises. Fonctionne très bien avec de nombreux éditeurs WYSIWYG, mais il peut être lourd pour certains cas d'utilisation.

    • Dans d'autres cas, où nous ne voulons pas du tout accepter HTML / Javascript, j'ai trouvé cette fonction simple utile (et j'ai passé plusieurs audits contre XSS):

      /* Prevent XSS input */ function sanitizeXSS () { $_GET = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING); $_POST = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING); $_REQUEST = (array)$_POST + (array)$_GET + (array)$_REQUEST; }

  3. XSS - Assainir les données à la sortie ... à moins que vous ne garantissiez que les données ont été correctement assainies avant de les ajouter à votre base de données, vous devrez les assainir avant de les afficher à votre utilisateur, nous pouvons tirer parti de ces fonctions PHP utiles:

    • Lorsque vous appelez echoou printpour afficher des valeurs fournies par l'utilisateur, utilisez-les htmlspecialcharssauf si les données ont été correctement nettoyées en toute sécurité et sont autorisées à afficher du HTML.
    • json_encode est un moyen sûr de fournir des valeurs fournies par l'utilisateur de PHP à Javascript
  4. Appelez-vous des commandes shell externes à l' aide exec()ou des system()fonctions, ou à l' backtickopérateur? Si c'est le cas, en plus de SQL Injection & XSS, vous pourriez avoir une préoccupation supplémentaire à résoudre, les utilisateurs exécutant des commandes malveillantes sur votre serveur . Vous devez utiliser escapeshellcmdsi vous souhaitez échapper à la commande entière OU escapeshellargpour échapper aux arguments individuels.

webaholik
la source
mb_encode_numericentity pourrait-il être utilisé à la place? Puisqu'il encode tout?
drtechno
@drtechno - mb_encode_numericentityest discuté dans le htmlspecialcharslien sur # 3 XSS
webaholik
5

Le moyen le plus simple d'éviter les erreurs de nettoyage des données d'entrée et d'échappement est d'utiliser un framework PHP comme Symfony , Nette etc. ou une partie de ce framework (moteur de template, couche de base de données, ORM).

Le moteur de modèle comme Twig ou Latte a un échappement de sortie par défaut - vous n'avez pas à résoudre manuellement si vous avez correctement échappé votre sortie en fonction du contexte (partie HTML ou Javascript de la page Web).

Le framework nettoie automatiquement les entrées et vous ne devez pas utiliser directement les variables $ _POST, $ _GET ou $ _SESSION, mais par le biais de mécanismes comme le routage, la gestion des sessions, etc.

Et pour la couche de base de données (modèle), il existe des cadres ORM comme Doctrine ou des wrappers autour de PDO comme Nette Database.

Vous pouvez en savoir plus ici - Qu'est - ce qu'un framework logiciel?

Ondřej Šotek
la source
3

Je voulais juste ajouter qu'au sujet de l'échappement de sortie, si vous utilisez php DOMDocument pour faire votre sortie html, il s'échappera automatiquement dans le bon contexte. Un attribut (valeur = "") et le texte interne d'un <span> ne sont pas égaux. Pour être sûr contre XSS, lisez ceci: OWASP XSS Prevention Cheat Sheet

user138720
la source
2

Vous ne désinfectez jamais l'entrée.

Vous désinfectez toujours la sortie.

Les transformations que vous appliquez aux données pour garantir leur inclusion dans une instruction SQL sont complètement différentes de celles que vous demandez pour l'inclusion dans HTML sont complètement différentes de celles que vous demandez pour l'inclusion dans Javascript sont complètement différentes de celles que vous demandez pour l'inclusion dans LDIF sont complètement différents de ceux que vous appliquez à l'inclusion dans CSS sont complètement différents de ceux que vous appliquez à l'inclusion dans un e-mail ....

Validez par tous les moyens l' entrée - décidez si vous devez l'accepter pour un traitement ultérieur ou dites à l'utilisateur qu'elle est inacceptable. Mais n'appliquez aucune modification à la représentation des données avant qu'elles ne quittent PHP Land.

Il y a longtemps, quelqu'un a essayé d'inventer un mécanisme unique pour échapper aux données et nous nous sommes retrouvés avec " magic_quotes " qui n'échappait pas correctement les données pour toutes les cibles de sortie et entraînait une installation différente nécessitant un code différent pour fonctionner.

symcbean
la source
un problème avec cela est que ce n'est pas toujours une attaque de base de données, et toutes les entrées utilisateur doivent être protégées du système. pas seulement un type de langue. Ainsi, sur vos sites, lorsque vous énumérez vos données $ _POST, même en utilisant la liaison, elles peuvent s'échapper suffisamment pour exécuter le shell ou même un autre code php.
drtechno
"ce n'est pas toujours une attaque de base de données": "Les transformations que vous appliquez aux données pour les rendre sûres pour inclusion dans une instruction SQL sont complètement différentes de celles ...."
symcbean
"toutes les entrées utilisateur doivent être protégées du système": non, le système doit être protégé contre les entrées utilisateur.
symcbean
eh bien j'ai manqué de mots, mais oui, l'entrée doit être empêchée d'effectuer le fonctionnement du système. pour clarifier cela ...
drtechno
L'entrée et la sortie doivent être nettoyées.
Tajni
1

Ne faites jamais confiance aux données utilisateur.

function clean_input($data) {
  $data = trim($data);
  $data = stripslashes($data);
  $data = htmlspecialchars($data);
  return $data;
}

La trim()fonction supprime les espaces et autres caractères prédéfinis des deux côtés d'une chaîne.

La stripslashes()fonction supprime les contre-obliques

La htmlspecialchars()fonction convertit certains caractères prédéfinis en entités HTML.

Les caractères prédéfinis sont:

& (ampersand) becomes &amp;
" (double quote) becomes &quot;
' (single quote) becomes &#039;
< (less than) becomes &lt;
> (greater than) becomes &gt;
Erik Thiart
la source
1
De quoi cela protégerait-il? Est-ce pour XSS? Pourquoi est-il appelé clean_inputalors? Pourquoi voudriez-vous supprimer les barres obliques?
Dharman
5
AVERTISSEMENT: cela ne sécurise pas comme par magie les données utilisateur. Cette fonction endommagera inutilement vos données sans les protéger de quoi que ce soit. NE L'UTILISE PAS!
Dharman
Votre déclaration est fausse.
Erik Thiart
0

Il y a l'extension de filtre ( howto-link , manuel ), qui fonctionne assez bien avec toutes les variables GPC. Ce n'est pas une chose magique, cependant, vous devrez toujours l'utiliser.

Jusqu'à
la source