Éviter L'injection SQL sans paramètres

nous avons une autre discussion ici au travail sur l'utilisation des requêtes SQL paramétrées dans notre code. Nous avons deux côtés dans la discussion: moi et quelques autres qui disent que nous devrions toujours utiliser des paramètres pour nous protéger contre les injections sql et les autres gars qui ne pensent pas que ce soit nécessaire. Au lieu de cela, ils veulent remplacer les apostrophes simples par deux apostrophes dans toutes les chaînes pour éviter les injections de sql. Nos bases de données fonctionnent toutes avec Sql Server 2005 ou 2008 et notre base de code fonctionne avec .NET. framework 2.0.

Permettez-moi de vous donner un exemple simple en C#:

je veux que nous utilisions ceci:

string sql = "SELECT * FROM Users WHERE Name=@name";
SqlCommand getUser = new SqlCommand(sql, connection);
getUser.Parameters.AddWithValue("@name", userName);
//... blabla - do something here, this is safe

Tandis que l'autre, les gars veulent faire ceci:

string sql = "SELECT * FROM Users WHERE Name=" + SafeDBString(name);
SqlCommand getUser = new SqlCommand(sql, connection);
//... blabla - are we safe now?

où la fonction SafeDBString est définie comme suit:

string SafeDBString(string inputValue) 
{
    return "'" + inputValue.Replace("'", "''") + "'";
}

maintenant, aussi longtemps que nous utilisons SafeDBString sur toutes les valeurs de chaîne de caractères dans nos requêtes, nous devrions être en sécurité. Droit?

il y a deux raisons d'utiliser la fonction SafeDBString. Premièrement, c'est la façon dont cela a été fait depuis l'âge de Pierre, et deuxièmement, il est plus facile de déboguer les déclarations sql puisque vous voyez la requête excact qui est exécutée sur la base de données.

alors. Ma question Est de savoir s'il est vraiment suffisant d'utiliser la fonction SafeDBString pour éviter les attaques par injection sql. J'ai essayé de trouver des exemples de code qui casse cette mesure de sécurité, mais je ne trouve pas d'exemples.

y a-t-il quelqu'un dehors qui puisse casser ça? Comment le feriez-vous?

EDIT: Pour résumer les réponses jusqu'à présent:

  • personne n'a encore trouvé le moyen de contourner le SafeDBString sur Sql Server 2005 ou 2008. C'est bon, je pense?
  • plusieurs réponses ont souligné que vous obtenez un gain de performance en utilisant des requêtes paramétrées. La raison est que les plans de requête peuvent être réutiliser.
  • nous sommes également d'accord que l'utilisation de requêtes paramétrées donne un code plus lisible qui est plus facile à maintenir
  • en outre, il est plus facile de toujours utiliser des paramètres que d'utiliser diverses versions de SafeDBString, chaîne de caractères à nombre conversions et chaîne de caractères à date conversions.
  • en utilisant des paramètres vous obtenez la conversion de type automatique, quelque chose qui est particulièrement utile lorsque nous travaillons avec des dates ou des nombres décimaux.
  • et enfin: N'essayez pas de faire vous-même la sécurité comme JulianR l'a écrit. Les vendeurs de bases de données consacrent beaucoup de temps et d'argent à la sécurité. Nous ne pouvons pas faire mieux et nous n'avons aucune raison d'essayer de faire leur travail.

ainsi, bien que personne n'ait pu briser la sécurité simple de la fonction SafeDBString, j'ai obtenu beaucoup d'autres bons arguments. Merci!

106
demandé sur Community 2009-05-26 16:38:20

21 réponses

je pense que la bonne réponse est:

N'essayez pas de faire de la sécurité de vous-même . Utilisez n'importe quel confiance, bibliothèque standard de l'industrie il est disponible pour ce que vous essayez de faire, plutôt que essayer pour le faire vous-même. Quelles que soient vos suppositions sur la sécurité, elles pourraient être inexactes. Aussi sûr que votre propre approche peut sembler (et il semble Tremblant au mieux), Il ya un risque que vous négligez quelque chose et vous voulez vraiment prendre ce risque quand il s'agit de la sécurité?

utiliser les paramètres.

81
répondu JulianR 2009-05-26 13:31:01

et puis quelqu'un va et utilise "au lieu de". Les paramètres sont, IMO, la seule façon sûre d'y aller.

