Quelle est la meilleure méthode pour assainir les entrées des utilisateurs avec PHP?
18 réponses
c'est une idée fausse commune que les entrées de l'utilisateur peuvent être filtrées. PHP a même une "fonctionnalité" (Maintenant dépréciée), appelée Magic-quotes, qui s'appuie sur cette idée. C'est un non-sens. Oubliez le filtrage (ou le nettoyage, ou peu importe comment les gens l'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 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 de caractères Avec la fonction MySql à cet effet ( mysqli_real_escape_string
). (Ou, dans le cas des bases de données, l'utilisation d'énoncés préparés est une meilleure approche, lorsque cela est possible)
un autre exemple est HTML: Si vous intégrez des chaînes dans le markup HTML, vous devez l'échapper avec htmlspecialchars
. Cela signifie que chaque déclaration echo
ou print
doit utiliser htmlspecialchars
.
un troisième exemple pourrait être shell commandes: si vous voulez intégrer des chaînes (comme des arguments) à des commandes externes, et les appeler avec exec
, vous devez alors utiliser escapeshellcmd
et escapeshellarg
.
et ainsi de suite ...
le seulement cas où vous avez besoin de filtrer activement les données, est si vous acceptez l'entrée préformatée. Par exemple. si vous laissez vos utilisateurs poster HTML balisage, que vous prévoyez d'afficher sur le site. Cependant, vous devez être prudent pour éviter cela à tout prix, depuis n'importe comment bien vous filtre, il sera toujours un risque de sécurité.
N'essayez pas d'empêcher L'injection de SQL en nettoyant les données d'entrée.
à la place, ne permettent pas aux données d'être utilisées dans la création de votre code SQL . Utilisez des énoncés préparés (c.-à-d. en utilisant des paramètres dans une requête de modèle) qui utilisent des variables liées. C'est la seule garantie contre L'injection SQL.
S'il vous plaît voir mon site web http://bobby-tables.com / for more about preventing SQL injection.
Pas de. Vous ne pouvez pas filtrer des données de manière générique sans aucun contexte de ce qu'il est pour. Parfois vous voulez prendre une requête SQL comme entrée et parfois vous voulez prendre HTML comme entrée.
vous devez filtrer l'entrée sur une liste blanche -- s'assurer que les données correspondent à une spécification de ce que vous attendez. Ensuite, vous devez vous échapper avant de l'utiliser, selon le contexte dans lequel vous l'utilisez.
le processus d'échapper des données pour SQL-to prevent SQL injection - est très différent du processus d'échappement de données pour (X)HTML, pour prévenir XSS.
PHP a les nouvelles fonctions filter_input nice maintenant, qui par exemple vous libérer de la recherche 'l'e-mail ultime regex' maintenant qu'Il ya un intégré FILTER_VALIDATE_EMAIL type
ma propre classe de filtre (utilise javascript pour mettre en évidence les champs défectueux) peut être initialisée soit par une requête ajax soit par un courrier 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');
* $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 votre requête sql s'échapper aussi 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. En outre, comme mentionné ci-dessus: pour la sortie vers html utiliser les autres fonctions php dédiées comme htmlspécialchars ;)
pour permettre vraiment L'entrée HTML avec des classes et/ou des tags dépends de l'un des paquets de validation XSS dédiés. NE PAS ÉCRIRE VOTRE PROPRES REGEXES POUR ANALYSER HTML!
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 un problème d'échappement de sortie - Donc vous n'exécuteriez même pas ces deux opérations en même temps dans le cycle de vie du code.
rules of thumb
- pour la requête SQL, les paramètres de bind (comme avec PDO) ou utilisez une fonction d'échappement natif du pilote pour les variables de requête (comme
mysql_real_escape_string()
) - Use
strip_tags()
pour filtrer les HTML indésirables - échapper à toute autre sortie avec
htmlspecialchars()
et être conscient des 2nd et 3rd paramètres ici.
pour répondre à la question XSS, jetez un oeil à Purificateur HTML . Il est assez configurable et a une bonne réputation.
comme pour les attaques par injection SQL, assurez-vous de vérifier l'entrée de l'utilisateur, puis de l'exécuter via mysql_real_escape_string(). La fonction ne vaincra pas toutes les attaques par injection, cependant, il est donc important de vérifier les données avant de les transférer dans votre chaîne de requête.
Une meilleure solution est d'utiliser déclarations préparées. La bibliothèque PDO et l'extension mysqli les supportent.
PHP 5.2 introduit la fonction filter_var .
il prend en charge beaucoup de désinfectant, de valider les filtres.
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 entier, comme ainsi:
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 que cela n'élimine 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 très grande, mais elle montre le spécifique de la défense.)
ce que vous décrivez ici est deux numéros distincts:
- assainissement / filtrage des données d'entrée des utilisateurs.
- sortie D'échappement.
1) les entrées des utilisateurs doivent toujours être considérées comme mauvaises.
utilisant des déclarations préparées, ou / et le filtrage avec mysql_real_escape_string est certainement un must. PHP a aussi créé filter_input, ce qui est un bon point de départ.
2) Il s'agit d'un sujet important, qui dépend du contexte dans lequel les données sont produites. Pour HTML, il existe des solutions telles que htmlpurifier. en règle générale, toujours échapper à tout ce que vous sortie.
les deux questions sont beaucoup trop grandes pour entrer dans un seul poste, mais il ya beaucoup de postes qui vont dans plus de détails:
Méthodes de stérilisation de la saisie de l'utilisateur avec PHP:
-
utiliser les Versions modernes de MySQL et PHP.
-
Set charset explicitly:
-
$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 = new PDO( "mysql:host=$host;dbname=$db", $user, $pass, array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8" ) );
manuel -
[obsolète en PHP 5.5.0, supprimé en PHP 7.0.0].mysql_set_charset('utf8')
-
-
utiliser des charsets sécurisés:
- sélectionner utf8, latin1, ascii.. pas d' utilisez les charsets vulnérables big5, cp932, gb2312, gbk, sjis.
-
Use fonction spatialisée:
- MySQLi déclarations préparées à l'avance:
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "' OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute(); -
AOP::quote () - place des guillemets autour de la chaîne de saisie (si nécessaire) et échappe des caractères spéciaux dans la chaîne de saisie, 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 prepared statements supporte 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[obsolète en PHP 5.5.0, supprimé en PHP 7.0.0]. - mysqli_real_escape_string échappe des caractères spéciaux dans une chaîne de caractères pour une utilisation dans une instruction SQL, en tenant compte du jeu de caractères actuel de la connexion. Mais recommandé d'utiliser des déclarations préparées parce qu'elles ne sont pas simplement des chaînes échappées, une déclaration vient avec un plan d'exécution de requête complet, y compris les tables et les index qu'il utiliserait, c'est une manière optimisée.
- guillemets ( ''' ) autour de vos variables dans votre requête.
- MySQLi déclarations préparées à l'avance:
-
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 cordes:
is_string() — Find whether the type of a variable is string
Utiliser Fonction " Filtre filter_var() - filtres d'une variable avec un filtre spécifié:
$email = filter_var($email, FILTER_SANITIZE_EMAIL);
filtres plus prédéfinis
$newstr = filter_var($str, FILTER_SANITIZE_STRING);
- filter_input () - obtient une variable externe spécifique par son nom et la Filtre optionnellement:
$search_html = filter_input(INPUT_GET, 'search', FILTER_SANITIZE_SPECIAL_CHARS);
- preg_match () - effectuer une correspondance d'expression régulière;
- écrivez votre propre fonction de validation.
- si vous attendez un entier, utilisez:
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 de caractères pour interroger la base de données. Il renvoie une chaîne échappée au format PostgreSQL sans guillemets.
la façon la plus facile d'éviter des erreurs dans l'assainissement des données d'entrée et d'évasion est d'utiliser le cadre de PHP comme Symfony , Nette etc. ou une partie de ce framework (moteur de templation, couche de base de données, ORM).
moteur de modèle comme Twig ou Latte a la sortie s'échappant sur par défaut - vous ne devez pas résoudre manuellement si vous avez correctement échappé à votre sortie en fonction du contexte (HTML ou Javascript partie de page web).
Framework nettoie automatiquement les entrées et vous ne devez pas utiliser les variables $_POST, $_GET ou $_SESSION directement, mais par le biais d'un mécanisme comme le routage, la gestion de session, etc.
et pour la couche de base de données (modèle) il y a des cadres ORM comme Doctrine ou wrappers autour de PDO comme base de données Nette.
vous pouvez en savoir plus ici - Qu'est-ce qu'un framework logiciel?
il n'y a pas de fonction de catchall, car il y a de multiples problèmes à régler.
-
Injection SQL -Aujourd'hui, en général, chaque projet PHP doit utiliser déclarations préparées via les objets de données PHP (AOP) comme une meilleure pratique, prévenir une erreur d'une citation errante ainsi que d'une solution complète contre l'injection . C'est aussi le plus flexible & façon sécurisée d'accéder à votre base de données.
Check out (La seule) AOP tutoriel pour à peu près tout ce que vous devez savoir à propos de l'AOP. (Un grand merci à @YourCommonSense, le meilleur des contributeurs, pour cette excellente ressource sur le sujet.)
-
XSS - Désinfecter les données sur la manière...
-
Purifier HTML a été autour d'une longue période et est toujours activement mis à jour. Vous pouvez l'utiliser pour assainir les entrées malveillantes, tout en permettant une liste de blancs généreux et configurable de tags. Fonctionne très bien avec de nombreux éditeurs WYSIWYG, mais il pourrait ê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 a 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; }
-
-
XSS - Désinfecter les données sur le moyen de sortir... sauf si vous garantissez que les données ont été correctement épurées avant de les ajouter à votre base de données, vous aurez besoin de les épurer avant de les afficher à votre utilisateur, nous pouvons tirer parti de ces fonctions PHP utiles:
- lorsque vous appelez
echo
ouprint
à affichez les valeurs fournies par l'utilisateur, utilisezhtmlspecialchars
à moins que les données aient été correctement épurées Sécuritaire et est autorisé à afficher HTML. -
json_encode
est un moyen sûr de fournir des valeurs fournies par L'utilisateur de PHP à Javascript
- lorsque vous appelez
-
Faites-vous appel externe des commandes shell à l'aide de
exec()
ousystem()
ou à l'opérateurbacktick
? si oui, en plus de SQL Injection & XSS vous pourriez avoir un problème supplémentaire à adresser, utilisateurs exécutant des commandes malveillantes sur votre serveur . Vous devez utiliserescapeshellcmd
si vous voulez échapper à la commande entière ouescapeshellarg
pour échapper aux arguments individuels.
voulait juste ajouter que sur le 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 ne sont pas égaux. Pour être sûr contre XSS lire ceci: OWASP XSS Prevention Cheat Sheet
tu ne nettoies jamais les entrées.
vous nettoyez toujours la sortie.
les transformations que vous appliquez aux données pour les rendre sûres pour l'inclusion dans une déclaration SQL sont complètement différents de ceux que vous demandez l'inclusion dans HTML sont complètement différents de ceux que vous demandez l'inclusion dans Javascript sont complètement différents de ceux que vous demandez l'inclusion dans LDIF sont complètement différents de ceux que vous demandez l'inclusion dans CSS sont complètement différents différent de ceux que vous demandez d'inclure dans un courriel....
par tous les moyens valider l'entrée - décider si vous devez l'accepter pour un traitement ultérieur ou dire à l'utilisateur qu'il est inacceptable. Mais n'appliquez aucun changement à la représentation des données jusqu'à ce qu'elles soient sur le point de quitter PHP land.
il y a longtemps quelqu'un a essayé d'inventer un mécanisme de taille unique pour échapper aux données et nous avons fini avec" magic_quotes " qui n'ont pas correctement échappé aux données pour toutes les cibles de sortie et ont abouti à une installation différente nécessitant un code différent pour fonctionner.
il y a l'extension du filtre ( howto-link , manual ), qui fonctionne assez bien avec toutes les variables GPC. Ce n'est pas une chose magique, mais tu devras quand même l'utiliser.
function clean_input($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data);
return $data;
}
La meilleure méthode de BASE pour l'assainissement de l'utilisateur entrée 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;
}