web-dev-qa-db-fra.com

Est-il sûr de ne pas paramétrer une requête SQL lorsque le paramètre n'est pas une chaîne?

En termes de injection SQL , je comprends parfaitement la nécessité de paramétrer un paramètre string; c'est l'un des trucs les plus anciens du livre. Mais quand peut-il être justifié de ne pas paramétrer un SqlCommand? Certains types de données sont-ils considérés comme "sûrs" pour ne pas être paramétrés?

Par exemple: je ne me considère nulle part près d'un expert en SQL, mais je ne pense à aucun cas où il serait potentiellement vulnérable à Injection SQL pour accepter un bool ou un int et juste le concaténer directement dans la requête.

Mon hypothèse est-elle correcte, ou cela pourrait-il potentiellement laisser une énorme vulnérabilité de sécurité dans mon programme?

Pour clarification, cette question est balisée c # qui est une langue fortement typée; quand je dis "paramètre", pensez à quelque chose comme public int Query(int id).

113
johnnyRose

Je pense que c'est sûr ... techniquement , mais c'est une terrible habitude à prendre. Voulez-vous vraiment écrire des requêtes comme celle-ci?

var sqlCommand = new SqlCommand("SELECT * FROM People WHERE IsAlive = " + isAlive + 
" AND FirstName = @firstName");

sqlCommand.Parameters.AddWithValue("firstName", "Rob");

Cela vous laisse également vulnérable dans la situation où un type passe d'un entier à une chaîne (pensez au numéro d'employé qui, malgré son nom, peut contenir des lettres).

Nous avons donc changé le type de EmployeeNumber de int à string, mais nous avons oublié de mettre à jour nos requêtes SQL. Oops.

101
Rob

Lorsque vous utilisez une plate-forme fortement typée sur un ordinateur que vous contrôlez (comme un serveur Web), vous pouvez empêcher l'injection de code pour les requêtes avec uniquement bool, DateTime ou int ( et d'autres valeurs numériques). Ce qui est préoccupant, ce sont les problèmes de performances causés par le fait de forcer le serveur SQL à recompiler chaque requête et en l'empêchant d'obtenir de bonnes statistiques sur les requêtes exécutées avec quelle fréquence (ce qui nuit à la gestion du cache).

Mais cette partie "sur un ordinateur que vous contrôlez" est importante, car sinon un utilisateur peut changer le comportement utilisé par le système pour générer des chaînes à partir de ces valeurs pour inclure du texte arbitraire.

J'aime aussi penser à long terme. Que se passe-t-il lorsque la base de code fortement typée d'aujourd'hui est transférée via la traduction automatique vers le langage dynamique new-hotness, et que vous perdez soudainement la vérification de type, mais vous n'avez pas encore tous les bons tests unitaires pour le code dynamique ?

Vraiment, il n'y a aucune bonne raison de ne pas utiliser de paramètres de requête pour ces valeurs. C'est la bonne façon pour s'y prendre. Allez-y et codez en dur les valeurs dans la chaîne sql alors qu'elles sont vraiment des constantes, mais sinon, pourquoi ne pas simplement utiliser un paramètre? Ce n'est pas comme si c'était dur.

En fin de compte, je n'appellerais pas cela un bug, en soi, mais je l'appellerais un odeur: quelque chose qui tombe juste en deçà d'un bug en soi, mais qui est un forte indication que les bogues sont à proximité, ou le seront éventuellement. Un bon code évite de laisser des odeurs, et tout bon outil d'analyse statique le signalera.

J'ajouterai que ce n'est malheureusement pas le genre d'argument que vous pouvez gagner directement. Cela ressemble à une situation où "avoir raison" ne suffit plus, et marcher sur les pieds de vos collègues pour résoudre ce problème par vous-même n'est pas susceptible de promouvoir une bonne dynamique d'équipe; cela pourrait en fin de compte faire plus de mal que cela n'aide. Une meilleure approche dans ce cas peut être de promouvoir l'utilisation d'un outil d'analyse statique. Cela donnerait légitimité et crédibilité aux efforts visant à revenir en arrière et à fixer le code existant.

65
Joel Coehoorn

Dans certains cas, il IS possible d'effectuer une attaque par injection SQL avec des variables non paramétrées (concaténées) autres que des valeurs de chaîne - voir cet article de Jon: http: // codeblog. jonskeet.uk/2014/08/08/the-bobbytables-culture/ .