il évite aussi beaucoup de problèmes i18n avec les dates / nombres; quelle date est 01/02/03? Combien est 123,456? Vos serveurs (app-server et db-server) sont-ils d'accord entre eux?

si le facteur de risque n'est pas convaincant pour eux, qu'en est-il de la performance? Le RDBMS peut réutiliser le plan de requête si vous utilisez des paramètres, aidant la performance. Il ne peut pas le faire avec juste la chaîne.

71
répondu Marc Gravell 2009-05-26 12:45:21

L'argument est un no-win. Si vous parvenez à trouver une vulnérabilité, vos collègues changeront simplement la fonction SafeDBString pour en tenir compte et vous demanderont ensuite de prouver qu'elle est à nouveau dangereuse.

étant donné que les requêtes paramétrées sont une meilleure pratique de programmation incontestée, la charge de la preuve devrait être sur eux pour indiquer pourquoi ils n'utilisent pas une méthode qui est à la fois plus sûr et plus performant.

si l'émission est réécrite tout le code d'héritage, le compromis facile serait d'utiliser des requêtes paramétrées dans tout le nouveau code, et de remanier l'ancien code pour les utiliser quand on travaille sur ce code.

à mon avis, la véritable question Est la fierté et l'entêtement, et il n'y a pas beaucoup plus que vous pouvez faire à ce sujet.

27
répondu Matthew Christensen 2009-05-27 15:04:53

tout d'abord, votre échantillon pour la version "remplacer" est erroné. Vous devez mettre des apostrophes autour du texte:

string sql = "SELECT * FROM Users WHERE Name='" + SafeDBString(name) & "'";
SqlCommand getUser = new SqlCommand(sql, connection);

donc c'est une autre chose que les paramètres font pour vous: vous n'avez pas à vous soucier de savoir si oui ou non une valeur doit être incluse dans les guillemets. Bien sûr, vous pouvez construire cela dans la fonction, mais alors vous devez ajouter beaucoup de complexité à la fonction: Comment savoir la différence entre 'NULL' comme null et 'NULL' comme une chaîne de caractères, ou entre un nombre et une chaîne qui contient beaucoup de chiffres. C'est juste une autre source pour les insectes.

une autre chose est la performance: les plans de requête paramétrés sont souvent mieux mis en cache que les plans concaténés, ce qui permet peut-être d'économiser au serveur une étape lors de l'exécution de la requête.

de plus, échapper à des guillemets n'est pas suffisant. de nombreux produits DB permettent des méthodes alternatives pour échapper aux caractères qu'un attaquant pourrait prendre avantage de. Dans MySQL, par exemple, vous pouvez également échapper à une simple citation avec un antislash. Et ainsi la valeur "name" suivante exploserait MySQL avec juste la fonction SafeDBString() , parce que quand vous doublez la citation simple la première est encore échappée par le backslash, laissant la 2ème "active":

x\' OR 1=1;--


aussi, JulianR soulève un bon point ci-dessous: JAMAIS essayer de faire de la sécurité du travail vous-même. Il est si facile d'obtenir des erreurs de programmation de sécurité de manière subtile que semblent de travailler, même avec des tests approfondis. Puis le temps passe et un an plus tard, vous découvrez que votre système a été fissuré il y a six mois et vous ne le saviez même pas jusqu'à ce moment-là.

toujours compter autant que possible sur les bibliothèques de sécurité fournies pour votre plate-forme. Ils seront écrit par des gens qui font du code de sécurité pour vivre, bien mieux testé que ce que vous pouvez gérer, et servi par le fournisseur si une vulnérabilité est trouvée.

19
répondu Joel Coehoorn 2009-05-26 13:37:07

donc je dirais:

1) Pourquoi essayez-vous de ré-implémenter quelque chose qui est intégré? il est là, facilement disponible, facile à utiliser et déjà débogué à l'échelle mondiale. Si de futurs bugs y sont trouvés, ils seront corrigés et disponibles pour tout le monde très rapidement sans que vous ayez à faire quoi que ce soit.

2) Quels sont les processus en place pour garantie que vous jamais manquer un appel à SafeDBString? Manquant un seul endroit pourrait soulever toute une série de questions. Combien allez-vous regarder ces choses, et considérer combien gaspillé cet effort est quand la réponse correcte acceptée est si facile à atteindre.

3) Comment êtes-vous certain que vous avez couvert chaque vecteur d'attaque que Microsoft(l'auteur de la base de données et de la bibliothèque d'accès) connaît dans votre implémentation SafeDBString ...

