Pourquoi le mécanisme de prévention d'injection SQL a-t-il évolué dans le sens de l'utilisation de requêtes paramétrées?

59

À mon avis, les attaques par injection SQL peuvent être évitées par:

  1. Filtrer, filtrer et encoder soigneusement les entrées (avant leur insertion dans SQL)
  2. Utilisation d' instructions préparées / requêtes paramétrées

Je suppose qu'il y a des avantages et des inconvénients pour chacun, mais pourquoi le n ° 2 a-t-il décollé et est-il considéré comme un moyen plus ou moins efficace de prévenir les attaques par injection? Est-ce simplement plus sûr et moins sujet aux erreurs ou y avait-il d'autres facteurs?

Si je comprends bien, si le n ° 1 est utilisé correctement et que toutes les mises en garde sont prises en compte, cela peut être tout aussi efficace que le n ° 2.

Désinfection, filtrage et encodage

Il y avait une certaine confusion de ma part entre ce que signifiait désinfection , filtrage et codage . Je dirai que, pour mes besoins, tout ce qui précède peut être pris en compte pour l'option 1. Dans ce cas, je comprends que la désinfection et le filtrage peuvent modifier ou supprimer les données d'entrée, tandis que le codage conserve les données telles quelles, mais les code. correctement pour éviter les attaques par injection. Je crois que les données en échappée peuvent être considérées comme un moyen de les encoder.

Requêtes paramétrées vs bibliothèque de codage

Il y a des réponses où les concepts de parameterized querieset encoding librariesqui sont traités de manière interchangeable. Corrigez-moi si je me trompe, mais j'ai l'impression qu'ils sont différents.

D'après ce que je comprends encoding libraries, même s'ils sont bons, ils ont toujours le potentiel de modifier le "Programme" SQL, car ils modifient le code SQL lui-même avant qu'il ne soit envoyé au SGBDR.

Parameterized queries d'autre part, envoyez le programme SQL au SGBDR, qui optimise ensuite la requête, définit le plan d'exécution de la requête, sélectionne les index à utiliser, etc., puis connecte les données, en tant que dernière étape du SGBDR. lui-même.

Bibliothèque de codage

  data -> (encoding library)
                  |
                  v
SQL -> (SQL + encoded data) -> RDBMS (execution plan defined) -> execute statement

Requête paramétrée

                                               data
                                                 |
                                                 v
SQL -> RDBMS (query execution plan defined) -> data -> execute statement

Importance historique

Certaines réponses mentionnent qu'historiquement, les requêtes paramétrées (PQ) étaient créées pour des raisons de performances et avant que les attaques par injection ciblant des problèmes de codage ne deviennent populaires. À un moment donné, il est devenu évident que le PQ était également assez efficace contre les attaques par injection. Pour rester dans l’esprit de ma question, pourquoi PQ est-il resté la méthode de choix et pourquoi at-il prospéré au-dessus de la plupart des autres méthodes en matière de prévention des attaques par injection SQL?

Dennis
la source
1
Les commentaires ne sont pas pour une discussion prolongée; cette conversation a été déplacée pour discuter .
maple_shaft
23
Les instructions préparées ne résultent pas de l'évolution des attaques par injection SQL. Ils étaient là depuis le début. Votre question est basée sur une fausse prémisse.
user207421
4
Si vous pensez être plus intelligent que les méchants, optez pour # 1
paparazzo le
1
"pourquoi le PQ est-il resté la méthode de choix" parce que c'est la plus simple et la plus robuste. Plus les avantages de performance mentionnés ci-dessus pour PQ. Il n'y a vraiment pas d'inconvénient.
Paul Draper
1
Parce que c'est la bonne solution au problème de la façon de faire des requêtes, même s'il n'y avait pas eu le problème de l'injection SQL dans un contexte de sécurité . Les formulaires nécessitant une échappement et l'utilisation de données intrabande avec des commandes constituent toujours un bogue de conception car ils sont sujets aux erreurs, qu'ils sont contre-intuitifs et qu'ils se cassent mal lorsqu'ils sont mal utilisés. Voir aussi: scripts shell.
R ..

Réponses:

147

Le problème est que # 1 nécessite que vous analysiez et interprétiez efficacement l'intégralité de la variante SQL sur laquelle vous travaillez afin que vous sachiez si elle fait quelque chose, elle ne devrait pas. Et gardez ce code à jour lorsque vous mettez à jour votre base de données. Partout, vous acceptez les entrées pour vos requêtes. Et pas bousiller.

Donc oui, ce genre de chose arrêterait les attaques par injection SQL, mais sa mise en œuvre est absurdement plus coûteuse.