Le fait est que lorsque ToString est appelé, un fournisseur de culture personnalisé peut transformer un paramètre non-chaîne en sa représentation de chaîne qui injecte du SQL dans la requête.

53
Maciek

Ce n'est pas sûr même pour les types non-chaîne. Utilisez toujours les paramètres. Période.

Considérez l'exemple de code suivant:

var utcNow = DateTime.UtcNow;
var sqlCommand = new SqlCommand("SELECT * FROM People WHERE created_on <= '" + utcNow + "'");

À première vue, le code semble sûr, mais tout change si vous apportez des modifications aux paramètres régionaux de Windows et ajoutez une injection au format de date courte:

Datetime Injection

Le texte de commande résultant ressemble maintenant à ceci:

SELECT * FROM People WHERE created_on <= '26.09.2015' OR '1'<>' 21:21:43'

La même chose peut être faite pour le type int car l'utilisateur peut définir un signe négatif personnalisé qui peut être facilement changé en injection SQL.

On pourrait faire valoir que la culture invariante devrait être utilisée à la place de la culture actuelle, mais j'ai vu des concaténations de chaînes comme celle-ci tant de fois et il est assez facile de manquer lors de la concaténation de chaînes avec des objets utilisant +.

51
Kaspars Ozols

"SELECT * FROM Table1 WHERE Id =" + intVariable.ToString ()


Sécurité
C'est bon.
Les attaquants ne peuvent rien injecter dans votre variable int typée.

Performance
Pas d'accord.

Il est préférable d'utiliser des paramètres, donc la requête sera compilée une fois et mise en cache pour la prochaine utilisation. La prochaine fois, même avec différentes valeurs de paramètre, la requête est mise en cache et n'a pas besoin d'être compilée dans le serveur de base de données.

Style de codage
Mauvaise pratique.

  • Les paramètres sont plus lisibles
  • Peut-être que cela vous permet de vous habituer aux requêtes sans paramètres, puis vous avez peut-être fait une erreur une fois et utilisez une valeur de chaîne de cette façon et vous devriez probablement dire au revoir à vos données. Mauvaise habitude!


"SELECT * FROM Product WHERE Id =" + TextBox1.Text


Bien que ce ne soit pas votre question, mais peut-être utile pour les futurs lecteurs:

Sécurité
Catastrophe!
Même lorsque le champ Id est un entier, votre requête peut être soumise à une injection SQL. Supposons que vous ayez une requête dans votre application "SELECT * FROM Table1 WHERE Id=" + TextBox1.Text, Un attaquant peut insérer dans la zone de texte 1; DELETE Table1 et la requête sera:

"SELECT * FROM Table1 WHERE Id=1; DELETE Table1"

Si vous ne souhaitez pas utiliser la requête paramétrée ici, vous devez utiliser des valeurs saisies:

string.Format("SELECT * FROM Table1 WHERE Id={0}", int.Parse(TextBox1.Text))


Votre question


Ma question s'est posée parce qu'un collègue a écrit un tas de requêtes concaténant des valeurs entières, et je me demandais si c'était une perte de temps de passer en revue et de les corriger toutes.

Je pense que changer ces codes n'est pas une perte de temps. En effet, le changement est recommandé!

si votre collègue utilise des variables int, cela ne présente aucun risque pour la sécurité, mais je pense que le changement de ces codes n'est pas une perte de temps et en effet, le changement de ces codes est recommandé. Il rend le code plus lisible, plus facile à maintenir et accélère l'exécution.

23
Reza Aghaei

Il y a en fait deux questions en une. Et la question du titre a très peu à voir avec les préoccupations exprimées par le PO dans les commentaires ultérieurs.

Bien que je réalise que pour le PO, c'est leur cas particulier qui compte, pour les lecteurs venant de Google, il est important de répondre à la question plus générale, qui peut être formulée comme "la concaténation est-elle aussi sûre que les déclarations préparées si je m'assurais que chaque littéral que je concatène est sûr? ". Je voudrais donc me concentrer sur ce dernier. Et la réponse est

Définitivement non.

L'explication n'est pas aussi directe que la plupart des lecteurs le souhaiteraient, mais je ferai de mon mieux.