4) est-il facile de lire la structure du sql? Le exemple utilise + concaténation, les paramètres sont très semblables à la chaîne.Le Format, qui est plus lisible.

en outre, il y a 2 façons de travailler ce qui a été réellement exécuté - lancez votre propre fonction LogCommand, une fonction simple avec pas de préoccupations de sécurité , ou même regarder une trace sql pour déterminer ce que la base de données pense est vraiment en cours.

notre fonction LogCommand est simplement:

    string LogCommand(SqlCommand cmd)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendLine(cmd.CommandText);
        foreach (SqlParameter param in cmd.Parameters)
        {
            sb.Append(param.ToString());
            sb.Append(" = \"");
            sb.Append(param.Value.ToString());
            sb.AppendLine("\"");
        }
        return sb.ToString();
    }

bien ou mal, il nous donne l'information dont nous avons besoin sans problèmes de sécurité.

10
répondu Jim T 2009-05-26 15:00:43

avec des requêtes paramétrées vous obtenez plus qu'une protection contre l'injection sql. Vous obtenez également un meilleur potentiel de cache plan d'exécution. Si vous utilisez le profileur de requête sql server, vous pouvez toujours voir le 'sql exact qui est exécuté sur la base de données' de sorte que vous ne perdez pas grand chose en termes de débogage de vos instructions sql non plus.

7
répondu Steve Willcock 2009-05-26 12:47:49

j'ai utilisé les deux approches pour éviter les attaques par injection SQL et je préfère définitivement les requêtes paramétrées. Quand j'ai utilisé des requêtes concaténées, j'ai utilisé une fonction de bibliothèque pour échapper aux variables (comme mysql_real_escape_string) et je ne serais pas sûr d'avoir tout couvert dans une implémentation propriétaire (comme il semble que vous le soyez aussi).

5
répondu RedBlueThing 2009-05-26 12:44:21

vous n'êtes pas en mesure de faire facilement n'importe quel type de vérification de l'entrée de l'utilisateur sans utiliser les paramètres.

si vous utilisez les classes SQLCommand et SQLParameter pour faire vos appels DB, vous pouvez toujours voir la requête SQL qui est exécutée. Regardez la propriété CommandText de SQLCommand.

je suis toujours un petit suspect de l'approche roll-your-own pour empêcher L'injection SQL lorsque les requêtes paramétrées sont si faciles à utiliser. Deuxième, juste parce que "c'est toujours été fait de cette façon" ne veut pas dire que c'est la bonne façon de le faire.

4
répondu Tim Scarborough 2009-05-26 12:58:58

ce n'est sûr que si vous êtes sûr que vous allez passer dans une corde.

et si vous ne passiez pas dans une chaîne à un moment donné? Et si tu passes juste un numéro?

http://www.mywebsite.com/profile/?id=7;DROP DATABASE DB

deviendrait finalement:

SELECT * FROM DB WHERE Id = 7;DROP DATABASE DB
3
répondu joshcomley 2009-05-26 13:17:18

j'utilisais des procédures ou des fonctions stockées pour tout, donc la question ne se poserait pas.

où je dois mettre SQL dans le code, j'utilise des paramètres, ce qui est la seule chose qui a du sens. Rappelez aux dissidents qu'il y a des hackers plus intelligents qu'eux, et avec une meilleure incitation à briser le code qui essaie de les surpasser. En utilisant des paramètres, c'est tout simplement impossible, et ce n'est pas comme si c'était difficile.

2
répondu John Saunders 2009-05-26 12:43:45

sont d'accord sur les questions de sécurité.

Une autre raison d'utiliser les paramètres est pour l'efficacité.

Les bases de données

compileront toujours votre requête et la mettront en cache, puis réutiliseront la requête mise en cache (ce qui est évidemment plus rapide pour les requêtes suivantes). Si vous utilisez des paramètres, même si vous utilisez des paramètres différents, la base de données réutilisera votre requête mise en cache car elle correspond à la chaîne SQL avant de lier les paramètres.

si cependant, si vous ne liez pas les paramètres, la chaîne de caractères SQL change à chaque requête (qui a des paramètres différents) et elle ne correspondra jamais à ce qu'il y a dans votre cache.

2
répondu Darren Greaves 2009-05-26 13:26:53

vu le peu de temps que j'ai eu à enquêter sur les problèmes D'injection SQL, je peux voir que rendre une valeur "sûre" signifie aussi que vous fermez la porte à des situations où vous pourriez réellement vouloir des apostrophes dans vos données - qu'en est-il du nom de quelqu'un, par exemple O'Reilly.

