Question

Si je devais sélectionner une ligne dans une table, j'ai essentiellement deux options, soit comme ceci

int key = some_number_derived_from_a_dropdown_or_whatever
SqlCommand cmd = new SqlCommand("select * from table where primary_key = " + key.ToString());

ou utilisez un paramètre

SqlCommand cmd = new SqlCommand("select * from table where primary_key = @pk");
SqlParameter param  = new SqlParameter();
param.ParameterName = "@pk";
param.Value         = some_number_derived_from_a_dropdown_or_whatever;
cmd.Parameters.Add(param);

Maintenant, je sais que la première méthode est désapprouvée en raison d'attaques d'injection SQL possibles, mais dans ce cas, le paramètre est un entier et ne devrait donc pas vraiment pouvoir injecter de code malveillant.

Ma question est la suivante: utilisez-vous l'option 1 dans le code de production parce que vous considérez que l'utilisation est sûre en raison de la facilité d'utilisation et du contrôle du paramètre inséré (comme ci-dessus, ou si le paramètre est créé dans le code)? Ou utilisez-vous toujours des paramètres, peu importe quoi? Les paramètres 100% d’injection sont-ils sûrs?

Était-ce utile?

La solution

Je vais ignorer l'argument Injection SQL, qui est trop bien connu et me concentrer sur l'aspect SQL des paramètres et non des paramètres.

Lorsque vous envoyez un lot SQL au serveur, tout lot doit être analysé pour être compris. Comme tout autre compilateur, le compilateur SQL doit générer un AST à partir du texte, puis fonctionner l'arbre de syntaxe. En fin de compte, l’optimiseur transforme l’arbre de syntaxe en un arbre d’exécution et génère un plan d’exécution qui est exécuté. À l’époque des ténèbres d’environ 1995, le fait que le lot soit une requête ad-hoc ou une procédure stockée faisait une différence, mais aujourd’hui, il n’en fait absolument aucune, elles sont toutes identiques.

Là où les paramètres font la différence, c'est qu'un client qui envoie une requête du type select * from table où primary_key = @pk envoie exactement le même texte SQL à chaque fois quelle que soit la valeur qui intéresse. Ce qui se produit alors est que le processus entier que je viens de décrire est court-circuité. SQL recherchera en mémoire un plan d'exécution pour le texte brut, non analysé (sur la base d'un condensé de hachage de l'entrée) et, s'il est trouvé, exécutera ce plan. Cela signifie pas d'analyse, pas d'optimisation, rien, le lot passe directement à l'exécution . Sur les systèmes OLTP qui exécutent des centaines et des milliers de petites requêtes chaque seconde, ce raccourci accélère considérablement les performances.

Si vous envoyez la requête sous la forme select * from table où primary_key = 1 , SQL devra au moins l'analyser pour comprendre le contenu du texte, ce dernier étant probablement un nouveau un, différent de tout lot précédent vu (même un seul caractère comme 1 ou 2 rend le lot entier différent). Il fonctionnera ensuite sur l'arbre de syntaxe résultant et tentera un processus appelé Paramétrage simple . Si la requête peut être paramétrée automatiquement, SQL recherchera probablement un plan d'exécution mis en cache à partir d'autres requêtes exécutées précédemment avec d'autres valeurs pk et réutilisera ce plan. Par conséquent, au moins votre requête n'a pas besoin d'être optimisée et vous ignorez le processus. étape de génération d’un plan d’exécution réel. Mais vous n’avez en aucun cas réalisé le court-circuit complet, le chemin le plus court possible avec une véritable requête paramétrée client.

Vous pouvez consulter SQL Server, objet statistiques SQL . compteur de performance de votre serveur. Le compteur Tentatives Auto-Param / s indiquera plusieurs fois par seconde que SQL doit traduire une requête reçue sans paramètres en une requête auto-paramétrée. Chaque tentative peut être évitée si vous paramétrez correctement la requête dans le client. Si vous avez également un nombre élevé de Echec d'Auto-Params / s , c'est encore pire, cela signifie que les requêtes entament le cycle complet d'optimisation et de génération du plan d'exécution.