J'ai réfléchi à la question pendant un certain temps, ce qui a abouti à article (bien que basé sur l'environnement PHP) où j'ai essayé de tout résumer. moi que la question de la protection contre l'injection SQL est souvent éludée vers des sujets connexes mais plus étroits, comme l'échappement de chaînes, le transtypage de type, etc. Bien que certaines des mesures puissent être considérées comme sûres lorsqu'elles sont prises seules, règle à suivre. Ce qui rend le terrain très glissant, mettant trop l'attention et l'expérience du développeur.

La question de l'injection SQL ne peut pas être simplifiée en une question de syntaxe particulière. Il est plus large que le développeur moyen ne le pensait. C'est aussi une question méthodologique. Ce n'est pas seulement "Quel format particulier nous devons appliquer", mais " Comment cela doit être fait" également.

(De ce point de vue, un article de Jon Skeet cité dans l'autre réponse fait plutôt du mal que du bien, car il est à nouveau tatillon sur certains cas Edge, se concentrant sur un problème de syntaxe particulier et ne traitant pas le problème dans son ensemble.)

Lorsque vous essayez de résoudre la question de la protection non pas comme un tout, mais comme un ensemble de problèmes de syntaxe différents, vous êtes confronté à une multitude de problèmes.

  • la liste des choix de formatage possibles est vraiment énorme. Cela signifie que l'on peut facilement en oublier certains. Ou confondez-les (en utilisant chaîne échappant pour identifiant par exemple).
  • La concaténation signifie que toutes les mesures de protection doivent être effectuées par le programmeur et non par le programme. Ce problème à lui seul entraîne plusieurs conséquences:
    • un tel formatage est manuel. Manuel signifie extrêmement sujet aux erreurs. On pourrait simplement oublier de postuler.
    • de plus, il existe une tentation de déplacer les procédures de formatage dans une fonction centralisée, de gâcher encore plus les choses et de gâcher des données qui ne vont pas dans la base de données.
  • lorsque plusieurs développeurs sont impliqués, les problèmes se multiplient par dix.
  • quand la concaténation est utilisée, on ne peut pas voir une requête potentiellement dangereuse en un coup d'œil: ils tous potentiellement dangereux!

Contrairement à ce gâchis, les déclarations préparées sont en effet Le Saint Graal:

  • elle peut s'exprimer sous la forme d'une règle simple, facile à suivre.
  • il s'agit essentiellement d'une mesure non détachable, ce qui signifie que le développeur ne peut pas interférer et, volontairement ou non, gâcher le processus.
  • la protection contre l'injection n'est vraiment qu'un effet secondaire des instructions préparées, dont le véritable objectif est de produire une instruction syntaxiquement correcte. Et une déclaration syntaxiquement correcte est 100% résistante aux injections. Pourtant, nous avons besoin que notre syntaxe soit correcte malgré toute possibilité d'injection.
  • s'il est utilisé tout autour, il protège l'application quelle que soit l'expérience du développeur. Disons, il y a une chose appelée injection de second ordre . Et une illusion très forte qui se lit "afin de protéger, Escape All User Supplied Input ". Combinés ensemble, ils conduisent à l'injection, si un développeur prend la liberté de décider, ce qui doit être protégé et ce qui ne l'est pas.

(En réfléchissant plus loin, j'ai découvert que l'ensemble actuel d'espaces réservés n'est pas suffisant pour les besoins réels et doit être étendu, à la fois pour les structures de données complexes, comme les tableaux, et même les mots-clés ou identificateurs SQL, qui doivent parfois être ajoutés à la requête dynamiquement aussi, mais un développeur n'est pas armé pour un tel cas, et forcé de se replier sur la concaténation de chaînes mais c'est une question d'une autre question).

Fait intéressant, la controverse de cette question est provoquée par la nature très controversée de Stack Overflow. L'idée du site est d'utiliser des questions particulières d'utilisateurs qui demandent directement pour atteindre l'objectif d'avoir une base de données de réponses à usage général adaptées aux utilisateurs issus de la recherche . L'idée n'est pas mauvaise en soi, mais elle échoue dans une situation comme celle-ci: lorsqu'un utilisateur pose une question très étroite, en particulier pour obtenir un argument dans un différend avec un collègue (ou pour décider s'il vaut la peine de refactoriser le code). Alors que la plupart des participants expérimentés essaient d'écrire une réponse, en gardant à l'esprit la mission de Stack Overflow dans son ensemble, ce qui rend leur réponse bonne pour autant de lecteurs que possible, pas seulement pour l'OP.

18
Your Common Sense

Ne pensons pas seulement à des considérations de sécurité ou de type sécurisé.

La raison pour laquelle vous utilisez des requêtes paramétrées est d'améliorer les performances au niveau de la base de données. Du point de vue de la base de données, une requête paramétrée est une requête dans le tampon SQL (pour utiliser la terminologie d'Oracle bien que j'imagine que toutes les bases de données ont un concept similaire en interne). Ainsi, la base de données peut contenir une certaine quantité de requêtes en mémoire, préparées et prêtes à être exécutées. Ces requêtes n'ont pas besoin d'être analysées et seront plus rapides. Les requêtes fréquemment exécutées seront généralement dans le tampon et n'auront pas besoin d'être analysées chaque fois qu'elles sont utilisées.

SAUF SI

Quelqu'un n'utilise pas de requêtes paramétrées. Dans ce cas, le tampon est continuellement vidé par un flux de requêtes presque identiques dont chacune doit être analysée et exécutée par le moteur de base de données et les performances en souffrent de manière générale, car même les requêtes fréquemment exécutées finissent par être analysées à nouveau plusieurs fois par journée. J'ai réglé des bases de données pour gagner ma vie et cela a été l'une des plus grandes sources de fruits à faible pendaison.

À PRÉSENT

Pour répondre à votre question, SI votre requête a un petit nombre de valeurs numériques distinctes, vous ne causerez probablement pas de problèmes et vous pourrez en fait améliorer les performances à l'infini. SI toutefois il existe potentiellement des centaines de valeurs et que la requête est souvent appelée, vous allez affecter les performances de votre système, alors ne le faites pas.

Oui, vous pouvez augmenter la mémoire tampon SQL, mais c'est toujours en fin de compte au détriment d'autres utilisations plus critiques de la mémoire comme la mise en cache des index ou des données. Moralement, utilisez les requêtes paramétrées assez religieusement afin d'optimiser votre base de données et d'utiliser plus de mémoire serveur pour les choses qui comptent ...

15
mcottle

Pour ajouter des informations à la réponse de Maciek:

Il est facile de modifier les informations culturelles d'une application tierce .NET en appelant la fonction principale de l'Assemblée par réflexion:

using System;
using System.Globalization;
using System.Reflection;
using System.Threading;

namespace ConsoleApplication2
{
  class Program
  {
    static void Main(string[] args)
    {
      Assembly asm = Assembly.LoadFile(@"C:\BobbysApp.exe");
      MethodInfo mi = asm.GetType("Test").GetMethod("Main");
      mi.Invoke(null, null);
      Console.ReadLine();
    }

    static Program()
    {
      InstallBobbyTablesCulture();
    }

    static void InstallBobbyTablesCulture()
    {
      CultureInfo bobby = (CultureInfo)CultureInfo.InvariantCulture.Clone();
      bobby.DateTimeFormat.ShortDatePattern = @"yyyy-MM-dd'' OR ' '=''";
      bobby.DateTimeFormat.LongTimePattern = "";
      bobby.NumberFormat.NegativeSign = "1 OR 1=1 OR 1=";
      Thread.CurrentThread.CurrentCulture = bobby;
    }
  }
}

Cela ne fonctionne que si la fonction principale de BobbysApp est publique. Si Main n'est pas public, vous pouvez appeler d'autres fonctions publiques.

8
user1027167

À mon avis, si vous pouvez garantir que le paramètre avec lequel vous travaillez ne contiendra jamais de chaîne, il est sûr, mais je ne le ferais en aucun cas. En outre, vous constaterez une légère baisse des performances en raison du fait que vous effectuez la concaténation. La question que je vous pose est pourquoi ne voulez-vous pas utiliser de paramètres?

7
Max

C'est ok mais jamais sûr .. et la sécurité dépend toujours des entrées, par exemple si l'objet d'entrée est TextBox, les attaquants peuvent faire quelque chose de délicat puisque la zone de texte peut accepter une chaîne, vous devez donc mettre une sorte de validation/conversion pour être en mesure d'empêcher les utilisateurs la mauvaise entrée. Mais le fait est que ce n'est pas sûr. Aussi simplement que ça.

3
japzdivino