Plus tôt dans la journée, une question a été posée concernant stratégies de validation des entrées dans les applications Web .
La première réponse, au moment de la rédaction, suggère dans PHP
en utilisant simplement htmlspecialchars
et mysql_real_escape_string
.
Ma question est: est-ce toujours suffisant? Y a-t-il plus que nous devrions savoir? Où ces fonctions se décomposent-elles?
En ce qui concerne les requêtes de base de données, essayez toujours d'utiliser des requêtes paramétrées préparées. Les bibliothèques mysqli
et PDO
le supportent. C'est infiniment plus sûr que d'utiliser des fonctions d'échappement telles que mysql_real_escape_string
.
Oui, mysql_real_escape_string
N'est en fait qu'une fonction d'échappement de chaîne. Ce n'est pas une balle magique. Tout ce qu'il fera est d'échapper aux caractères dangereux afin qu'ils puissent être utilisés en toute sécurité dans une seule chaîne de requête. Cependant, si vous ne nettoyez pas vos entrées au préalable, vous serez vulnérable à certains vecteurs d'attaque.
Imaginez le SQL suivant:
$result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']);
Vous devriez pouvoir voir que cela est vulnérable à l'exploitation.
Imaginez que le paramètre id
contenait le vecteur d'attaque commun:
1 OR 1=1
Il n'y a aucun caractère risqué à coder, il passera donc directement à travers le filtre qui s'échappe. Nous quittant:
SELECT fields FROM table WHERE id= 1 OR 1=1
Ce qui est un joli vecteur d'injection SQL et permettrait à l'attaquant de renvoyer toutes les lignes. Ou
1 or is_admin=1 order by id limit 1
qui produit
SELECT fields FROM table WHERE id=1 or is_admin=1 order by id limit 1
Ce qui permet à l'attaquant de renvoyer les détails du premier administrateur dans cet exemple complètement fictif.
Bien que ces fonctions soient utiles, elles doivent être utilisées avec précaution. Vous devez vous assurer que toutes les entrées Web sont validées dans une certaine mesure. Dans ce cas, nous voyons que nous pouvons être exploités parce que nous n'avons pas vérifié qu'une variable que nous utilisions comme nombre était en fait numérique. Dans PHP vous devez largement utiliser un ensemble de fonctions pour vérifier que les entrées sont des entiers, des flottants, des caractères alphanumériques, etc. Mais en ce qui concerne SQL, tenez compte de la valeur de l'instruction préparée. Le code ci-dessus aurait été sécurisé s'il s'agissait d'une instruction préparée car les fonctions de la base de données auraient su que 1 OR 1=1
n'est pas un littéral valide.
Quant à htmlspecialchars()
. C'est son propre champ de mines.
Il y a un vrai problème dans PHP en ce qu'il a toute une sélection de différentes fonctions d'échappement liées au html, et aucune indication claire sur exactement quelles fonctions font quoi.
Premièrement, si vous êtes à l'intérieur d'une balise HTML, vous avez de vrais ennuis. Regarder
echo '<img src= "' . htmlspecialchars($_GET['imagesrc']) . '" />';
Nous sommes déjà dans une balise HTML, nous n'avons donc pas besoin de <ou> pour faire quoi que ce soit de dangereux. Notre vecteur d'attaque pourrait simplement être javascript:alert(document.cookie)
Le HTML résultant ressemble maintenant à
<img src= "javascript:alert(document.cookie)" />
L'attaque passe directement.
Ça a empiré. Pourquoi? car htmlspecialchars
(lorsqu'elle est appelée de cette façon) code uniquement les guillemets doubles et non les simples. Donc, si nous avions
echo "<img src= '" . htmlspecialchars($_GET['imagesrc']) . ". />";
Notre attaquant maléfique peut désormais injecter de nouveaux paramètres
pic.png' onclick='location.href=xxx' onmouseover='...
nous donne
<img src='pic.png' onclick='location.href=xxx' onmouseover='...' />
Dans ces cas, il n'y a pas de solution miracle, il vous suffit de santiser l'entrée vous-même. Si vous essayez de filtrer les mauvais personnages, vous échouerez sûrement. Adoptez une approche de liste blanche et ne laissez passer que les caractères qui sont bons. Regardez le Aide-mémoire XSS pour des exemples sur la façon dont divers vecteurs peuvent être
Même si vous utilisez htmlspecialchars($string)
en dehors des balises HTML, vous êtes toujours vulnérable aux vecteurs d'attaque de jeux de caractères multi-octets.
Le plus efficace possible consiste à utiliser la combinaison de mb_convert_encoding et htmlentities comme suit.
$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
$str = htmlentities($str, ENT_QUOTES, 'UTF-8');
Même cela laisse IE6 vulnérable, en raison de la façon dont il gère UTF. Cependant, vous pouvez revenir à un encodage plus limité, tel que ISO-8859-1, jusqu'à ce que l'utilisation d'IE6 diminue.
Pour une étude plus approfondie des problèmes multioctets, voir https://stackoverflow.com/a/12118602/182
En plus de l'excellente réponse de Cheekysoft:
Il n'y a pas vraiment de solution miracle pour empêcher l'injection de code HTML (par exemple, l'écriture de scripts intersites), mais vous pourrez peut-être y parvenir plus facilement si vous utilisez une bibliothèque ou un système de modèles pour produire du code HTML. Lisez la documentation pour savoir comment échapper de manière appropriée.
En HTML, les choses doivent être échappées différemment selon le contexte. Cela est particulièrement vrai pour les chaînes placées en Javascript.
Je serais certainement d'accord avec les messages ci-dessus, mais j'ai une petite chose à ajouter en réponse à la réponse de Cheekysoft, en particulier:
En ce qui concerne les requêtes de base de données, essayez toujours d'utiliser des requêtes paramétrées préparées. Les bibliothèques mysqli et PDO le supportent. C'est infiniment plus sûr que d'utiliser des fonctions d'échappement telles que mysql_real_escape_string.
Oui, mysql_real_escape_string n'est en fait qu'une fonction d'échappement de chaîne. Ce n'est pas une balle magique. Tout ce qu'il fera est d'échapper aux caractères dangereux afin qu'ils puissent être utilisés en toute sécurité dans une seule chaîne de requête. Cependant, si vous ne nettoyez pas vos entrées au préalable, vous serez vulnérable à certains vecteurs d'attaque.
Imaginez le SQL suivant:
$ result = "SELECT champs FROM table WHERE id =" .mysql_real_escape_string ($ _ POST ['id']);
Vous devriez pouvoir voir que cela est vulnérable à l'exploitation. Imaginez que le paramètre id contienne le vecteur d'attaque commun:
1 OR 1 = 1
Il n'y a aucun caractère risqué à coder, il passera donc directement à travers le filtre qui s'échappe. Nous quittant:
CHOISISSEZ les champs DE LA table OERE id = 1 OR 1 = 1
J'ai codé une petite fonction rapide que j'ai mise dans ma classe de base de données qui supprimera tout ce qui n'est pas un nombre. Il utilise preg_replace, il y a donc probablement une fonction un peu plus optimisée, mais cela fonctionne à la rigueur ...
function Numbers($input) {
$input = preg_replace("/[^0-9]/","", $input);
if($input == '') $input = 0;
return $input;
}
Donc, au lieu d'utiliser
$ result = "SELECT champs FROM table WHERE id =" .mysqlrealescapestring ("1 OR 1 = 1");
J'utiliserais
$ result = "SELECT champs FROM table WHERE id =" .Numbers ("1 OR 1 = 1");
et il exécuterait la requête en toute sécurité
CHOISIR les champs DE LA table OERE id = 111
Bien sûr, cela l'a juste empêché d'afficher la bonne ligne, mais je ne pense pas que ce soit un gros problème pour quiconque essaie d'injecter sql dans votre site;)
Les contextes constituent une pièce importante de ce puzzle. Quelqu'un envoyant "1 OR 1 = 1" car l'ID n'est pas un problème si vous citez chaque argument de votre requête:
SELECT fields FROM table WHERE id='".mysql_real_escape_string($_GET['id'])."'"
Ce qui se traduit par:
SELECT fields FROM table WHERE id='1 OR 1=1'
ce qui est inefficace. Puisque vous échappez à la chaîne, l'entrée ne peut pas sortir du contexte de chaîne. J'ai testé cela jusqu'à la version 5.0.45 de MySQL, et l'utilisation d'un contexte de chaîne pour une colonne entière ne pose aucun problème.
$result = "SELECT fields FROM table WHERE id = ".(INT) $_GET['id'];
Fonctionne bien, encore mieux sur les systèmes 64 bits. Méfiez-vous des limites de vos systèmes pour traiter de grands nombres, mais pour les identifiants de base de données, cela fonctionne très bien 99% du temps.
Vous devez également utiliser une seule fonction/méthode pour nettoyer vos valeurs. Même si cette fonction n'est qu'un wrapper pour mysql_real_escape_string (). Pourquoi? Parce qu'un jour où un exploit à votre méthode préférée de nettoyage des données est trouvé, vous n'avez qu'à le mettre à jour à un endroit, plutôt qu'une recherche et un remplacement à l'échelle du système.