Autres conseils

Utilisez toujours l'option 2).

Le premier n'est PAS en sécurité pour Attaques par injection SQL .

Le second est non seulement beaucoup plus sûr, mais également plus performant, car l'optimiseur de requête a plus de chances de créer un bon plan de requête car il semble toujours la même chose, attendez-vous bien sûr aux paramètres.

Pourquoi devriez-vous éviter l'option 1

Même s'il semble que vous obtenez cette valeur de l'élément select , une personne peut simuler une requête HTTP et publier ce qu'elle souhaite dans certains champs. Le code de l’option 1 doit au moins remplacer certains caractères / combinaisons dangereux (par exemple, guillemets simples, crochets, etc.).

Pourquoi êtes-vous encouragé à utiliser l'option 2

Le moteur de requête SQL est capable de mettre en cache le plan d’exécution et de gérer les statistiques pour diverses requêtes. Donc, avoir des requêtes unifiées (la meilleure serait bien sûr d'avoir une procédure stockée séparée) accélère l'exécution à long terme.

Je n'ai encore entendu parler d'aucun exemple dans lequel des paramètres peuvent être piratés pour une injection SQL. Jusqu'à preuve du contraire, je les considère comme étant sans danger.

Le SQL dynamique ne doit jamais être considéré comme sûr. Même avec " digne de confiance " entrée, c’est une mauvaise habitude à prendre. Notez que cela inclut le SQL dynamique dans les procédures stockées; L'injection SQL peut également s'y produire.

Parce que vous avez le contrôle sur le fait que la valeur est un entier, elles sont pratiquement équivalentes. En général, je n'utilise pas le premier formulaire et, en général, je n'autorise pas le second non plus, car je n'autorise généralement pas l'accès aux tables et nécessite l'utilisation de procédures stockées. Et bien que vous puissiez effectuer l’exécution de SP sans utiliser la collection de paramètres, je vous recommande néanmoins d’utiliser les paramètres de SP AND :

-- Not vulnerable to injection as long as you trust int and int.ToString()
int key = some_number_derived_from_a_dropdown_or_whatever ;
SqlCommand cmd = new SqlCommand("EXEC sp_to_retrieve_row " + key.ToString()); 

-- Vulnerable to injection all of a sudden
string key = some_number_derived_from_a_dropdown_or_whatever ;
SqlCommand cmd = new SqlCommand("EXEC sp_to_retrieve_row " + key.ToString()); 

Notez que bien que l'option 1 soit sûre dans votre cas , que se passe-t-il lorsque quelqu'un voit et utilise cette technique avec une variable non entière? Vous formez maintenant des personnes à utiliser une technique qui pourrait être ouvert à l'injection.

Notez que vous pouvez renforcer votre base de données de manière proactive pour éviter toute injection efficace, même lorsque le code de votre application est défectueux. Pour les comptes / rôles utilisés par les applications:

  • Autorisez uniquement l'accès aux tables si cela est absolument nécessaire
  • Autorisez uniquement l'accès aux vues si cela est absolument nécessaire
  • Ne pas autoriser les instructions DDL
  • Autoriser l'exécution sur les SP sur la base d'un rôle
  • Tout code SQL dynamique dans les fournisseurs de services doit être examiné comme absolument nécessaire

Personnellement, j’utiliserais toujours l’option 2 par habitude. Cela étant dit:

Étant donné que vous forcez la conversion de la valeur de liste déroulante en un entier, cela fournira une certaine protection contre les attaques par injection SQL. Si quelqu'un essaie d'injecter des informations supplémentaires dans les informations publiées, C # lève une exception lors de la tentative de conversion du code malveillant en une valeur entière contrecarrant la tentative.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top