Qui laisse les paramètres et les procédures stockées.

et oui, vous devriez toujours essayer de mettre en œuvre le code de la meilleure façon que vous savez maintenant - pas seulement comment il a toujours été fait.

1
répondu quamrana 2009-05-26 13:09:21

voici quelques articles qui pourraient vous aider à convaincre vos collègues.

http://www.sommarskog.se/dynamic_sql.html

http://unixwiz.net/techtips/sql-injection.html

personnellement, je préfère ne jamais permettre à aucun code dynamique de toucher ma base de données, exigeant tout contact à travers sps (et pas un qui utilise le SQl dynamique). Cela ne signifie rien excpt ce que j'ai donné aux utilisateurs la permission de faire peut être fait et que les utilisateurs internes (à l'exception de très peu avec l'accès de production à des fins administratives) ne peuvent pas accéder directement à mes tables et créer des ravages, voler des données ou commettre des fraudes. Si vous utilisez une application financière, c'est la façon la plus sûre de procéder.

1
répondu HLGEM 2009-05-26 13:20:29

il peut être cassé, cependant le moyen dépend des versions exactes / correctifs etc.

L'un d'entre eux qui a déjà été soulevé est le bug de débordement/troncature qui peut être exploité.

un autre moyen futur serait de trouver des bogues similaires à d'autres bases de données - par exemple la pile MySQL/PHP a souffert d'un problème d'évasion parce que certaines séquences UTF8 pourraient être utilisées pour manipuler la fonction replace - la fonction replace serait piégée dans introduisant les caractères d'injection.

à la fin de la journée, le mécanisme de sécurité de remplacement repose sur la fonctionnalité prévue mais pas sur la fonctionnalité prévue . Puisque la fonctionnalité n'était pas le but prévu du code, il y a une forte probabilité que certains quirk découvert va briser la fonctionnalité attendue.

si vous avez beaucoup de code d'héritage, la méthode de remplacement pourrait être utilisé comme un bouche-trou pour éviter les longues réécritures et les tests. Si vous écrivez un nouveau code, il n'y a pas d'excuse.

1
répondu David 2009-05-26 14:58:07

Pour les raisons déjà indiquées, les paramètres sont une très bonne idée. Mais nous détestons les utiliser parce que créer le param et assigner son nom à une variable pour une utilisation ultérieure dans une requête est une triple épave de tête indirecte.

la classe suivante enveloppe le stringbuilder que vous utiliserez couramment pour les requêtes SQL de construction. Il vous permet d'écrire des requêtes paramaterisées sans jamais avoir à créer un paramètre , de sorte que vous pouvez vous concentrer sur le SQL. Votre code ressemblera à ça...

var bldr = new SqlBuilder( myCommand );
bldr.Append("SELECT * FROM CUSTOMERS WHERE ID = ").Value(myId, SqlDbType.Int);
//or
bldr.Append("SELECT * FROM CUSTOMERS WHERE NAME LIKE ").FuzzyValue(myName, SqlDbType.NVarChar);
myCommand.CommandText = bldr.ToString();

la lisibilité du Code, j'espère que vous êtes d'accord, est grandement améliorée, et la sortie est une requête paramétrée appropriée.

la classe ressemble à ceci...

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;

namespace myNamespace
{
    /// <summary>
    /// Pour le confort et le bonheur, cette classe remplace StringBuilder pour la construction
    /// des requêtes SQL, avec l'avantage qu'elle gère la création des paramètres via la méthode
    /// Value().
    /// </summary>
    public class SqlBuilder
    {
        private StringBuilder _rq;
        private SqlCommand _cmd;
        private int _seq;
        public SqlBuilder(SqlCommand cmd)
        {
            _rq = new StringBuilder();
            _cmd = cmd;
            _seq = 0;
        }
        //Les autres surcharges de StringBuilder peuvent être implémenté ici de la même façon, au besoin.
        public SqlBuilder Append(String str)
        {
            _rq.Append(str);
            return this;
        }
        /// <summary>
        /// Ajoute une valeur runtime à la requête, via un paramètre.
        /// </summary>
        /// <param name="value">La valeur à renseigner dans la requête</param>
        /// <param name="type">Le DBType à utiliser pour la création du paramètre. Se référer au type de la colonne cible.</param>
        public SqlBuilder Value(Object value, SqlDbType type)
        {
            //get param name
            string paramName = "@SqlBuilderParam" + _seq++;
            //append condition to query
            _rq.Append(paramName);
            _cmd.Parameters.Add(paramName, type).Value = value;
            return this;
        }
        public SqlBuilder FuzzyValue(Object value, SqlDbType type)
        {
            //get param name
            string paramName = "@SqlBuilderParam" + _seq++;
            //append condition to query
            _rq.Append("'%' + " + paramName + " + '%'");
            _cmd.Parameters.Add(paramName, type).Value = value;
            return this; 
        }