Telastyn
la source
60
@dennis - Eh bien, qu'est-ce qu'une citation dans votre variante SQL? "? '?"? U + 2018? \ U2018? Existe-t-il des astuces pour séparer les expressions? Vos sous-requêtes peuvent-elles effectuer des mises à jour? Il y a beaucoup de choses à considérer.
Telastyn
7
@Dennis Chaque moteur de base de données a sa propre façon de faire des choses, comme échapper des caractères dans des chaînes. Cela fait beaucoup de trous à combler, en particulier si une application doit fonctionner avec plusieurs moteurs de base de données ou être compatible avec les versions futures du même moteur, car cela pourrait modifier une syntaxe de requête mineure qui pourrait être exploitable.
12
Un autre avantage des instructions préparées est le gain de performances que vous obtenez lorsque vous devez réexécuter la même requête, avec des valeurs différentes. En outre, les instructions préparées peuvent savoir si une valeur est réellement définie comme nullune chaîne ou un nombre et agir en conséquence. C'est très bon pour la sécurité. Et même si vous exécutez la requête une fois, le moteur de base de données l’aura déjà optimisée. Mieux encore s'il est mis en cache!
Ismael Miguel
8
@ Dennis M. Henry Null vous remerciera de l'avoir fait correctement.
Mathieu Guindon
14
@Dennis le prénom n'est pas pertinent. Le problème est avec le nom de famille. Voir Stack Overflow , Programmers.SE , Fox Sports , Wired , BBC , et tout ce que vous pouvez trouver dans une recherche rapide sur Google ;-)
Mathieu Guindon
80

Parce que l'option 1 n'est pas une solution. Filtrer et filtrer signifie rejeter ou supprimer une entrée non valide. Mais toute entrée peut être valide. Par exemple, apostrophe est un caractère valide portant le nom "O'Malley". Il suffit juste de l'encoder correctement avant de l'utiliser en SQL, comme le font les instructions préparées.


Après avoir ajouté la note, il semble que vous vous demandiez essentiellement pourquoi utiliser une fonction de bibliothèque standard plutôt que d'écrire votre propre code fonctionnellement similaire à partir de zéro. Vous devriez toujours préférer les solutions de bibliothèque standard à l'écriture de votre propre code. C'est moins de travail et plus facile à maintenir. C’est le cas pour toutes les fonctionnalités, mais en particulier pour les éléments sensibles à la sécurité, il n’a absolument aucun sens de réinventer la roue par vous-même.

JacquesB
la source
2
C'est tout (et c'était la partie manquante dans deux autres réponses, donc +1). Compte tenu de la formulation de la question, il ne s’agit pas de désinfecter les entrées de l’utilisateur, mais bien de la question: «entrée de filtrage (avant insertion)». Si la question porte maintenant sur la désinfection de l'entrée, pourquoi voudriez-vous le faire vous-même au lieu de laisser la bibliothèque le faire (tout en perdant la possibilité d'avoir des plans d'exécution mis en cache, en passant)?
Arseni Mourzenko
8
@Dennis: Désinfecter ou filtrer signifie supprimer des informations. Encoder signifie transformer la représentation des données sans perdre d'informations.
JacquesB
9
@Dennis: le filtrage signifie l'acceptation ou le rejet de la saisie de l'utilisateur. Par exemple, “Jeff” serait filtré comme entrée du champ “Âge de l'utilisateur”, car la valeur est évidemment invalide. Si, au lieu de filtrer les entrées, vous commencez à les transformer, par exemple en remplaçant le caractère guillemet simple, vous agissez exactement de la même manière que les bibliothèques de bases de données dans lesquelles elles utilisent des requêtes paramétrées; dans ce cas, votre question est simplement «Pourquoi utiliserais-je quelque chose d'existant qui a été écrit par des experts du domaine alors que je peux réinventer la roue dans chaque projet?»
Arseni Mourzenko, le
3
@Dennis: O\'Malleyutilise la barre oblique pour échapper à la citation pour une insertion correcte (du moins dans certaines bases de données). En MS SQL ou Access, il peut être échappé avec un devis supplémentaire O''Malley. Pas très portable si vous devez le faire vous-même.
AbraCadaver
5
Je ne peux pas vous dire combien de fois mon nom a été carrément rejeté par un système. Parfois, j'ai même vu des erreurs causées par une injection SQL simplement en utilisant mon nom. Heck, on m'a demandé une fois de changer mon nom d'utilisateur parce que je casserais quelque chose sur le backend.
Alexander O'Mara
60

Si vous essayez de traiter des chaînes, vous ne générez pas vraiment de requête SQL. Vous générez une chaîne pouvant générer une requête SQL. Il existe un niveau d'indirection qui laisse beaucoup de place aux erreurs et aux bugs. C'est un peu surprenant, étant donné que dans la plupart des contextes, nous sommes heureux d'interagir avec quelque chose par programmation. Par exemple, si nous avons une structure de liste et voulons ajouter un élément, nous ne faisons généralement pas:

List<Integer> list = /* a list of 1, 2, 3 */
String strList = list.toString();   /* to get "[1, 2, 3]" */
strList = /* manipulate strList to become "[1, 2, 5, 3]" */
list = parseList(strList);

