prevent - php sql injection protection function




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

Méthodes pour désinfecter l'entrée de l'utilisateur avec PHP:

  • Utilisez les versions modernes de MySQL et PHP.

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

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

    • Sélectionnez utf8, latin1, ascii .., n'utilisez pas les charsets vulnérables big5, cp932, gb2312, gbk, sjis.
  • Utilisez la fonction spatialisée:

    • Les instructions préparées par MySQLi:
      $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 des caractères spéciaux dans la chaîne d'entrée, en utilisant un style de citation approprié au pilote sous-jacent:

      $pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF8', $user, $password);explicit set the character set
      $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);disable emulating prepared statements to prevent fallback to emulating statements that MySQL can't prepare natively (to prevent injection)
      $var = $pdo->quote("' OR 1=1 /*");not only escapes the literal, but also quotes it (in single-quote ' characters) $stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1");

    • PDO Prepared Statements : vs MySQLi instructions préparées prend en charge plus de pilotes de base de données et les paramètres nommés:

      $pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF8', $user, $password);explicit set the character set
      $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);disable emulating prepared statements to prevent fallback to emulating statements that MySQL can't prepare natively (to prevent injection) $stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1'); $stmt->execute(["' OR 1=1 /*"]);

    • mysql_real_escape_string [déconseillé dans PHP 5.5.0, supprimé dans PHP 7.0.0].
    • mysqli_real_escape_string les caractères spéciaux dans une chaîne pour l'utiliser dans une instruction SQL, en tenant compte du jeu de caractères courant de la connexion. Mais il est recommandé d'utiliser Prepared Statements car ce ne sont pas des chaînes simplement échappées, une déclaration arrive avec un plan complet d'exécution de requête, y compris les tables et index qu'elle utilisera, 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 — Check for numeric character(s);
      $value = (int) $value;
      $value = intval($value);
      $var = filter_var('0755', FILTER_VALIDATE_INT, $options);
    • Pour les cordes, utilisez:
      is_string() — Find whether the type of a variable is string

      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);
      plus de filtres prédéfinis
    • filter_input() - Récupère une variable externe spécifique par son nom et la filtre éventuellement:
      $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.

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?


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

Vous désinfectez toujours la sortie.

Les transformations que vous appliquez aux données pour les rendre sûres à l'inclusion dans une requête SQL sont complètement différentes de celles que vous appliquez pour inclusion en HTML sont complètement différentes de celles que vous appliquez pour l'inclusion en Javascript sont complètement différentes de celles que vous appliquez pour inclusion dans LDIF complètement différent 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 ....

Par tous les moyens, validez l'entrée - décidez si vous devez l'accepter pour un traitement ultérieur ou dire à l'utilisateur que c'est inacceptable. Mais n'appliquez aucun changement à la représentation des données avant d'être sur le point de quitter PHP.

Il y a longtemps, quelqu'un a essayé d'inventer un mécanisme de taille unique pour échapper des données et nous avons fini avec des « magic_quotes » qui n'échappaient pas correctement aux données pour toutes les cibles de sortie et entraînaient une installation différente nécessitant un code différent.


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


Il n'y a pas de fonction fourre-tout, car il y a plusieurs problèmes à résoudre.

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

    Découvrez (Le seul vrai) tutoriel PDO pour à peu près tout ce que vous devez savoir sur PDO. (Sincères remerciements au meilleur contributeur de SO, @YourCommonSense, pour cette excellente ressource sur le sujet.)

  2. XSS - Désinfecter les données en cours ...

    • HTML Purifier existe depuis longtemps et est toujours activement mis à jour. Vous pouvez l'utiliser pour désinfecter les entrées malveillantes, tout en permettant une liste de balises généreuse et configurable. 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 sur 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 - Désinfectez les données à la sortie ... sauf si vous garantissez que les données ont été correctement filtrées avant de les ajouter à votre base de données, vous devrez les désinfecter avant de les afficher à votre utilisateur, nous pouvons exploiter ces fonctions PHP utiles:

    • Lorsque vous appelez echo ou print pour afficher les valeurs fournies par l'utilisateur, utilisez htmlspecialchars sauf si les données ont été correctement filtrées et sont autorisées à afficher du code HTML.
    • json_encode est un moyen sûr de fournir des valeurs fournies par l'utilisateur de PHP à Javascript
  4. Appelles-tu des commandes shell externes en utilisant les fonctions exec() ou system() , ou à l'opérateur backtick ? Si c'est le cas, en plus de SQL Injection et XSS vous pourriez avoir un autre problème à résoudre, les utilisateurs exécutant des commandes malveillantes sur votre serveur . Vous devez utiliser escapeshellcmd si vous souhaitez échapper à la commande entière ou escapeshellarg pour échapper des arguments individuels.


Je voulais juste ajouter que sur le sujet de l'échappement en sortie, si vous utilisez PHP DOMDocument pour rendre votre sortie html, il s'échappera automatiquement dans le bon contexte. Un attribut (value = "") et le texte interne d'un <span> ne sont pas égaux. Pour être sûr contre XSS lire ceci: OWASP XSS Prévention Cheat Sheet


La meilleure méthode BASIC pour désinfecter l'entrée de l'utilisateur avec PHP:


    function sanitizeString($var)
    {
        $var = stripslashes($var);
        $var = strip_tags($var);
        $var = htmlentities($var);
        return $var;
    }

    function sanitizeMySQL($connection, $var)
    {
        $var = $connection->real_escape_string($var);
        $var = sanitizeString($var);
        return $var;
    }

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

Au lieu de cela, n'autorisez pas l'utilisation de données dans la création de votre code SQL . Utilisez Prepared Statements (c'est-à-dire, utilisez 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.

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


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 la 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() )
  • Utilisez strip_tags() pour filtrer le code HTML indésirable
  • Échapper à toutes les autres sorties avec htmlspecialchars() et être conscient des paramètres 2 et 3 ici.


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('[email protected]', '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!


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

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

De 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.


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 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.)





user-input