Dois-je me prémunir contre l'injection SQL si j'utilise une liste déroulante?


96

Je comprends que vous ne devriez JAMAIS faire confiance aux entrées utilisateur d'un formulaire, principalement en raison du risque d'injection SQL.

Cependant, cela s'applique-t-il également à un formulaire où la seule entrée provient d'une ou plusieurs listes déroulantes (voir ci-dessous)?

J'enregistre le $_POST['size']dans une session qui est ensuite utilisée dans tout le site pour interroger les différentes bases de données (avec une mysqlirequête Select) et toute injection SQL les endommagerait certainement (éventuellement les supprimerait).

Il n'y a pas de zone pour l'entrée utilisateur tapée pour interroger les bases de données, uniquement des listes déroulantes.

<form action="welcome.php" method="post">
<select name="size">
  <option value="All">Select Size</option> 
  <option value="Large">Large</option>
  <option value="Medium">Medium</option>
  <option value="Small">Small</option>
</select>
<input type="submit">
</form>

112
Oui. Rien n'empêche un attaquant de soumettre les valeurs qu'il souhaite dans votre <select>entrée. En effet, même un utilisateur légèrement technique pourrait ajouter des options supplémentaires en utilisant la console du navigateur. si vous conservez une liste blanche de tableau des valeurs disponibles et comparez l'entrée avec celle-ci, vous pouvez atténuer cela (et vous devriez car cela empêche les valeurs indésirables)
Michael Berkowski

13
Vous devez comprendre les choses de base de demande / réponse et le fait que peu importe la façon dont le front-end est construit sur la demande, c'est-à-dire dans ce cas, liste déroulante
Royal Bg

13
@YourCommonSense Parce que c'est une bonne question. Tout le monde ne réalise pas à quel point un client est manipulable. Cela provoquera des réponses très précieuses à ce site.
Cruncher

8
@Cruncher je vois. Pour un stackoverflowian moyen, c'est une science fusée dont ils ont entendu parler auparavant. Même en dépit de la question la plus votée sous la balise PHP.
Votre bon sens

9
"Je comprends que vous ne devriez JAMAIS faire confiance aux commentaires des utilisateurs". Aucune exception.
Prinzhorn

Réponses:


69

Vous pouvez faire quelque chose d'aussi simple que l'exemple suivant pour vous assurer que la taille publiée correspond à ce que vous attendez.

$possibleOptions = array('All', 'Large', 'Medium', 'Small');

if(in_array($_POST['size'], $possibleOptions)) {
    // Expected
} else {
    // Not Expected
}

Ensuite, utilisez mysqli_ * si vous utilisez une version de php> = 5.3.0 que vous devriez être, pour enregistrer votre résultat. S'il est utilisé correctement, cela aidera à l'injection SQL.


est-ce une version abrégée d'un OliverBS «liste blanche»?
Tatters

Pas vraiment juste une version très basique de l'un, vous pouvez ajouter les valeurs dans une base de données à vérifier pour le rendre plus facile et réutilisable, ou peut-être créer une classe de liste blanche avec une méthode spécifique pour chaque liste blanche à vérifier. si vous ne souhaitez pas utiliser de base de données, les propriétés de la liste blanche peuvent se trouver à l'intérieur d'une propriété de tableau dans votre classe de liste blanche.
Oliver Bayes-Shelton

9
Néanmoins, vous devez utiliser les déclarations préparées (recommandé) ou mysqli_real_escape_string. Échappez également correctement les valeurs lors de leur sortie (par exemple, utilisez htmlspecialchars () dans un document HTML).
ComFreek

4
Je suggère de définir le troisième paramètre de in_arrayà truepour une comparaison stricte. Je ne suis pas sûr de ce qui pourrait mal tourner, mais une comparaison lâche est assez bizarre.
Brilliand

1
Les valeurs @OliverBS $ _POST peuvent également être des tableaux. Les chaînes numériques se comparent à des nombres ('5' == '05'). Je ne pense pas que vous ayez une faille de sécurité dans votre exemple spécifique, mais les règles sont complexes, et des failles pourraient s'ouvrir pour des raisons que je ne comprends même pas. Une comparaison stricte est plus facile à raisonner, et donc plus facile à utiliser en toute sécurité.
Brilliand

195

Oui, vous devez vous protéger contre cela.

Laissez-moi vous montrer pourquoi, en utilisant la console développeur de Firefox:

J'ai modifié l'une des valeurs de la liste déroulante pour qu'elle soit une instruction de table de dépôt