Si quelqu'un suggère de le faire, vous répondriez à juste titre que c'est plutôt ridicule et qu'il faut simplement faire:

List<Integer> list = /* ... */;
list.add(5, position=2);

Cela interagit avec la structure de données à son niveau conceptuel. Il n'introduit aucune dépendance sur la manière dont cette structure peut être imprimée ou analysée. Ce sont des décisions complètement orthogonales.

Votre première approche est semblable au premier exemple (un peu moins bon): vous supposez que vous pouvez construire par programme la chaîne qui sera correctement analysée comme la requête que vous souhaitez. Cela dépend de l'analyseur et de toute une logique de traitement de chaîne.

La deuxième approche consistant à utiliser des requêtes préparées ressemble beaucoup plus au deuxième exemple. Lorsque vous utilisez une requête préparée, vous analysez essentiellement une pseudo-requête qui est légale mais contient des espaces réservés, puis vous utilisez une API pour substituer correctement certaines valeurs. Vous n'impliquez plus le processus d'analyse et vous n'avez pas à vous soucier du traitement des chaînes.

En général, il est beaucoup plus facile et beaucoup moins sujet aux erreurs d'interagir avec les choses à leur niveau conceptuel. Une requête n'est pas une chaîne, c'est ce que vous obtenez lorsque vous analysez une chaîne ou en créez une par programme (ou toute autre méthode qui vous permet d'en créer une).

