À mon avis, les attaques par injection SQL peuvent être évitées par:
- Filtrer, filtrer et encoder soigneusement les entrées (avant leur insertion dans SQL)
- 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 queries
et encoding libraries
qui 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?
Réponses:
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.
la source
null
une 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!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.
la source
O\'Malley
utilise 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émentaireO''Malley
. Pas très portable si vous devez le faire vous-même.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:
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:
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:
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.
la source
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.
la source
Simplement dit: ils ne l'ont pas fait. Votre déclaration:
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.
la source
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.
la source
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'
, ilsselect * 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.
la source
prepare
est souvent très différent d'un niveau SQL réelprepare
).SELECT * FROM employees WHERE last_name IN (?, ?)
etSELECT * FROM employees WHERE last_name IN (?, ?, ?, ?, ?, ?)
.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.
la source
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:
la source
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:
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.
la source
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é.
la source
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
la source
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.
la source
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.
la source