web-dev-qa-db-fra.com

É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 se protéger contre les injections sql et les autres gars qui ne pensent pas que c'est nécessaire. Au lieu de cela, ils veulent remplacer les apostrophes simples par deux apostrophes dans toutes les chaînes pour éviter les injections sql. Nos bases de données exécutent toutes Sql Server 2005 ou 2008 et notre base de code fonctionne sur .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

Alors que les autres gars veulent faire ça:

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, tant que nous utilisons SafeDBString sur toutes les valeurs de chaîne dans nos requêtes, nous devons être en sécurité. Droite?

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 instructions sql car vous voyez la requête exacte qui est exécutée sur la base de données.

Donc alors. Ma question est de savoir s'il suffit vraiment d'utiliser la fonction SafeDBString pour éviter les attaques par injection SQL. J'ai essayé de trouver des exemples de code qui enfreignent cette mesure de sécurité, mais je n'en trouve aucun exemple.

Y a-t-il quelqu'un là-bas qui peut briser cela? Comment feriez-vous?

EDIT: Pour résumer les réponses à ce jour:

  • Personne n'a encore trouvé un moyen de contourner SafeDBString sur Sql Server 2005 ou 2008. C'est bien, je pense?
  • Plusieurs réponses ont souligné que vous obtenez un gain de performances lors de l'utilisation de requêtes paramétrées. La raison en est que les plans de requête peuvent être réutilisés.
  • Nous convenons également que l'utilisation de requêtes paramétrées donne un code plus lisible et plus facile à maintenir
  • De plus, il est plus facile de toujours utiliser des paramètres que d'utiliser différentes versions de SafeDBString, des conversions de chaîne en nombre et des conversions de chaîne en date.
  • En utilisant des paramètres, vous obtenez une conversion de type automatique, ce 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 l'a écrit JulianR. Les fournisseurs de bases de données consacrent beaucoup de temps et d'argent à la sécurité. Il n'y a aucun moyen de faire mieux et aucune raison pour laquelle nous devrions essayer de faire leur travail.

Donc, alors que personne n'a pu briser la sécurité simple de la fonction SafeDBString, j'ai reçu beaucoup d'autres bons arguments. Merci!

107
Rune Grimstad

Je pense que la bonne réponse est:

N'essayez pas de faire vous-même la sécurité. Utilisez la bibliothèque standard de confiance disponible pour ce que vous essayez de faire, plutôt que essayez pour le faire vous-même. Quelles que soient vos hypothèses sur la sécurité, elles peuvent être incorrectes. Aussi sûr que votre propre approche puisse paraître (et elle semble fragile au mieux), il y a un risque que vous oubliez quelque chose et voulez-vous vraiment prendre cette chance en matière de sécurité?

Utilisez des paramètres.

82
JulianR

Et puis quelqu'un va et utilise "au lieu de". Les paramètres sont, l'OMI, le seul moyen sûr d'aller.

Il évite également de nombreux problèmes i18n avec les dates/chiffres; quelle est la date du 01/02/03? Quel est 123 456? Vos serveurs (serveur d'application et serveur de base de données) sont-ils d'accord entre eux?

Si le facteur de risque ne les convainc pas, qu'en est-il des performances? Le SGBDR peut réutiliser le plan de requête si vous utilisez des paramètres, ce qui améliore les performances. Il ne peut pas faire cela avec juste la chaîne.

71
Marc Gravell

L'argument est un non-gain. Si vous parvenez à trouver une vulnérabilité, vos collègues changeront simplement la fonction SafeDBString pour en tenir compte, puis vous demanderont de prouver à nouveau qu'elle n'est pas sûre.

Étant donné que les requêtes paramétrées sont une meilleure pratique de programmation incontestée, la charge de la preuve devrait incomber à elles pour expliquer pourquoi elles n'utilisent pas une méthode à la fois plus sûre et plus performante.

Si le problème est la réécriture de tout le code hérité, le compromis le plus simple serait d'utiliser des requêtes paramétrées dans tout le nouveau code et de remanier l'ancien code pour les utiliser lorsque vous travaillez sur ce code.

Je suppose que le véritable problème est la fierté et l'entêtement, et il n'y a pas grand-chose d'autre à faire à ce sujet.

27

Tout d'abord, votre exemple pour la version "Remplacer" est incorrect. Vous devez mettre des apostrophes autour du texte:

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

C'est donc une autre chose que les paramètres font pour vous: vous n'avez pas à vous soucier de savoir si une valeur doit être placée entre guillemets. Bien sûr, vous pouvez l'intégrer dans la fonction, mais vous devez ensuite ajouter beaucoup de complexité à la fonction: comment connaître la différence entre 'NULL' comme null et 'NULL' comme une simple chaîne, ou entre un nombre et une chaîne qui se trouve juste contenir beaucoup de chiffres. C'est juste une autre source de bugs.

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, économisant ainsi peut-être une étape au serveur lors de l'exécution de la requête.

De plus, échapper des guillemets simples n'est pas suffisant. De nombreux produits DB permettent des méthodes alternatives pour échapper les caractères dont un attaquant pourrait profiter. Dans MySQL, par exemple, vous pouvez également échapper un guillemet simple avec une barre oblique inverse. Et donc la valeur "nom" suivante ferait exploser MySQL avec juste la fonction SafeDBString(), car lorsque vous doublez le guillemet simple, le premier est toujours échappé par la barre oblique inverse, laissant le 2ème "actif":

x\'OR 1 = 1; -


En outre, JulianR soulève un bon point ci-dessous: [~ # ~] jamais [~ # ~] essayez de faire de la sécurité travaillez vous-même. Il est si facile de se tromper de programmation de sécurité de manière subtile qui semble fonctionner, 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 jusque-là.

Comptez toujours autant que possible sur les bibliothèques de sécurité fournies pour votre plateforme. Ils seront rédigés par des personnes qui font du code de sécurité pour gagner leur vie, bien mieux testés que ce que vous pouvez gérer, et entretenus par le fournisseur si une vulnérabilité est trouvée.

19
Joel Coehoorn

Je dirais donc:

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 détecté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 processus sont en place pour garantir que vous jamais manquez un appel à SafeDBString? Le manquer en un seul endroit pourrait ouvrir une multitude de problèmes. Combien allez-vous observer ces choses et considérez combien cet effort est gaspillé lorsque la bonne réponse acceptée est si facile à atteindre.

3) Dans quelle mesure êtes-vous certain d'avoir couvert tous les vecteurs d'attaque que Microsoft (l'auteur de la base de données et la bibliothèque d'accès) connaissent dans votre implémentation SafeDBString ...

4) Est-il facile de lire la structure du sql? L'exemple utilise + concaténation, les paramètres sont très similaires à string.Format, qui est plus lisible.

