Puis-je me protéger contre l'injection SQL en échappant les guillemets simples et l'entrée utilisateur environnante avec des guillemets simples?

140

Je me rends compte que les requêtes SQL paramétrées sont le moyen optimal de nettoyer les entrées utilisateur lors de la création de requêtes contenant des entrées utilisateur, mais je me demande ce qui ne va pas avec la saisie des entrées utilisateur et en échappant aux guillemets simples et en entourant la chaîne entière avec des guillemets simples. Voici le code:

sSanitizedInput = "'" & Replace(sInput, "'", "''") & "'"

Tout guillemet simple saisi par l'utilisateur est remplacé par des guillemets simples doubles, ce qui élimine la possibilité pour les utilisateurs de terminer la chaîne, de sorte que tout ce qu'ils peuvent taper, comme des points-virgules, des signes de pourcentage, etc., fera partie de la chaîne et pas réellement exécuté dans le cadre de la commande.

Nous utilisons Microsoft SQL Server 2000, pour lequel je pense que le guillemet simple est le seul délimiteur de chaîne et le seul moyen d'échapper au délimiteur de chaîne, il n'y a donc aucun moyen d'exécuter quoi que ce soit que l'utilisateur tape.

Je ne vois aucun moyen de lancer une attaque par injection SQL contre cela, mais je me rends compte que si c'était aussi infaillible qu'il me semble, quelqu'un d'autre y aurait déjà pensé et ce serait une pratique courante.

Quel est le problème avec ce code? Existe-t-il un moyen de faire passer une attaque par injection SQL au-delà de cette technique de nettoyage? Un exemple d'entrée utilisateur exploitant cette technique serait très utile.


METTRE À JOUR:

Je ne connais toujours aucun moyen de lancer efficacement une attaque par injection SQL contre ce code. Quelques personnes ont suggéré qu'une barre oblique inverse échapperait à un guillemet simple et laisserait l'autre pour terminer la chaîne afin que le reste de la chaîne soit exécuté dans le cadre de la commande SQL, et je me rends compte que cette méthode fonctionnerait pour injecter SQL dans une base de données MySQL, mais dans SQL Server 2000, le seul moyen (que j'ai pu trouver) d'échapper à un guillemet simple est d'utiliser un autre guillemet simple; les barres obliques inverses ne le feront pas.

Et à moins qu'il n'y ait un moyen d'arrêter l'échappement du guillemet simple, aucune des autres entrées utilisateur ne sera exécutée car tout sera considéré comme une chaîne contiguë.

Je comprends qu'il existe de meilleures façons de nettoyer les entrées, mais je suis vraiment plus intéressé à savoir pourquoi la méthode que j'ai fournie ci-dessus ne fonctionnera pas. Si quelqu'un connaît un moyen spécifique de monter une attaque par injection SQL contre cette méthode de désinfection, j'aimerais le voir.

Patrick
la source
17
@BryanH Admettre ne pas comprendre comment la sagesse communément admise s'applique à un cas spécifique et demander un exemple sur un tel cas spécifique n'est pas de l'orgueil, c'est de l'humilité. Être ennuyé quand quelqu'un demande un exemple de la raison pour laquelle la sagesse communément acceptée est juste peut par contre paraître arrogant. Raisonner par des exemples spécifiques est souvent un excellent moyen d'enquêter et d'apprendre. La façon dont le PO a abordé ce doute a été très utile pour ma compréhension du sujet, surtout lorsqu'il a expliqué la réponse qu'il a trouvée.
SantiBailors
@patrik Je viens de tomber sur ceci alors que je travaille sur le même morceau de code mais que j'essaye d'échapper à la chaîne et d'imbriquer une requête. L'avez-vous déjà compris?
3therk1ll
1
@ 3therk1ll il vaut mieux ne pas essayer, il vaut mieux utiliser SQL paramétré: blog.codinghorror.com
Patrick
@Patrick, je l'aborde du point de vue des attaquants!
3therk1ll

Réponses:

88

Tout d'abord, c'est juste une mauvaise pratique. La validation des entrées est toujours nécessaire, mais elle est également toujours incertaine.
Pire encore, la validation de la liste noire est toujours problématique, il est préférable de définir explicitement et strictement les valeurs / formats que vous acceptez. Certes, ce n'est pas toujours possible - mais dans une certaine mesure, cela doit toujours être fait.
Quelques travaux de recherche sur le sujet:

Le fait est que toute liste noire que vous faites (et les listes blanches trop permissives) peut être contournée. Le dernier lien vers mon article montre des situations où même les échappements de citation peuvent être contournés.

Même si ces situations ne s'appliquent pas à vous, c'est toujours une mauvaise idée. De plus, à moins que votre application ne soit trivialement petite, vous allez devoir gérer la maintenance, et peut-être un certain niveau de gouvernance: comment vous assurer que c'est bien fait, partout et tout le temps?

La bonne façon de le faire:

  • Validation de la liste blanche: type, longueur, format ou valeurs acceptées
  • Si vous souhaitez mettre sur liste noire, allez-y. L'échappée de devis est bonne, mais dans le contexte des autres atténuations.
  • Utilisez les objets Command et Parameter pour préparer et valider
  • Appelez uniquement les requêtes paramétrées.
  • Mieux encore, utilisez exclusivement les procédures stockées.
  • Évitez d'utiliser SQL dynamique et n'utilisez pas la concaténation de chaînes pour créer des requêtes.
  • Si vous utilisez des SP, vous pouvez également limiter les autorisations dans la base de données à l'exécution des SP nécessaires uniquement, et ne pas accéder directement aux tables.
  • vous pouvez également vérifier facilement que toute la base de code accède uniquement à la base de données via les SP ...
Avide
la source
2
Lorsqu'il est utilisé correctement, le SQL dynamique et la concaténation de chaînes peuvent être utilisés en toute sécurité avec des requêtes paramétrées (c'est-à-dire avec sp_executesqlau lieu de EXEC). Autrement dit, vous pouvez générer dynamiquement votre instruction SQL tant qu'aucun texte concaténé ne provient de l'utilisateur. Cela présente également des avantages en termes de performances; sp_executesqlprend en charge la mise en cache.
Brian
2
@Brian, bien duh :). Mais en réalité, à quelle fréquence voyez-vous des programmeurs faire cela? De plus, le scénario typique où le SQL dynamique est «nécessaire», nécessite l'entrée de l'utilisateur dans le cadre de la requête (supposément). Si vous pouviez faire sp_executesql, vous n'auriez pas (généralement) besoin du SQL dynamique en premier lieu.
AviD
J'ai finalement rencontré une situation qui m'a fait réaliser qu'il est possible d'utiliser unicode pour passer le remplacement de la chaîne. Le texte d'entrée a été tapé dans Word, ce qui a changé l'apostrophe de la version simple en une apostrophe "bouclée" (qui ressemble plus à une virgule), qui n'a pas été affectée par le remplacement de chaîne mais a été traitée comme un délimiteur de chaîne par SQL Serveur. Merci pour la réponse AviD (et tout le monde)!
Patrick
1
@ElRonnoco bien sûr, mais je ne néglige pas cela, puisque je l'ai vu dans la nature plus de fois que vous ne le pensez ...
AviD
1
@AviD J'ai mis à jour le lien vers le PDF SQL Smuggling que vous avez écrit vers la seule version que j'ai pu trouver en ligne ... veuillez nous indiquer s'il y a un autre emplacement pour votre article.
Michael Fredrickson
41

D'accord, cette réponse portera sur la mise à jour de la question:

"Si quelqu'un connaît un moyen spécifique de monter une attaque par injection SQL contre cette méthode de désinfection, j'aimerais bien le voir."

Maintenant, en plus de l'échappement de la barre oblique inverse MySQL - et en tenant compte du fait que nous parlons en fait de MSSQL, il existe en fait 3 façons possibles de continuer à injecter SQL votre code

sSanitizedInput = "'" & Replace (sInput, "'", "''") & "'"

Tenez compte du fait que ceux-ci ne seront pas tous valides à tout moment et dépendent beaucoup du code réel qui l'entoure:

  1. Injection SQL de second ordre - si une requête SQL est reconstruite en fonction des données extraites de la base de données après l'échappement , les données sont concaténées sans échappement et peuvent être indirectement injectées SQL. Voir
  2. Troncature de chaîne - (un peu plus compliqué) - Le scénario est que vous avez deux champs, disons un nom d'utilisateur et un mot de passe, et le SQL concatène les deux. Et les deux champs (ou juste le premier) ont une limite stricte de longueur. Par exemple, le nom d'utilisateur est limité à 20 caractères. Disons que vous avez ce code:
username = left(Replace(sInput, "'", "''"), 20)

Ensuite, ce que vous obtenez - est le nom d'utilisateur, échappé, puis réduit à 20 caractères. Le problème ici - je vais coller ma citation dans le 20e caractère (par exemple après 19 a), et votre citation d'échappement sera coupée (dans le 21e caractère). Puis le SQL

sSQL = "select * from USERS where username = '" + username + "'  and password = '" + password + "'"

combiné avec le nom d'utilisateur malformé susmentionné, le mot de passe se trouvera déjà en dehors des guillemets et contiendra simplement la charge utile directement.
3. Contrebande Unicode - Dans certaines situations, il est possible de passer un caractère Unicode de haut niveau qui ressemble à une citation, mais qui ne l'est pas - jusqu'à ce qu'il arrive à la base de données, où il se trouve soudainement . Puisqu'il ne s'agit pas d'un devis lorsque vous le validez, il se déroulera facilement ... Voir ma réponse précédente pour plus de détails et un lien vers la recherche originale.

Avide
la source
28

En un mot: ne faites jamais de requête qui vous échappe. Vous êtes obligé de vous tromper. Utilisez plutôt des requêtes paramétrées ou, si vous ne pouvez pas le faire pour une raison quelconque, utilisez une bibliothèque existante qui le fait pour vous. Il n'y a aucune raison de le faire vous-même.

Nick Johnson
la source
2
Que faire si vous devez gérer quelque chose comme "Google Fusion tables" où, afaik, il n'y a pas de bibliothèque d'abstraction disponible qui prend en charge son dialecte? Que suggérerais-tu?
systempuntoout
20

Je me rends compte que c'est longtemps après que la question a été posée, mais ..

Une façon de lancer une attaque sur la procédure 'quote the argument' est d'utiliser la troncature de chaîne. Selon MSDN, dans SQL Server 2000 SP4 (et SQL Server 2005 SP1), une chaîne trop longue sera silencieusement tronquée.

Lorsque vous citez une chaîne, la taille de la chaîne augmente. Chaque apostrophe est répétée. Cela peut ensuite être utilisé pour pousser des parties du SQL hors du tampon. Ainsi, vous pourriez effectivement supprimer des parties d'une clause where.

Cela serait probablement surtout utile dans un scénario de page «administrateur utilisateur» où vous pourriez abuser de l'instruction «update» pour ne pas faire toutes les vérifications qu'elle était censée faire.

Donc, si vous décidez de citer tous les arguments, assurez-vous de savoir ce qui se passe avec les tailles de chaîne et veillez à ce que vous ne rencontriez pas de troncature.

Je recommanderais d'aller avec des paramètres. Toujours. J'aimerais juste pouvoir appliquer cela dans la base de données. Et en tant qu'effet secondaire, vous êtes plus susceptible d'obtenir de meilleurs hits de cache, car plus d'instructions se ressemblent. (C'était certainement vrai sur Oracle 8)

Jørn Jensen
la source
1
Après avoir posté, j'ai décidé que l'article d'AviD couvrait cela, et plus en détail. J'espère que mon message sera toujours utile à quelqu'un.
Jørn Jensen
10