Si vous ne nettoyez pas ces données, votre base de données sera détruite. (Ce n'est peut-être pas une instruction SQL totalement valide, mais j'espère avoir compris mon point de vue.)

Ce n'est pas parce que vous avez limité les options disponibles dans votre liste déroulante que vous avez limité les données que je peux envoyer à votre serveur.

Si vous avez essayé de restreindre davantage ce comportement en utilisant sur votre page, mes options incluent la désactivation de ce comportement ou simplement l'écriture d'une requête HTTP personnalisée sur votre serveur qui imite de toute façon cette soumission de formulaire. Il y a un outil appelé curl utilisé pour exactement cela, et je pense que la commande pour soumettre cette injection SQL ressemblerait de toute façon à ceci:

curl --data "size=%27%29%3B%20DROP%20TABLE%20*%3B%20--"  http://www.example.com/profile/save

(Ce n'est peut-être pas une commande curl totalement valide, mais encore une fois, j'espère avoir compris mon point de vue.)

Alors, je répète:

Ne faites JAMAIS confiance à l'entrée de l'utilisateur. Protégez-vous TOUJOURS.

Ne présumez pas qu'aucune entrée utilisateur n'est jamais sûre. Il est potentiellement dangereux même s'il arrive par un autre moyen qu'un formulaire. Rien de tout cela n'est assez fiable pour renoncer à vous protéger de l'injection SQL.


24
Sans parler de la création d'une charge utile personnalisée avec curl. Nettoyez TOUJOURS les entrées côté serveur !
recursion.ninja

14
Une image en dit plus que mille mots.
davidkonrad

4
Cela devrait être la réponse par défaut. Devrait également inclure quelque chose sur curl. Les gens ne comprennent pas que vous pouvez envoyer une requête HTTP de n'importe où à n'importe où en utilisant n'importe quel format et en passant des valeurs, il appartient au serveur de s'assurer que la requête est valide avant de la traiter.
retrohacker

2
Comme c'est mignon! Ce sont des petites tables Bobby!
Aura

45

Comme cette question a été identifiée avec , voici une réponse concernant ce type particulier d'attaque:

Comme vous l'avez dit dans les commentaires, vous devez utiliser des instructions préparées pour chaque requête impliquant des données variables, sans exception .

Indépendamment de tout truc HTML!
Il est essentiel de comprendre que les requêtes SQL doivent être correctement formatées quels que soient les facteurs externes, que ce soit une entrée HTML ou autre.

Bien que vous puissiez utiliser la liste blanche suggérée dans d'autres réponses à des fins de validation d'entrée, cela ne devrait affecter aucune action liée à SQL - elles doivent rester les mêmes, peu importe si vous avez validé l'entrée HTML ou non. Cela signifie que vous devez toujours utiliser des instructions préparées lors de l'ajout de variables dans la requête.

Vous trouverez ici une explication détaillée, pourquoi les instructions préparées sont indispensables et comment les utiliser correctement et où elles ne sont pas applicables et que faire dans ce cas: Le guide de l'auto-stoppeur sur la protection contre les injections SQL

De plus, cette question a été identifiée avec . Surtout par accident, je présume, mais de toute façon, je dois vous avertir que le mysqli brut n'est pas une substitution adéquate aux anciennes fonctions mysq_ * . Tout simplement parce que s'il est utilisé dans l'ancien style, il n'ajoutera aucune sécurité. Bien que la prise en charge des instructions préparées soit pénible et gênante, au point que l'utilisateur PHP moyen est tout simplement incapable de les essayer. Ainsi, si aucun ORM ou une sorte de bibliothèque d'abstraction n'est option, alors PDO est votre seul choix.


5
i = aléatoire (0, 15); // une requête utilisant i. Dois-je encore préparer une déclaration ici?
Cruncher

2
Quelle est la mise en œuvre de random?
Slicedpan

9
@YourCommonSense vue étroite? Pour moi, "Toujours faire X, et je ne fournirai aucun raisonnement à ce sujet" est une vision étroite.
Cruncher

5
@Cruncher Je ne vois aucune raison de ne pas toujours utiliser des déclarations préparées, à chaque fois, pour tout, toujours. Pouvez-vous m'en dire un?
Wesley Murch

3
@Cruncher Avec qui je suis d'accord, il semble que vous jouiez Devil's Advocate. La stipulation dans la réponse est également "impliquant des données variables" . Il y a peut-être quelques cas comme le casting PHP int, mais il semble préférable d'utiliser simplement des instructions préparées. Dire aux gens (inexpérimentés) qu'une amélioration mineure des performances est plus importante que la sécurité n'est pas du tout. La réponse est "incomplète" mais elle envoie un message fort que les autres ignorent. Je vais reposer mon cas.
Wesley Murch