Il existe une bonne analogie entre les macros de style C qui effectuent un remplacement de texte simple et les macros de style Lisp qui génèrent du code arbitraire. Avec les macros de style C, vous pouvez remplacer du texte dans le code source, ce qui signifie que vous avez la possibilité d'introduire des erreurs de syntaxe ou un comportement trompeur. Avec les macros Lisp, vous générez du code sous la forme que le compilateur le traite (c'est-à-dire que vous renvoyez les structures de données réelles que le compilateur traite, et non le texte que le lecteur doit traiter avant que le compilateur puisse y accéder). . Avec une macro Lisp, vous ne pouvez pas générer quelque chose qui serait une erreur d’analyse. Par exemple, vous ne pouvez pas générer (let ((ab) a .

Même avec les macros Lisp, vous pouvez toujours générer du code incorrect, car vous ne devez pas nécessairement connaître la structure qui est supposée être là. Par exemple, en Lisp, (let ((ab)) a) signifie "établit une nouvelle liaison lexicale de la variable a à la valeur de la variable b, puis renvoie la valeur de a", et (let (ab) a) signifie "établit de nouvelles liaisons lexicales des variables a et b et initialise les deux à néant, puis renvoie la valeur de a." Celles-ci sont syntaxiquement correctes, mais elles signifient des choses différentes. Pour éviter ce problème, vous pouvez utiliser des fonctions plus sensibles à la sémantique et effectuer les opérations suivantes:

Variable a = new Variable("a");
Variable b = new Variable("b");
Let let = new Let();
let.getBindings().add(new LetBinding(a,b));
let.setBody(a);
return let;

Avec quelque chose comme ça, il est impossible de renvoyer quelque chose dont la syntaxe est invalide, et il est beaucoup plus difficile de renvoyer quelque chose qui, accidentellement, n'est pas ce que vous vouliez.

Joshua Taylor
la source
Bonne explication!
Mike Partridge
2
Vous m'avez perdu à la "bonne analogie" mais j'ai voté sur la base de l'explication précédente. :)
Wildcard
1
Excellent exemple! - Et vous pourriez ajouter: Selon le type de données, il est parfois même impossible ou impossible de créer une chaîne analysable. - Que se passe-t-il si l'un de mes paramètres est un champ de texte libre contenant un brouillon d'histoire (environ 10 000 caractères)? ou si un paramètre est une image JPG ? - La seule solution est alors une requête paramétrée
Falco
En fait non, c’est une assez mauvaise description de la raison pour laquelle les déclarations préparées ont évolué en tant que moyen de défense de l’injection SQL. En particulier, l'exemple de code est java, ce qui n'existait pas lorsque les requêtes paramétrées étaient probablement développées dans la période où C / C ++ était considéré comme étant à la pointe de la technologie. Les bases de données SQL ont commencé à être utilisées au début de la période 1970-1980. WAY avant les langues de niveau supérieur où populaire. Heck, je dirais que beaucoup d'entre eux sont venus pour faciliter le travail avec des bases de données (PowerBuilder, ça vous tente?)
TomTom
@ TomTom en fait, je suis d'accord avec la plupart de votre contenu. Je n’ai implicitement abordé que l’aspect sécurité. Sur SO, je réponds à beaucoup de questions SPARQL (langage de requête RDF, avec quelques similitudes avec SQL) et beaucoup de personnes se heurtent à des problèmes car elles concaténent des chaînes plutôt que d’utiliser des requêtes paramétrées. Même en l'absence d'attaques par injection, les requêtes paramétrées aident à éviter les bugs / crash, et les bugs / crashs peuvent également être des problèmes de sécurité, même s'ils ne sont pas des attaques par injection. Donc, je dirais de moins en moins: les requêtes paramétrées sont bonnes, même si l'injection SQL n'était pas un problème, et elles étaient bonnes ...
Joshua Taylor
21

Il est utile que l'option n ° 2 soit généralement considérée comme une pratique recommandée, car la base de données peut mettre en cache la version non paramétrée de la requête. Les requêtes paramétrées sont antérieures à la question de l'injection SQL de plusieurs années (je crois), il se trouve que vous pouvez faire d'une pierre deux coups.

JasonB
la source
10
L'injection SQL est un problème depuis sa première invention. Ce n'est pas devenu un problème plus tard.
Servir le
9
@Servy Théoriquement oui. En pratique, le problème ne s'est véritablement posé que lorsque nos mécanismes de saisie ont été mis en ligne, offrant ainsi une surface d'attaque gigantesque que tout le monde peut marteler.
Jan Doggen
8
Little Bobby Tables ne dirait pas que vous avez besoin d'Internet ni d'une base d'utilisateurs importante pour tirer parti de l'injection SQL. Et bien sûr, les réseaux sont antérieurs à SQL, vous n'avez donc pas besoin d'attendre les réseaux une fois que SQL est sorti. Oui, les vulnérabilités de sécurité sont moins vulnérables lorsque votre application compte un petit nombre d'utilisateurs, mais elles restent des vulnérabilités de sécurité. Les utilisateurs les exploitent quand la base de données elle-même contient des données précieuses (et de nombreuses bases de données très anciennes contenaient des données avec de précieuses bases de données pourraient se permettre la technologie) ..
Servy
5
@Servy à ma connaissance, le SQL dynamique était une fonctionnalité relativement récente; L'utilisation initiale de SQL était principalement pré-compilée / prétraitée avec des paramètres pour les valeurs (entrées et sorties), de sorte que les paramètres dans les requêtes peuvent être antérieurs à l'injection SQL dans le logiciel (peut-être pas dans les requêtes ad hoc / CLI).
Mark Rotteveel
6
Ils peuvent être antérieurs à la connaissance de l' injection SQL.
user253751
20

Simplement dit: ils ne l'ont pas fait. Votre déclaration:

Pourquoi le mécanisme de prévention d'injection SQL a-t-il évolué dans le sens de l'utilisation de requêtes paramétrées?

est fondamentalement défectueux. Les requêtes paramétrées ont existé bien plus longtemps que SQL Injection est au moins largement connu. Celles-ci ont généralement été conçues pour éviter la concentration de chaînes dans la fonctionnalité habituelle de "formulaire de recherche" des applications LOB (secteur d'activité). Beaucoup - BEAUCOUP - d'années - plus tard, quelqu'un a trouvé un problème de sécurité avec ladite manipulation de chaîne.

Je me souviens de faire du SQL il y a 25 ans (quand Internet n'était PAS largement utilisé - il venait juste de commencer) et je me souvenais de faire du SQL par rapport à IBM DB5 IIRC version 5 - et que les requêtes étaient déjà paramétrées.

TomTom
la source
Merci. Pourquoi était-il nécessaire d'éviter la concaténation de chaînes? Il me semble que ce serait une fonctionnalité utile. Quelqu'un at-il eu un problème avec cela?
Dennis
3
Deux en fait. Premièrement, ce n’est pas toujours totalement trivial - pourquoi s’occuper de l’allocation de mémoire, etc. quand cela n’est pas nécessaire. Mais deuxièmement, autrefois, la mise en cache des performances par la base de données SQL n’était pas si bonne, car la compilation SQL coûtait cher. L’effet secondaire de l’utilisation d’instructions SQL préparées (c’est de là que viennent les paramètres), les plans d’exeuction pourraient être réutilisés. SQL Server a introduit le paramétrage automatique (pour réutiliser les plans de requête même sans paramètres - ils sont déduits et implicites). Je pense que 2000 ou 2007 - quelque part entre, IIRC.
TomTom
2
Avoir des requêtes paramétrées n'élimine pas la possibilité de concaténer des chaînes. Vous pouvez faire une concaténation de chaînes pour générer une requête paramétrée. Ce n'est pas parce qu'une fonctionnalité est utile que c'est toujours un bon choix pour un problème donné.
JimmyJames
Oui, mais comme je l’ai dit - au moment où ils ont été inventés, le SQL dynamique a eu un impact assez bon en termes de performances;) même aujourd’hui, les gens vous disent que les plans de requêtes SQL dynamiques sur serveur SQL ne sont pas réutilisés (ce qui est faux depuis - hm - comme J'ai dit quelque chose entre 2000 et 2007 - donc très long). A cette époque, vous vouliez vraiment des déclarations PREPARED si vous exécutez SQL plusieurs fois;)
TomTom
La mise en cache des plans pour le SQL dynamique a en fait été ajoutée à SQL Server 7.0 en 1998 - sqlmag.com/database-performance-tuning/…
Mike Dimmick le
13

En plus de toutes les autres bonnes réponses:

La raison pour laquelle # 2 est préférable, c'est parce qu'il sépare vos données de votre code. Dans le n ° 1, vos données font partie de votre code et c’est de là que viennent toutes les mauvaises choses. Avec n ° 1, vous obtenez votre requête et devez effectuer des étapes supplémentaires pour vous assurer que votre requête comprend vos données en tant que données, tandis qu'en n ° 2, vous obtenez votre code et son code, et vos données sont des données.

Pieter B
la source
3
La séparation du code et des données signifie également que vos défenses contre l’injection de code hostile sont écrites et testées par le fournisseur de la base de données. Par conséquent, si un élément passé en paramètre avec une requête inoffensive finit par détruire ou subvertir votre base de données, la réputation de l'entreprise de base de données est en jeu, et votre organisation pourrait même les poursuivre en justice et gagner. Cela signifie également que si ce code contient un bogue exploitable, il y a de fortes chances pour que ce soit le site de quelqu'un d'autre où tout se passe mal, plutôt que le vôtre. (N'ignorez pas les corrections de sécurité!)
nigel222
11

Les requêtes paramétrées, outre la défense par injection SQL, présentent souvent l'avantage supplémentaire d'être compilées une seule fois, puis exécutées plusieurs fois avec des paramètres différents.

Du point de vue de la base de données SQL select * from employees where last_name = 'Smith', ils select * from employees where last_name = 'Fisher'sont nettement différents et nécessitent donc une analyse, une compilation et une optimisation distinctes. Ils occuperont également des emplacements distincts dans la zone de mémoire dédiée au stockage des instructions compilées. Dans un système lourdement chargé avec un grand nombre de requêtes similaires ayant des paramètres de calcul différents, la surcharge de mémoire peut être considérable.

Par la suite, l’utilisation de requêtes paramétrées offre souvent des avantages majeurs en termes de performances.

mustaccio
la source
Je pense que c'est la théorie (basée sur les instructions préparées utilisées pour les requêtes paramétrées). En pratique, je doute que ce soit souvent le cas, car la plupart des implémentations se contentent de préparer, d'associer, d'exécuter en un seul appel. Vous devez donc utiliser une instruction préparée différente pour chaque requête paramétrée, à moins que vous ne preniez des mesures explicites pour préparer les instructions (et une bibliothèque). -level prepareest souvent très différent d'un niveau SQL réel prepare).
Jcaron
Les requêtes suivantes sont également différentes de l'analyseur SQL: SELECT * FROM employees WHERE last_name IN (?, ?)et SELECT * FROM employees WHERE last_name IN (?, ?, ?, ?, ?, ?).
Damian Yerrick
Oui ils ont. C’est pourquoi MS a ajouté la mise en cache du plan de requête en 1998 à SQL Server 7. Comme dans: Vos informations datent d’une génération.
TomTom
1
@TomTom - La mise en cache du plan de requête n'est pas la même chose que le paramétrage automatique, auquel vous semblez faire allusion. Comme dans, lisez avant de poster.
Mustaccio
@ mustaccio En fait, au moins MS a introduit les deux en même temps.
TomTom
5

Attends mais pourquoi?

L'option 1 signifie que vous devez écrire des routines de désinfection pour chaque type d'entrée, tandis que l'option 2 est moins sujette aux erreurs et nécessite moins de code pour l'écriture / le test / la maintenance.

Presque certainement, "prendre en compte toutes les mises en garde" peut être plus complexe que vous le pensez, et votre langage (par exemple, Java PreparedStatement) en a plus sous le capot que vous ne le pensez.

Les instructions préparées ou les requêtes paramétrées sont précompilées dans le serveur de base de données. Ainsi, lorsque les paramètres sont définis, aucune concaténation SQL n'est effectuée car la requête n'est plus une chaîne SQL. Un avantage supplémentaire est que le SGBDR met en cache la requête et que les appels suivants sont considérés comme étant le même SQL même lorsque les valeurs de paramètre varient, alors qu'avec un SQL concaténé chaque fois que la requête est exécutée avec des valeurs différentes, la requête est différente et le SGBDR doit l'analyser , recréez le plan d'exécution, etc.

Tulains Córdova
la source
1
JDBC ne désinfecte pas l’anithing. Le protocole a une partie spécifique pour le paramètre et la base de données n’interprète pas ces paramètres. C’est pourquoi vous pouvez définir le nom de la table à partir du paramètre.
Talex
1
Pourquoi? si le paramètre n'est pas analysé ou interprété, il n'y a aucune raison d'échapper à quelque chose.
Talex
11
Je pense que vous avez une mauvaise image du fonctionnement d’une requête paramétrée. Les paramètres ne sont pas simplement substitués plus tard, ils ne le sont jamais . Un SGBD transforme toute requête en un "plan", un ensemble d'étapes qu'il va exécuter pour obtenir votre résultat. dans une requête paramétrée, ce plan s'apparente à une fonction: il comporte un certain nombre de variables à fournir lors de son exécution. Au moment où les variables sont fournies, la chaîne SQL a été complètement oubliée et le plan est simplement exécuté avec les valeurs fournies.
IMSoP
2
@IMSoP C'était une idée fausse de la mienne. Bien que je pense que c’est une question commune, comme vous pouvez le constater dans les deux réponses les plus votées à cette question dans SO stackoverflow.com/questions/3271249/… . J'ai lu à ce sujet et vous avez raison. J'ai édité la réponse.
Tulains Córdova
3
@TomTom C'est excellent pour les performances , mais cela ne fait rien pour la sécurité . Au moment où un morceau de SQL dynamique compromis est compilé et mis en cache, le programme a déjà été modifié . La création d'un plan à partir de SQL paramétré non dynamique, puis la transmission d'éléments de données sont toujours fondamentalement différentes d'un SGBD analysant la similarité entre deux requêtes qui lui sont présentées comme des chaînes SQL complètes.
IMSoP
1

Imaginons à quoi ressemblerait une approche idéale de «désinfection, filtrage et codage».

La désinfection et le filtrage peuvent avoir un sens dans le contexte d’une application particulière, mais en fin de compte, ils se résument à dire "vous ne pouvez pas mettre ces données dans la base de données". Pour votre application, cela peut être une bonne idée, mais ce n’est pas une solution que vous pouvez recommander, car certaines applications devront pouvoir stocker des caractères arbitraires dans la base de données.

Donc, cela laisse l'encodage. Vous pouvez commencer par créer une fonction qui code les chaînes en ajoutant des caractères d'échappement, de manière à pouvoir les remplacer par vous-même. Étant donné que différentes bases de données nécessitent différents caractères d'échappement (dans certaines bases de données, \'il ''s'agit de séquences d'échappement valides pour ', mais pas pour d'autres), cette fonction doit être fournie par le fournisseur de la base de données.

Mais toutes les variables ne sont pas des chaînes. Parfois, vous devez remplacer par un entier ou une date. Celles-ci sont représentées différemment des chaînes, vous avez donc besoin de méthodes de codage différentes (là encore, elles devraient être spécifiques au fournisseur de base de données) et vous devez les substituer dans la requête de différentes manières.

Ainsi, les choses seraient peut-être plus faciles si la base de données gérait également les substitutions pour vous. Elle sait déjà quels types de requête attend la requête, comment coder les données en toute sécurité et comment les substituer dans votre requête en toute sécurité. Vous n'avez donc pas à vous inquiéter dans votre code.

À ce stade, nous venons de réinventer les requêtes paramétrées.

Et une fois les requêtes paramétrées, cela ouvre de nouvelles possibilités, telles que l'optimisation des performances et la surveillance simplifiée.

Le codage est difficile à faire correctement, et coder-done-right est impossible à paramétrer.

Si vous aimez vraiment l’interpolation de chaînes comme moyen de construire des requêtes, quelques langages (Scala et ES2015 vous viennent à l’esprit) qui utilisent l’interpolation de chaîne enfichable. Il existe donc des bibliothèques qui vous permettent d’écrire des requêtes paramétrées sont à l'abri de l'injection SQL - donc dans la syntaxe ES2015:

import {sql} from 'cool-sql-library'

let result = sql`select *
    from users
    where user_id = ${user_id}
      and password_hash = ${password_hash}`.execute()

console.log(result)
James_pic
la source
1
"Il est difficile de coder correctement" - hahaha. Ce n'est pas. Un jour ou deux, tout est documenté. J'ai écrit un encodeur il y a de nombreuses années pour un ORM (parce que le serveur SQL a une limite de paramètres et qu'il est donc problématique d'insérer 5 000 à 1 000 lignes dans une instruction (il y a 15 ans). Je ne me souviens pas qu'il s'agisse d'un gros problème.
TomTom
1
Peut-être que SQL Server est suffisamment régulier pour que cela ne pose pas de problème, mais j'ai rencontré des problèmes dans d'autres bases de données: des erreurs de codage de caractères, des options de configuration obscures, des dates et des numéros spécifiques à l'environnement local. Tous peuvent être résolus, mais nécessitent au moins une compréhension rapide des bizarreries de la base de données (je vous regarde, MySQL et Oracle).
James_pic
3
@TomTom Le codage est en réalité très difficile à obtenir une fois que vous tenez compte du temps. Que faites-vous lorsque votre fournisseur de base de données décide de créer un nouveau style de commentaire dans la prochaine version ou lorsqu'un mot clé devient un nouveau mot clé dans une mise à niveau? Vous pourriez théoriquement obtenir un encodage correct pour une version de votre SGBDR et vous tromper lors de la prochaine révision. Ne commencez même pas à savoir ce qui se passe lorsque vous passez d'un fournisseur à un autre avec des commentaires conditionnels utilisant une syntaxe non standard
Eric
@ Eric, c'est franchement horrible. (J'utilise Postgres; s'il a de telles verrues bizarres, je ne les ai pas encore rencontrées.)
Wildcard
0

Dans l'option 1, vous utilisez un jeu d'entrée de taille = infini que vous essayez de mapper sur une très grande taille de sortie. Dans l'option 2, vous avez limité votre entrée à ce que vous avez choisi. En d'autres termes:

  1. Filtrage et filtrage minutieux [à l' infini ] de [ toutes les requêtes SQL sécurisées ]
  2. Utilisation de [ scénarios préconfigurés limités à votre portée ]

Selon d’autres réponses, il semble également présenter certains avantages en termes de performances de limiter votre champ de vision de l’infini à une solution gérable.

Ornithorynque mutant
la source
0

Un modèle mental utile de SQL (dialectes modernes en particulier) est que chaque instruction ou requête SQL est un programme. Dans un programme exécutable binaire natif, les vulnérabilités de sécurité les plus dangereuses sont les débordements où un attaquant peut écraser ou modifier le code du programme avec des instructions différentes.

Une vulnérabilité d’injection SQL est isomorphe à un débordement de mémoire tampon dans un langage tel que C. L’histoire a montré que les dépassements de mémoire tampon sont extrêmement difficiles à éviter - même le code extrêmement critique soumis à une révision ouverte a souvent contenu de telles vulnérabilités.

Un aspect important de l’approche moderne de résolution des vulnérabilités de dépassement de capacité est l’utilisation de mécanismes matériels et de systèmes d’exploitation pour marquer certaines parties de la mémoire comme non exécutables et pour marquer d’autres en mémoire. (Voir l'article de Wikipédia sur la protection de l' espace Executable , par exemple). De cette façon, même si un attaquant pourrait modifier les données, l'attaquant ne peut pas provoquer leurs données injectées à traiter sous forme de code.

Donc, si une vulnérabilité d'injection SQL équivaut à un dépassement de tampon, quel est l'équivalent SQL d'un bit NX ou d'une page mémoire en lecture seule? La réponse est: les instructions préparées , qui incluent des requêtes paramétrées ainsi que des mécanismes similaires pour les requêtes sans requête. L'instruction préparée étant compilée avec certaines parties marquées en lecture seule, un attaquant ne peut donc pas modifier ces parties du programme, et d'autres parties marquées comme données non exécutables (paramètres de l'instruction préparée), dans lesquelles l'attaquant pourrait injecter des données, mais qui ne sera jamais traité comme un code de programme, éliminant ainsi la plupart des risques d’abus.

Assainir les commentaires des utilisateurs est certes utile, mais pour être vraiment sûr, vous devez être paranoïaque (ou, de manière équivalente, penser comme un attaquant). Pour ce faire, une surface de contrôle en dehors du texte du programme est proposée, et les instructions préparées fournissent cette surface de contrôle pour SQL. Il n’est donc pas surprenant que les déclarations préparées, et donc les requêtes paramétrées, constituent l’approche recommandée par la grande majorité des professionnels de la sécurité.

Daniel Pryden
la source
Tout cela est beau et dandy, mais cela ne règle pas du tout la question du titre.
TomTom
1
@TomTom: Que voulez-vous dire? La question est précisément de savoir pourquoi les requêtes paramétrées sont le mécanisme privilégié pour empêcher l’injection SQL; Ma réponse explique pourquoi les requêtes paramétrées sont plus sécurisées et robustes que la désinfection des entrées utilisateur.
Daniel Pryden
Je suis désolé, mais ma question se lit "Pourquoi le mécanisme de prévention d’injection SQL a-t-il évolué vers l’utilisation de requêtes paramétrées?". Ils n'ont pas. Il ne s'agit pas de maintenant, mais de l'histoire.
TomTom
0

J'écris déjà à ce sujet ici: https://stackoverflow.com/questions/6786034/can-parameterized-statement-stop-all-sql-injection/33033576#33033576

Mais, pour rester simple:

La façon dont fonctionnent les requêtes paramétrées est que sqlQuery est envoyé en tant que requête et que la base de données sait exactement ce que fera cette requête. Ce n'est qu'alors qu'elle insérera le nom d'utilisateur et les mots de passe sous forme de valeurs. Cela signifie qu'ils ne peuvent pas effectuer la requête, car la base de données sait déjà ce que la requête va faire. Donc, dans ce cas, il faudrait rechercher un nom d'utilisateur "Nobody OR 1 = 1 '-" et un mot de passe vide, ce qui devrait être faux.

Cependant, ce n'est pas une solution complète, et la validation des entrées devra toujours être effectuée, car cela n'affectera pas d'autres problèmes, tels que les attaques XSS, car vous pourriez toujours insérer du code javascript dans la base de données. Ensuite, si ceci est lu sur une page, il sera affiché en javascript normal, en fonction de la validation de la sortie. La meilleure chose à faire est donc de continuer à utiliser la validation des entrées, mais en utilisant des requêtes paramétrées ou des procédures stockées pour arrêter toute attaque SQL

Josip Ivic
la source
0

Je n'ai jamais utilisé SQL. Mais évidemment, vous entendez parler des problèmes rencontrés par les utilisateurs et les développeurs SQL ont eu des problèmes avec cette "injection SQL". Pendant longtemps, je ne pouvais pas le comprendre. Et puis j'ai réalisé que les gens créaient des instructions SQL, de véritables instructions sources textuelles SQL, en concaténant des chaînes, dont certaines étaient entrées par un utilisateur. Et ma première pensée sur cette réalisation a été un choc. Choc total. J'ai pensé: Comment quelqu'un peut-il être aussi ridiculement stupide et créer des déclarations dans n'importe quel langage de programmation comme celui-là? Pour un développeur C, C ++, Java ou Swift, c'est de la folie.

Cela dit, il n’est pas très difficile d’écrire une fonction C prenant une chaîne de caractères C comme argument et produisant une chaîne différente qui ressemble exactement à un littéral de chaîne du code source C qui représente la même chaîne. Par exemple, cette fonction traduirait abc en "abc", et "abc" en "\" abc \ "" et "\" abc \ "" en "\" \\ "abc \\" \ "". (Eh bien, si cela vous semble faux, c'est du HTML. C'était juste quand je l'ai tapé, mais pas quand il est affiché) Et une fois que cette fonction C est écrite, il n'est pas difficile du tout de générer du code source C où le texte d'un champ de saisie fourni par l'utilisateur est transformé en littéral de chaîne C. Ce n'est pas difficile à sécuriser. Pourquoi les développeurs SQL n'utilisent-ils pas cette approche pour éviter les injections SQL me dépasse.

"Assainir" est une approche totalement imparfaite. La faille fatale est que certaines entrées utilisateur sont illégales. Vous vous retrouvez avec une base de données où un champ de texte générique ne peut pas contenir du texte comme; Drop Table ou tout ce que vous utiliseriez dans une injection SQL pour causer des dommages. Je trouve cela tout à fait inacceptable. Si une base de données stocke du texte, elle devrait pouvoir stocker n’importe quel texte. Et le défaut pratique est que le désinfectant ne semble pas pouvoir le faire correctement :-(

Bien entendu, les requêtes paramétrées sont ce à quoi tout programmeur utilisant un langage compilé s’attendrait. Cela rend la vie tellement plus facile: vous avez une entrée de chaîne, et vous n'avez même pas la peine de la traduire en chaîne SQL, mais vous la transmettez simplement en tant que paramètre, sans aucun risque que des caractères de cette chaîne ne causent des dommages.

Donc, du point de vue du développeur qui utilise des langages compilés, la désinfection n’arriverait jamais. Le besoin de désinfection est insensé. Les requêtes paramétrées sont la solution évidente au problème.

(J'ai trouvé la réponse de Josip intéressante. Il dit en gros qu'avec des requêtes paramétrées, vous pouvez arrêter toute attaque contre SQL, mais vous pouvez alors avoir du texte dans votre base de données utilisé pour créer une injection JavaScript :-( Eh bien, nous avons à nouveau le même problème , et je ne sais pas si Javascript a une solution à cela.

gnasher729
la source
-2

Le principal problème est que les pirates informatiques ont trouvé des moyens d’entourer l’assainissement alors que les requêtes paramétrées étaient une procédure existante qui fonctionnait parfaitement avec les avantages supplémentaires de la performance et de la mémoire.

Certaines personnes simplifient le problème car "il ne s'agit que de guillemets simples et de guillemets doubles", mais les pirates informatiques ont trouvé des moyens intelligents d'éviter la détection, par exemple en utilisant différents codages ou en utilisant des fonctions de base de données.

Quoi qu'il en soit, vous n'aviez besoin que d'oublier une seule chaîne pour créer une violation de données catastrophique. Les pirates ont pu automatiser des scripts pour télécharger la base de données complète avec une série ou des requêtes. Si le logiciel est bien connu, comme une suite open source ou une suite professionnelle réputée, vous pouvez simplement consulter la table des utilisateurs et des mots de passe.

D'autre part, le simple fait d'utiliser des requêtes concaténées consistait simplement à apprendre à utiliser et à s'y habituer.

Borjab
la source