        public override string ToString()
        {
            return _rq.ToString();
        }
    }
}
1
répondu bbsimonbb 2014-10-23 09:00:00

Je n'ai pas vu d'autres answers aborder ce côté de la 'Pourquoi le faire vous-même est mauvais', mais considérer un attaque de troncature SQL .

il y a aussi la fonction QUOTENAME T-SQL qui peut être utile si vous ne pouvez pas les convaincre d'utiliser des params. Il attrape beaucoup (toutes?) de l'échappé qoute préoccupations.

1
répondu JasonRShaver 2015-08-12 11:00:28

2 ans plus tard , j'ai récidivé... N'importe qui qui trouve des paramètres une douleur est bienvenu pour essayer mon extension VS, QueryFirst . Vous modifiez votre demande dans un vrai .fichier sql (Validation, Intellisense). Pour ajouter un paramètre, il suffit de le taper directement dans votre SQL, en commençant par le '@'. Lorsque vous sauvegardez le fichier, QueryFirst générera des classes wrapper pour vous permettre d'exécuter la requête et d'accéder aux résultats. Qu'il relève la base de données Tapez votre paramètre et associez-le à un type .net, que vous trouverez en entrée dans les méthodes Execute() générées. ne pouvait pas être plus simple . Le faire de la bonne façon est radicalement plus rapide et plus facile que de toute autre manière, et créer une vulnérabilité d'injection sql devient impossible, ou du moins perverse difficile. Il y a d'autres avantages de tueur, comme pouvoir supprimer des colonnes dans votre DB et voir immédiatement des erreurs de compilation dans votre application.

avertissement juridique: J'ai écrit QueryFirst

1
répondu bbsimonbb 2017-05-23 12:26:01

voici quelques raisons d'utiliser des requêtes paramétrées:

  1. la Sécurité de La base de données de la couche d'accès sait comment faire pour supprimer ou d'échapper à des éléments qui ne sont pas autorisés dans les données.
  2. la Séparation des préoccupations - Mon code n'est pas responsable pour transformer les données dans un format que la base de données aime.
  3. pas de redondance - Je n'ai pas besoin d'inclure un assemblage ou une classe dans chaque projet qui fait cette base de données formatage / échappement; il est intégré à la bibliothèque de classe.
0
répondu Powerlord 2009-05-26 13:41:43

il y avait peu de vulnérabilité(Je ne me souviens pas de quelle base de données il s'agissait) qui est liée au débordement de tampon de la déclaration SQL.

ce que je veux dire est, SQL-Injection est plus que" escape The quote", et vous n'avez aucune idée de ce qui va suivre.

0
répondu Dennis C 2009-05-26 14:38:50

une autre considération importante est de garder la trace des données échappées ou non. Il y a des tonnes et des tonnes d'applications, Web et autres, qui ne semblent pas correctement garder la trace de quand les données sont brutes-Unicode, &-encoded, HTML formaté, etc. Il est évident qu'il deviendra difficile de savoir quelles chaînes sont codées '' et lesquelles ne le sont pas.

c'est aussi un problème quand vous finissez par changer le type d'une variable - peut-être il utilisé pour être un nombre entier, mais maintenant, c'est une chaîne. Maintenant tu as un problème.

0
répondu Paul Fisher 2009-05-29 02:45:04

toujours utiliser des requêtes paramétrées si possible. Parfois même une entrée simple sans l'utilisation de caractères bizarres peut déjà créer une injection SQL si elle n'est pas identifiée comme entrée pour un champ dans la base de données.

il suffit donc de laisser la base de données faire son travail d'identification de l'entrée elle-même, sans mentionner qu'elle sauve également allot of trouble quand vous devez réellement insérer des caractères bizarres qui autrement seraient échappés ou changés. Il peut même en sauver précieux de l'exécution dans la fin pour ne pas avoir à calculer l'entrée.

0
répondu VulstaR 2014-10-23 09:27:53