En outre, il existe 2 façons de déterminer ce qui a été réellement exécuté - lancez votre propre fonction LogCommand, une fonction simple avec pas de problème de sécurité, ou même regardez une trace sql pour déterminer ce que la base de données pense être passe vraiment.

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();
    }

À tort ou à raison, il nous donne les informations dont nous avons besoin sans problèmes de sécurité.

10
Jim T

Avec les requêtes paramétrées, vous obtenez plus qu'une protection contre l'injection SQL. Vous bénéficiez également d'un meilleur potentiel de mise en cache du plan d'exécution. Si vous utilisez le profileur de requêtes du serveur SQL, vous pouvez toujours voir le `` SQL exact qui est exécuté sur la base de données '', de sorte que vous ne perdez vraiment rien en termes de débogage de vos instructions SQL.

7
Steve Willcock

J'ai utilisé les deux approches pour éviter les attaques par injection SQL et préfère définitivement les requêtes paramétrées. Lorsque 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 l'êtes aussi).

5
RedBlueThing

Vous ne pouvez pas effectuer facilement de vérification de type de l'entrée utilisateur sans utiliser de paramètres.

Si vous utilisez les classes SQLCommand et SQLParameter pour effectuer vos appels DB, vous pouvez toujours voir la requête SQL en cours d'exécution. Regardez la propriété CommandText de SQLCommand.

Je suis toujours un peu 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èmement, ce n'est pas parce que "cela a toujours été fait de cette façon" que c'est la bonne façon de procéder.

4
Tim Scarborough

Cela n'est sûr que si vous avez la garantie de passer une chaîne.

Et si vous ne passez pas une chaîne à un moment donné? Et si vous passez 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
joshcomley

D'accord sur les problèmes de sécurité.
Une autre raison d'utiliser des paramètres est 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 en fonction de la chaîne SQL avant de lier les paramètres.

Si toutefois vous ne liez pas de paramètres, la chaîne SQL change à chaque demande (qui a des paramètres différents) et elle ne correspondra jamais à ce qui se trouve dans votre cache.

2
Darren Greaves

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

Là où je dois mettre SQL en code, j'utilise des paramètres, ce qui est la seule chose sensée. Rappelez aux dissidents qu'il existe des pirates informatiques plus intelligents qu'eux, et avec une meilleure incitation à briser le code qui essaie de les déjouer. En utilisant des paramètres, ce n'est tout simplement pas possible, et ce n'est pas comme si c'était difficile.

2
John Saunders

Je n'ai vu aucune autre réponse aborder ce côté du "pourquoi le faire vous-même est mauvais", mais considérez un attaque de troncature SQL .

Il y a aussi le QUOTENAME Fonction T-SQL qui peut être utile si vous ne pouvez pas les convaincre d'utiliser des paramètres. Il attrape beaucoup (tous?) Des problèmes de qoute échappés.

1
JasonRShaver

Il peut être cassé, mais le moyen dépend des versions/correctifs exacts, etc.

Celui qui a déjà été évoqué est le bogue 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 subi un problème d'échappement car certaines séquences UTF8 pourraient être utilisées pour manipuler la fonction de remplacement - la fonction de remplacement serait piégée dans introduction = les caractères d'injection.

