web-dev-qa-db-fra.com

Est-ce que htmlspecialchars et mysql_real_escape_string gardent mon code PHP protégé contre l'injection?

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?

115
Cheekysoft

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

239
Cheekysoft

En plus de l'excellente réponse de Cheekysoft:

  • Oui, ils vous garderont en sécurité, mais uniquement s'ils sont utilisés de manière absolument correcte. Si vous les utilisez de manière incorrecte, vous serez toujours vulnérable et pourrez rencontrer d'autres problèmes (par exemple, corruption de données)
  • Veuillez plutôt utiliser des requêtes paramétrées (comme indiqué ci-dessus). Vous pouvez les utiliser par ex. AOP ou via un wrapper comme PEAR DB
  • Assurez-vous que magic_quotes_gpc et magic_quotes_runtime sont éteints à tout moment et ne sont jamais activés accidentellement, pas même brièvement. Il s'agit d'une tentative précoce et profondément erronée des développeurs de PHP pour éviter les problèmes de sécurité (qui détruisent les données)

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.

10
MarkR

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

3
BrilliantWinter

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.

2
Lucas Oman
$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.

2
cnizzardini