J'ai utilisé cette technique pour gérer la fonctionnalité de «recherche avancée», où la création d'une requête à partir de zéro était la seule réponse viable. (Exemple: permettre à l'utilisateur de rechercher des produits en fonction d'un ensemble illimité de contraintes sur les attributs du produit, en affichant les colonnes et leurs valeurs autorisées en tant que contrôles GUI pour réduire le seuil d'apprentissage pour les utilisateurs.)

En soi, il est sûr AFAIK. Cependant, comme un autre répondant l'a souligné, vous devrez peut-être également gérer l'échappement de retour arrière (mais pas lors du passage de la requête à SQL Server à l'aide d'ADO ou ADO.NET, au moins - ne peut pas garantir toutes les bases de données ou technologies).

Le hic, c'est que vous devez vraiment être certain quelles chaînes contiennent une entrée utilisateur (toujours potentiellement malveillante) et quelles chaînes sont des requêtes SQL valides. L'un des pièges est si vous utilisez des valeurs de la base de données - ces valeurs ont-elles été fournies à l'origine par l'utilisateur? Si tel est le cas, ils doivent également être échappés. Ma réponse est d'essayer de nettoyer le plus tard possible (mais pas plus tard!), Lors de la construction de la requête SQL.

Cependant, dans la plupart des cas, la liaison de paramètres est la voie à suivre - c'est simplement plus simple.

Pontus Gagge
la source
2
Vous pouvez toujours utiliser la substitution de paramètres même si vous créez vos propres requêtes.
Nick Johnson du
1
Vous devez créer la chaîne d'instruction SQL à partir de zéro, mais toujours utiliser la substitution de paramètres.
JeeBee le
Non, ne créez JAMAIS vos instructions SQL à partir de zéro.
AviD le
8

L'assainissement des entrées n'est pas quelque chose que vous voulez faire à moitié. Utilisez tout votre cul. Utilisez des expressions régulières sur les champs de texte. TryCast vos numériques au type numérique approprié et signalez une erreur de validation si cela ne fonctionne pas. Il est très facile de rechercher des modèles d'attaque dans votre entrée, tels que '-. Supposons que toutes les entrées de l'utilisateur sont hostiles.

tom.dietrich
la source
4
Et quand vous manquez que UN cas sur ONE entrée, vous êtes pwnd.
BryanH
4
«Certaines personnes, lorsqu'elles sont confrontées à un problème, pensent:« Je sais, je vais utiliser des expressions régulières. »Maintenant, elles ont deux problèmes.
MickeyfAgain_BeforeExitOfSO
1
@mickeyf Je sais que c'est un sentiment commun, mais honnêtement, les expressions régulières sont plutôt géniales une fois que vous les grepez.
tom.dietrich
@ tom.dietrich Cela dépend toujours de la situation réelle. Par ex. La syntaxe regexpr n'est pas standard donc en général je déconseille d'utiliser regexpr dans des contextes où différents systèmes sont intégrés pour fonctionner ensemble. C'est parce que différents moteurs regexpr évaluent les regexprs différemment, et plus important encore, ce fait difficile est généralement minimisé ou ignoré, ce qui peut amener les développeurs à ne pas se soucier de ces incompatibilités jusqu'à ce qu'ils soient mordus. Il existe de nombreuses incompatibilités de ce type; voir p.ex. regular-expressions.info/shorthand.html (recherchez flavorsdans cette page).
SantiBailors
6

C'est une mauvaise idée de toute façon comme vous semblez le savoir.

Qu'en est-il de quelque chose comme échapper à la citation dans une chaîne comme celle-ci: \ '

Votre remplacement entraînerait: \ ''

Si la barre oblique inverse échappe au premier guillemet, le deuxième a terminé la chaîne.

WW.
la source
3
Merci pour la réponse! Je sais que cette attaque fonctionnerait pour une base de données mySQL, mais je suis presque sûr que MS SQL Server n'acceptera pas de barre oblique inverse comme caractère d'échappement (je l'ai essayé). Plusieurs recherches Google n'ont révélé aucun autre caractère d'échappement, ce qui m'a vraiment fait me demander pourquoi cela ne fonctionnerait pas.
Patrick le
6

Réponse simple: cela fonctionnera parfois, mais pas tout le temps. Vous voulez utiliser la validation de la liste blanche sur tout ce que vous faites, mais je me rends compte que ce n'est pas toujours possible, vous êtes donc obligé de choisir la meilleure liste noire. De même, vous voulez utiliser des procs stockés paramétrés dans tout , mais encore une fois, ce n'est pas toujours possible, vous êtes donc obligé d'utiliser sp_execute avec des paramètres.

Il existe des moyens de contourner toute liste noire utilisable que vous pouvez créer (et certaines listes blanches aussi).

Un article décent est ici: http://www.owasp.org/index.php/Top_10_2007-A2

Si vous avez besoin de le faire comme solution rapide pour vous donner le temps de mettre en place un vrai, faites-le. Mais ne pensez pas que vous êtes en sécurité.

Caractère non valide
la source
6

Il y a deux façons de le faire, sans exception, pour être à l'abri des injections SQL; instructions préparées ou procédures stockées pré-paramétrées.

olle
la source
4

Si vous disposez de requêtes paramétrées, vous devez les utiliser à tout moment. Tout ce qu'il faut, c'est qu'une seule requête glisse sur le net et votre base de données est en danger.

Kev
la source
4

Oui, cela devrait fonctionner jusqu'à ce que quelqu'un exécute SET QUOTED_IDENTIFIER OFF et utilise un guillemet double sur vous.

Edit: Ce n'est pas aussi simple que de ne pas permettre à l'utilisateur malveillant de désactiver les identifiants entre guillemets:

Le pilote ODBC SQL Server Native Client et le fournisseur OLE DB SQL Server Native Client pour SQL Server définissent automatiquement QUOTED_IDENTIFIER sur ON lors de la connexion. Cela peut être configuré dans les sources de données ODBC, dans les attributs de connexion ODBC ou dans les propriétés de connexion OLE DB. La valeur par défaut de SET QUOTED_IDENTIFIER est OFF pour les connexions depuis les applications DB-Library.

Lorsqu'une procédure stockée est créée, les paramètres SET QUOTED_IDENTIFIER et SET ANSI_NULLS sont capturés et utilisés pour les appels ultérieurs de cette procédure stockée .

SET QUOTED_IDENTIFIER correspond également au paramètre QUOTED_IDENTIFER de ALTER DATABASE.

SET QUOTED_IDENTIFIER est défini au moment de l'analyse . La définition au moment de l'analyse signifie que si l'instruction SET est présente dans le lot ou la procédure stockée, elle prend effet, que l'exécution du code atteigne ou non ce point; et l'instruction SET prend effet avant l'exécution de toute instruction.

QUOTED_IDENTIFIER peut être désactivé de nombreuses façons sans que vous le sachiez nécessairement. Certes, ce n'est pas l'exploit que vous recherchez, mais c'est une assez grande surface d'attaque. Bien sûr, si vous avez également échappé des guillemets doubles, nous revenons là où nous avons commencé. ;)

Mark Brackett
la source
1
Cela pourrait fonctionner, mais encore une fois, comment pourraient-ils faire exécuter ce code lorsque toutes les entrées de l'utilisateur sont entourées de guillemets simples? Une ou plusieurs lignes de code spécifiques permettant d'injecter du SQL dans le code ci-dessus seraient très utiles. Merci!
Patrick
4

Votre défense échouerait si:

  • la requête attend un nombre plutôt qu'une chaîne
  • il y avait tout autre moyen de représenter un guillemet simple, y compris:
    • une séquence d'échappement telle que \ 039
    • un caractère unicode

(dans ce dernier cas, il faudrait que ce soit quelque chose qui n'a été développé qu'après avoir effectué votre remplacement)

UN J.
la source
4

Patrick, est-ce que vous ajoutez des guillemets simples autour de TOUTES les entrées, même numériques? Si vous avez une entrée numérique, mais ne mettez pas les guillemets simples autour d'elle, vous avez une exposition.

Rob Kraft
la source
1

Quel vilain code serait tout ce nettoyage des entrées utilisateur! Puis le StringBuilder maladroit pour l'instruction SQL. La méthode de l'instruction préparée donne un code beaucoup plus propre et les avantages de SQL Injection sont un ajout vraiment intéressant.

Aussi pourquoi réinventer la roue?

JeeBee
la source
1

Plutôt que de changer un guillemet simple en (à quoi ressemble) deux guillemets simples, pourquoi ne pas simplement le changer en une apostrophe, une citation ou le supprimer entièrement?

Quoi qu'il en soit, c'est un peu un kludge ... surtout quand vous avez légitimement des choses (comme des noms) qui peuvent utiliser des guillemets simples ...

REMARQUE: votre méthode suppose également que tout le monde travaillant sur votre application se souvient toujours de nettoyer les entrées avant qu'elles n'atteignent la base de données, ce qui n'est probablement pas réaliste la plupart du temps.

Kevin Fairchild
la source
Voté à la baisse car la réponse ne répond pas à la question. La question concerne l'échappement des chaînes en SQL. Lorsque vous échappez à une chaîne arbitraire (comme le questionneur essaie de le faire, afin de traiter des données non désinfectées), vous ne pouvez pas simplement remplacer des caractères problématiques par d'autres arbitraires; qui corrompt les données. (De plus, un guillemet simple EST une apostrophe (au moins en ASCII).)
andrewf
-1

Bien que vous puissiez trouver une solution qui fonctionne pour les chaînes, pour les prédicats numériques, vous devez également vous assurer qu'ils ne transmettent que des nombres (une vérification simple est peut-il être analysé comme int / double / décimal?).

C'est beaucoup de travail supplémentaire.

Joseph Daigle
la source
-2

Cela pourrait fonctionner, mais cela me semble un peu hokey. Je recommanderais de vérifier que chaque chaîne est valide en la testant par rapport à une expression régulière à la place.

Rob
la source
-3

Oui, vous pouvez, si ...

Après avoir étudié le sujet, je pense que les entrées nettoyées comme vous l'avez suggéré sont sûres, mais uniquement selon ces règles:

  1. vous ne permettez jamais aux valeurs de chaîne provenant des utilisateurs de devenir autre chose que des littéraux de chaîne (c'est-à-dire éviter de donner l'option de configuration: "Entrez des noms / expressions de colonne SQL supplémentaires ici:"). Types de valeur autres que les chaînes (nombres, dates, ...): convertissez-les dans leurs types de données natifs et fournissez une routine pour le littéral SQL de chaque type de données.

    • Les instructions SQL sont problématiques à valider
  2. vous utilisez soit nvarchar/ ncharcolonnes (et préfixez les littéraux de chaîne avec N) OU limitez les valeurs entrant dans varchar/ charcolonnes aux caractères ASCII uniquement (par exemple, lancez une exception lors de la création d'une instruction SQL)

    • de cette façon, vous éviterez la conversion automatique d'apostrophe de CHAR (700) à CHAR (39) (et peut-être d'autres hacks Unicode similaires)
  3. vous validez toujours la longueur de la valeur pour qu'elle corresponde à la longueur réelle de la colonne (jetez une exception si elle est plus longue)

    • il y avait un défaut connu dans SQL Server permettant de contourner l'erreur SQL lancée lors de la troncature (conduisant à une troncature silencieuse)
  4. vous vous assurez que SET QUOTED_IDENTIFIERc'est toujoursON

    • attention, il est pris en compte au moment de l'analyse, c'est-à-dire même dans les sections de code inaccessibles

En respectant ces 4 points, vous devez être en sécurité. Si vous violez l'un d'entre eux, un moyen d'injection SQL s'ouvre.

miroxlav
la source
1
C'est comme si vous n'aviez pas lu toutes les autres réponses à cette question vieille de 8 ans , car un certain nombre de ces réponses indiquent que sa méthode ne parvient pas à arrêter l'injection si l'attaquant utilise simplement des caractères Unicode.
Hogan
@Hogan - Je l'ai fait, mais je pense qu'il y a une valeur supplémentaire dans ma question. J'ai beaucoup d'expérience et de tests derrière ce que j'ai écrit. Je sais que l'utilisation des paramètres de requête est meilleure, mais je comprends également parfaitement la situation où quelqu'un doit l'éviter pour diverses raisons (par exemple, les demandes de l'employeur de conserver l'ancienne méthode). Dans ce cas, je pense que ma réponse est très complète et a plus de valeur que les réponses disant «ne faites pas ça», car elle montre le chemin. Montrez-moi d'autres réponses ici qui montrent la même manière et j'envisagerai de supprimer la mienne.
miroxlav
Ok, quand (pas si) votre système est compromis, veuillez revenir et supprimer cette réponse .... ou vous pouvez utiliser une requête paramétrée.
Hogan
@Hogan - Je n'ai aucun problème pour le faire :) Mais actuellement, je prétends qu'il n'y a aucun moyen connu de contourner cela si vous gardez les 4 règles que j'ai publiées. Si vous pensez vraiment qu'il existe un moyen de contourner le problème, indiquez simplement où.
miroxlav
Mauvais conseil hombre. toute interpolation peut être vaincue.
Shayne