À la fin de la journée, le mécanisme de sécurité de remplacement repose sur la fonctionnalité attendue mais pas prévue. Étant donné que la fonctionnalité n'était pas l'objectif prévu du code, il est fort probable que certaines bizarreries découvertes briseront vos fonctionnalités attendues.

Si vous avez beaucoup de code hérité, la méthode replace peut être utilisée comme un espace d'arrêt pour éviter une réécriture et des tests trop longs. Si vous écrivez du nouveau code, il n'y a aucune excuse.

1
David

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 autoriser un code dynamique à toucher ma base de données, ce qui nécessite que tous les contacts passent par des sps (et pas un qui utilise SQl dynamique). Cela signifie que rien d'autre que ce que j'ai donné aux utilisateurs la permission de faire ne peut être fait et que les utilisateurs internes (à l'exception de quelques-uns ayant un accès en production à des fins d'administration) ne peuvent pas accéder directement à mes tables et créer des ravages, voler des données ou commettre une fraude. Si vous exécutez une application financière, c'est la solution la plus sûre.

1
HLGEM

Pour les raisons déjà évoquées, les paramètres sont une très bonne idée. Mais nous détestons les utiliser parce que la création du paramètre et l'attribution de son nom à une variable pour une utilisation ultérieure dans une requête est une épave à triple indirection.

La classe suivante encapsule le constructeur de chaînes que vous utiliserez couramment pour créer des requêtes SQL. Il vous permet d'écrire des requêtes paramétrées sans jamais avoir à créer de paramètre, afin que vous puissiez vous concentrer sur le SQL. Votre code ressemblera à ceci ...

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 en conviendrez, est grandement améliorée et le résultat est une requête paramétrée appropriée.

La classe ressemble à ça ...

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
bbsimonbb

2 ans plus tard , j'ai récidivé ... Quiconque trouve des paramètres une douleur est la bienvenue pour essayer mon extension VS, QueryFirst. Vous modifiez votre demande dans un vrai fichier .sql (Validation, Intellisense). Pour ajouter un paramètre, il vous suffit de le saisir directement dans votre SQL, en commençant par le '@'. Lorsque vous enregistrez le fichier, QueryFirst génère des classes wrapper pour vous permettre d'exécuter la requête et d'accéder aux résultats. Il recherchera le type de base de données de votre paramètre et le mappera à un type .net, que vous trouverez en entrée des méthodes Execute () générées. Rien de plus simple . Le faire de la bonne manière est radicalement plus rapide et plus facile que de le faire de toute autre manière, et créer une vulnérabilité d'injection sql devient impossible, ou du moins perversement difficile. Il existe d'autres avantages importants, comme la possibilité de supprimer des colonnes dans votre base de données et de voir immédiatement les erreurs de compilation dans votre application.

mentions légales: j'ai écrit QueryFirst

1
bbsimonbb

Utilisez toujours des requêtes paramétrées dans la mesure du possible. Parfois, même une simple entrée sans l'utilisation de caractères étranges 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 oublier qu'elle économise également beaucoup de problèmes lorsque vous devez réellement insérer des caractères étranges qui autrement seraient échappés ou modifiés. Il peut même économiser un temps d'exécution précieux à la fin pour ne pas avoir à calculer l'entrée.

1
VulstaR

Depuis très peu de temps que j'ai eu à enquêter sur les problèmes d'injection SQL, je peux voir que le fait de rendre une valeur `` sûre '' signifie également 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.

Cela laisse des paramètres et des procédures stockées.

Et oui, vous devriez toujours essayer d'implémenter le code de la meilleure façon que vous connaissez maintenant - pas seulement comment cela a toujours été fait.

1
quamrana

Une autre considération importante est le suivi des données échappées et non échappées. Il y a des tonnes et des tonnes d'applications, Web et autres, qui ne semblent pas correctement suivre le moment où les données sont brutes-Unicode, & -encodées, HTML formaté, et cetera. Il est évident qu'il deviendra difficile de savoir quelles chaînes sont ''– encodé et qui ne l'est pas.

C'est aussi un problème lorsque vous finissez par changer le type d'une variable - peut-être que c'était un entier, mais maintenant c'est une chaîne. Vous avez maintenant un problème.

0
Paul Fisher

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

  1. Sécurité - La couche d'accès à la base de données sait comment supprimer ou échapper des éléments qui ne sont pas autorisés dans les données.
  2. Séparation des préoccupations - Mon code n'est pas responsable de la transformation des données dans un format que la base de données aime.
  3. Aucune redondance - je n'ai pas besoin d'inclure un assembly ou une classe dans chaque projet qui effectue cette mise en forme/échappement de la base de données; il est intégré à la bibliothèque de classes.
0
Powerlord

Il y avait peu de vulnérabilités (je ne me souviens pas de quelle base de données il s'agissait) liées au débordement de tampon de l'instruction SQL.

Ce que je veux dire, c'est que SQL-Injection est plus que juste "échapper à la citation", et vous n'avez aucune idée de ce qui va suivre.

0
Dennis C