12

Oui.

N'importe qui peut usurper n'importe quoi pour les valeurs qui sont réellement envoyées -

Donc, pour valider les menus déroulants, vous pouvez simplement vérifier que la valeur avec laquelle vous travaillez était dans la liste déroulante - quelque chose comme celui-ci serait la meilleure façon (la plus paranoïaque):

if(in_array($_POST['ddMenu'], $dropDownValues){

    $valueYouUseLaterInPDO = $dropDownValues[array_search("two", $arr)];

} else {

    die("effin h4x0rs! Keep off my LAMP!!"); 

}

5
Cette réponse est incomplète, vous devez en outre utiliser des instructions préparées, de sorte que l'injection ne soit pas possible quelle que soit la valeur définie
Slicedpan

7
@Slicedpan Vraiment? On dirait que vous sautez dans le train en marche sans savoir pourquoi ... Si j'ai une poignée d'entrées possibles dans une requête, de sorte que je puisse vérifier qu'elles sont toutes correctes (ce que je sais, car je les ai faites), alors vous ne gagnez aucun avantage de sécurité supplémentaire en utilisant une déclaration préparée
Cruncher

3
Sauf que le fait d'appliquer l'utilisation d'instructions préparées en tant que convention vous protège de l'introduction de vulnérabilités d'injection SQL à l'avenir.
Slicedpan

1
@Cruncher Faire une promesse "toutes les valeurs de la liste déroulante seront toujours sûres" est une promesse très dangereuse à faire. Les valeurs sont modifiées, le code n'est pas nécessairement mis à jour. Celui qui met à jour les valeurs peut même ne pas savoir ce qu'est une valeur dangereuse! Surtout dans le domaine de la programmation Web, qui regorge de toutes sortes de problèmes de sécurité, lésiner sur quelque chose comme ça est tout simplement irresponsable (et d'autres mots moins gentils).
hyde

1
@Cruncher Si vous gérez correctement vos trucs SQL, vous pouvez accepter n'importe quelle entrée sans interférer avec le bon fonctionnement du SQL. La partie SQL doit être préparée et prête à tout accepter, peu importe d'où il vient. Tout le reste est sujet aux erreurs.
glglgl

8

Une façon de se protéger contre les utilisateurs qui modifient vos listes déroulantes à l'aide de la console consiste à n'utiliser que des valeurs entières. Ensuite, vous pouvez valider que la valeur POST contient un entier et utiliser un tableau pour le convertir en texte si nécessaire. Par exemple:

<?php
// No, you don't need to specify the numbers in the array but as we're using them I always find having them visually there helpful.
$sizes = array(0 => 'All', 1 => 'Large', 2 => 'Medium', 3 => 'Small');
$size = filter_input(INPUT_POST, "size", FILTER_VALIDATE_INT);

echo '<select name="size">';
foreach($sizes as $i => $s) {
    echo '<option value="' . $i . '"' . ($i == $size ? ' selected' : '') . '>' . $s . '</option>';
}
echo '</select>';

Ensuite, vous pouvez utiliser $sizedans votre requête en sachant qu'il ne contiendra jamais FALSEqu'un entier.


2
@OliverBS Qu'est-ce que filter_inputsinon un chèque côté serveur? Personne ne peut publier autre chose qu'un entier.
Styphon

Merci Styphon, donc cela remplace le formulaire dans l'OP? Et cela serait-il applicable à des formulaires plus grands avec plusieurs listes déroulantes? Si j'y ai ajouté un autre menu déroulant pour dire «couleur»?
Tatters

@SamuelTattersfield Oui et oui. Vous pouvez l'utiliser pour autant de listes déroulantes que vous le souhaitez, avec autant d'options que vous le souhaitez. Il vous suffit de créer un nouveau tableau pour chaque liste déroulante et de mettre toutes les options de la liste déroulante dans le tableau.
Styphon

Agréable! Je l'aime. Je vais essayer et voir ce que je reçois dans les tests.
Tatters

@SamuelTattersfield Super, assurez-vous simplement d'utiliser également la filter_inputpièce pour la validation, sinon c'est aussi utile qu'une théière en chocolat pour la sécurité.
Styphon

7

Les autres réponses couvrent déjà ce que vous devez savoir. Mais peut-être que cela aide à clarifier un peu plus:

Il y a DEUX CHOSES que vous devez faire:

1. Validez les données du formulaire.

Comme le montre très clairement la réponse de Jonathan Hobbs , le choix de l'élément html pour la saisie du formulaire ne fait aucun filtrage fiable pour vous.

La validation est généralement effectuée d'une manière qui ne modifie pas les données, mais qui montre à nouveau le formulaire, avec les champs marqués comme "Veuillez corriger cela".

La plupart des frameworks et CMS ont des générateurs de formulaires qui vous aident dans cette tâche. Et pas seulement cela, ils aident également contre CSRF (ou "XSRF"), qui est une autre forme d'attaque.

2. Variables de nettoyage / d'échappement dans les instructions SQL.

.. ou laissez les déclarations préparées faire le travail à votre place.

Si vous créez une instruction SQL (My) avec des variables, fournies par l'utilisateur ou non, vous devez échapper et citer ces variables.

En général, une telle variable que vous insérez dans une instruction MySQL doit être soit une chaîne, soit quelque chose que PHP peut transformer de manière fiable en une chaîne que MySQL peut digérer. Tels que les nombres.

Pour les chaînes, vous devez ensuite choisir l'une des nombreuses méthodes pour échapper à la chaîne, c'est-à-dire remplacer tous les caractères qui auraient des effets secondaires dans MySQL.

  • Dans MySQL + PHP à l'ancienne, mysql_real_escape_string () fait le travail. Le problème est qu'il est beaucoup trop facile à oublier, vous devez donc absolument utiliser des instructions préparées ou des générateurs de requêtes.
  • Dans MySQLi, vous pouvez utiliser des instructions préparées.
  • La plupart des frameworks et CMS fournissent des générateurs de requêtes qui vous aident dans cette tâche.

Si vous avez affaire à un nombre, vous pouvez omettre les échappements et les guillemets (c'est pourquoi les instructions préparées permettent de spécifier un type).

Il est important de souligner que vous échappez aux variables pour l'instruction SQL, et PAS pour la base de données elle-même . La base de données stockera la chaîne d'origine, mais l'instruction a besoin d'une version échappée.

Que se passe-t-il si vous en omettez un?

Si vous n'utilisez pas la validation de formulaire , mais que vous nettoyez votre entrée SQL, vous pourriez voir toutes sortes de mauvaises choses se produire, mais vous ne verrez pas d'injection SQL! (*)

Tout d'abord, cela peut amener votre application dans un état que vous n'aviez pas prévu. Par exemple, si vous souhaitez calculer l'âge moyen de tous les utilisateurs, mais qu'un utilisateur a donné "aljkdfaqer" pour l'âge, votre calcul échouera.

Deuxièmement, il peut y avoir toutes sortes d'autres attaques par injection que vous devez prendre en compte: par exemple, l'entrée utilisateur peut contenir du javascript ou d'autres éléments.

Il peut encore y avoir des problèmes avec la base de données: par exemple, si un champ (colonne de table de base de données) est limité à 255 caractères et que la chaîne est plus longue que cela. Ou si le champ n'accepte que des nombres et que vous essayez d'enregistrer une chaîne non numérique à la place. Mais ce n'est pas une "injection", il s'agit simplement de "planter l'application".

Mais, même si vous avez un champ de texte libre dans lequel vous autorisez toute entrée sans aucune validation, vous pouvez toujours l'enregistrer dans la base de données comme ça, si vous l'échappez correctement quand il va à une instruction de base de données. Le problème survient lorsque vous souhaitez utiliser cette chaîne quelque part.

(*) ou ce serait quelque chose de vraiment exotique.

Si vous n'échappez pas aux variables pour les instructions SQL , mais que vous avez validé l'entrée du formulaire, vous pouvez toujours voir de mauvaises choses se produire.

Tout d'abord, vous risquez que lorsque vous enregistrez des données dans la base de données et que vous la chargez à nouveau, ce ne sera plus les mêmes données, "perdues lors de la traduction".

Deuxièmement, cela peut entraîner des instructions SQL non valides et ainsi planter votre application. Par exemple, si une variable contient un guillemet ou un guillemet double, selon le type de guillemet que vous utilisez, vous obtiendrez une déclaration MySQL invalide.

Troisièmement, cela peut toujours provoquer une injection SQL.

Si votre entrée utilisateur à partir de formulaires est déjà filtrée / validée, l' injection intentionnelle de SQl peut devenir moins probable, SI votre entrée est réduite à une liste d'options codée en dur, ou si elle est limitée aux nombres. Mais toute entrée de texte libre peut être utilisée pour l'injection SQL, si vous n'échappez pas correctement les variables dans les instructions SQL.

Et même si vous n'avez aucune entrée de formulaire, vous pouvez toujours avoir des chaînes de toutes sortes de sources: lues à partir du système de fichiers, extraites d'Internet, etc. Personne ne peut garantir que ces chaînes sont sûres.


Aucune donnée trop longue, pas une chaîne dans un champ numérique ne plantera quoi que ce soit
Votre bon sens

hmm, je viens d'essayer cela maintenant, et en effet, il affiche un avertissement au lieu d'une erreur. Je pense que c'est PDO qui transforme cela en erreur. Je suis sûr que j'ai discuté dans une douzaine de problèmes sur drupal.org où une chaîne est trop longue pour un varchar.
donquixote

6

Votre navigateur Web ne "sait" pas qu'il reçoit une page de php, il ne voit que du HTML. Et la couche http en sait encore moins que cela. Vous devez être capable de gérer presque tous les types d'entrées qui peuvent traverser la couche http (heureusement pour la plupart des entrées, php donnera déjà une erreur). Si vous essayez d'empêcher les requêtes malveillantes de gâcher votre base de données, vous devez supposer que le gars à l'autre bout du fil sait ce qu'il fait et qu'il n'est pas limité à ce que vous pouvez voir dans votre navigateur dans des circonstances normales ( sans parler de ce que vous pouvez manipuler avec les outils de développement d'un navigateur). Alors oui, vous devez prendre en compte toutes les entrées de votre liste déroulante, mais pour la plupart des entrées, vous pouvez donner une erreur.


6

Le fait que vous ayez limité l'utilisateur à n'utiliser que les valeurs d'une certaine liste déroulante n'est pas pertinent. Un utilisateur technique peut capturer la requête http envoyée à votre serveur avant qu'elle ne quitte son réseau, la modifier à l'aide d'un outil tel qu'un serveur proxy local, puis la poursuivre. À l'aide de la demande modifiée, ils peuvent envoyer des valeurs de paramètres qui ne sont pas celles que vous avez spécifiées dans la liste déroulante. Les développeurs doivent avoir la mentalité que les restrictions des clients n'ont souvent aucun sens, car tout ce qui touche un client peut être modifié. La validation du serveur est requise à chaque point d'entrée des données client. Les attaquants comptent sur la naïveté des développeurs dans ce seul aspect.


5

Il est préférable d'utiliser une requête paramétrée afin de garantir contre l'injection SQL. Dans ce cas, l'apparence de la requête serait la suivante:

SELECT * FROM table WHERE size = ?

Lorsque vous fournissez une requête comme celle-ci avec du texte dont l'intégrité n'est pas vérifiée (l'entrée n'est pas validée sur le serveur) et qu'elle contient du code d'injection SQL, elle sera gérée correctement. En d'autres termes, la requête entraînera quelque chose comme ceci dans la couche de base de données:

SELECT * FROM table WHERE size = 'DROP table;'

Cela sélectionnera simplement 0 résultats lors de son retour, ce qui rendra la requête inefficace en causant réellement des dommages à la base de données sans avoir besoin d'une liste blanche, d'une vérification de vérification ou d'autres techniques. Veuillez noter qu'un programmeur responsable fera la sécurité dans les couches, et validera souvent en plus du paramétrage des requêtes. Cependant, il y a très peu de raisons de ne pas paramétrer vos requêtes du point de vue des performances et la sécurité ajoutée par cette pratique est une bonne raison pour vous familiariser avec les requêtes paramétrées.


4

Tout ce qui est soumis à partir de votre formulaire arrive sur votre serveur sous forme de texte à travers les fils. Rien n'empêche quiconque de créer un bot pour imiter le client ou le saisir à partir d'un terminal s'il le souhaite. Ne supposez jamais que parce que vous avez programmé le client, il agira comme vous le pensez. C'est vraiment facile à usurper.

Exemple de ce qui peut et se produira lorsque vous faites confiance au client.


4

Un pirate informatique peut contourner complètement le navigateur, y compris la vérification du formulaire Javascript, en envoyant une requête via Telnet. Bien sûr, il regardera le code de votre page html pour obtenir les noms de champs qu'il doit utiliser, mais à partir de là, c'est «tout va» pour lui. Vous devez donc vérifier toutes les valeurs soumises sur le serveur comme si elles ne provenaient pas de votre page html.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.