Pourquoi les procédures stockées et les instructions préparées sont-elles les méthodes modernes préférées pour empêcher l'injection SQL sur la fonction mysql_real_escape_string ()
?
Pourquoi les procédures stockées et les instructions préparées sont-elles les méthodes modernes préférées pour empêcher l'injection SQL sur la fonction mysql_real_escape_string ()
?
Le problème de l'injection SQL est essentiellement le mélange de logique et de données, où ce qui devrait être des données est traité comme logique. Les instructions préparées et les requêtes paramétrées résolvent en fait ce problème à la source en séparant complètement la logique des données, de sorte que l'injection devient presque impossible. La fonction d'échappement de chaîne ne résout pas réellement le problème, mais elle essaie d'éviter les dommages en échappant à certains caractères. Avec une fonction d'échappement de chaîne, aussi bonne soit-elle, il y a toujours le souci d'un cas de bord possible qui pourrait permettre à un caractère non échappé de passer.
Un autre point important, comme mentionné dans @ Sebastian's commentaire, c'est qu'il est beaucoup plus facile d'être cohérent lors de l'écriture d'une application à l'aide d'instructions préparées. L'utilisation d'instructions préparées est d'une architecture différente de l'ancienne méthode classique; au lieu de la concaténation de chaînes, vous construisez des instructions en liant des paramètres (par exemple PDOStatement :: bindParam pour PHP), suivis d'une instruction d'exécution distincte (PDOStatement :: execute). Mais avec mysql_real_escape_string ()
, en plus d'exécuter votre requête, vous devez vous rappeler d'appeler cette fonction en premier. Si votre application a 1000 points de terminaison qui exécutent des requêtes SQL mais que vous oubliez d'appeler mysql_real_escape_string ()
ne serait-ce que sur l'un d'entre eux, ou si vous le faites de manière incorrecte, toute votre base de données pourrait être laissée grande ouverte.
Je pense que la question principale ici est de savoir pourquoi l'échappement de chaîne n'est pas aussi bon que les autres méthodes.
La réponse est que certains cas de bord permettent aux injections de passer même si elles sont échappées. Stack Overflow a plusieurs exemples ici.
Bien que vous puissiez rester en sécurité en vous protégeant contre les entrées utilisateur qui échappent à SQLi, il est important de noter que cela peut ne pas toujours être suffisant. Dans ce terrible exemple, les guillemets ne sont jamais nécessaires pour réussir une injection SQL, malgré l'échappement:
<? Php / * Ceci est un exemple de mauvaise pratique pour plusieurs raisons. Ce code ne doit jamais être utilisé en production. * / $ Post_id = mysql_real_escape_string ($ _ GET ['id']); $ qry = mysql_query ("SELECT title, text FROM posts WHERE id = $ post_id"); $ row = mysql_fetch_array ( $ qry); echo '<h1>'. $ row ['title']. '< / h1>'; echo $ row ['text'];
Maintenant, que se passerait-il si on visitait .php? id = -1 + UNION + ALL + SELECT + 1, version ()
? Voyons comment la requête se déroule:
SELECT title, text FROM posts WHERE id = -1 UNION ALL SELECT 1, version ()
Bien sûr qu'il y en a d'autres façons de résoudre ce problème (par exemple, en utilisant des guillemets et des échappements ou des int-cast), mais l'utilisation d'instructions préparées est un moyen de rester moins enclin à manquer ces choses et de laisser le pilote se soucier de la manière d'inclure les entrées de l'utilisateur dans la requête (même parce que , bien que cet exemple semble si facile à corriger, il existe des injections SQL à plusieurs niveaux qui consistent essentiellement à enregistrer des parties de la requête SQL dans la base de données en sachant que les données de la base de données seront utilisées à l'avenir dans le cadre d'une autre requête) .
Mieux vaut prévenir que guérir.
Un trope commun mentionné contre PHP est mysqli_real_escape_string ()
(regardez combien de temps il est! Can ' sont-ils cohérents avec la nomenclature?), mais la plupart des gens ne réalisent pas que l'API PHP appelle simplement l'API MySQL. Et que fait-elle?
Cette fonction crée une chaîne SQL légale à utiliser dans une instruction SQL
En d'autres termes, lorsque vous utilisez ceci , vous demandez à MySQL de nettoyer la valeur pour qu'elle soit «sûre» à utiliser dans SQL. Vous demandez déjà à la base de données de travailler pour vous à cet égard. L'ensemble du processus ressemble à ceci
Lorsque vous utilisez une instruction préparée, vous dites à votre base de données qu'elle doit le faire dans un ordre différent
Parce que nous envoyons les données séparément, nous faisons le même travail que l'échappement, mais nous bénéficions de ne pas avoir de données non fiables dans notre SQL . En tant que tel, le moteur ne peut jamais confondre les données fournies avec les instructions SQL.
Il y a aussi un avantage caché ici. Ce que vous ne réalisez peut-être pas, c'est qu'une fois l'étape 1 terminée, vous pouvez répéter les étapes 2 et 3 encore et encore, à condition qu'elles aient toutes besoin d'exécuter la même requête, juste avec des données différentes.
$ prep = $ mysqli->prepare ('INSERT INTO visites_log (nom_surviste, id_survité) VALUES (?,?)'); $ prep->bind_param ('si', $ nom_tilisateur, $ identifiant_touriste); // cette fonction passe par referenceforeach ($ _ POST ['guest_list'] comme $ visiteur_id = > $ nom_touriste) $ prep->execute ();
Cette requête a l'avantage de n'avoir qu'à boucler sur les données et à les envoyer encore et encore, plutôt que d'ajouter la surcharge d'analyse à chaque fois.
Une bonne raison de ne pas utiliser mysql_real_escape_string (PHP 4> = 4.3.0, PHP 5) mysql_real_escape_string - Échappe les caractères spéciaux d'une chaîne pour une utilisation dans une instruction SQL Avertissement Cette extension est obsolète en PHP 5.5.0, et il a été supprimé dans PHP 7.0.0. À la place, l'extension MySQLi ou PDO_MySQL doit être utilisée. Voir aussi MySQL: choisir un guide d'API et FAQ associée pour plus d'informations. Les alternatives à cette fonction incluent: Source: mysql_real_escape_string La doc explique également que l'échappement dépend du jeu de caractères : Le jeu de caractères doit être défini soit au niveau du serveur, ou avec la fonction API mysql_set_charset () pour qu'elle affecte mysql_real_escape_string (). Voir la section des concepts sur les jeux de caractères pour plus d'informations. Je dois également ajouter que les procédures stockées ne sont pas toujours «sûres», par exemple lorsqu'elles contiennent du SQL dynamique ... elles ne protègent pas contre les mauvaises pratiques de codage. Mais en effet, vous devriez utiliser des instructions paramétrées , c'est à vous de décider si vous souhaitez utiliser des procédures stockées. mysql_real_escape_string
: il est déconseillé
mysqli_real_escape_string () PDO :: quote ()
Quelques bonnes réponses déjà, et je vais apporter quelques précisions supplémentaires:
Échapper
mysql_real_escape_string
peut être utilisé en toute sécurité. Le conseil avec l'échappement est d'utiliser la fonction d'échappement adaptée à votre base de données . Vous avez besoin d'une fonction légèrement différente pour chaque base de données. La plupart des problèmes de sécurité subtils proviennent de personnes utilisant leur propre désinfectant (par exemple, simplement '
-> ' '
) et ne réalisant pas les cas de coin. Vous devez toujours utiliser correctement mysql_real_escape_string
, en mettant la valeur entre guillemets, mais cela (le principe, pas les guillemets) est vrai pour la plupart des défenses, y compris les déclarations préparées.
Instructions préparées
Les instructions préparées sont un très bon moyen d'arrêter l'injection SQL. Cependant, vous pouvez toujours vous tromper, j'ai parfois vu des gens construire dynamiquement une instruction SQL et construire une instruction préparée à partir de cela. Ils ont été très bouleversés quand nous leur avons dit que ce n'était toujours pas sûr, car nous leur avions dit d'utiliser des déclarations préparées! De plus, le code avec lequel vous vous retrouvez avec des instructions préparées est un peu désagréable. Pas vraiment, mais juste légèrement discordant sur le plan esthétique.
Certains connecteurs de base de données implémentent des instructions préparées en utilisant l'échappement en interne (je pense que psychopg pour Python / Postgres fait cela). Avec d'autres bases de données (Oracle en est une), le protocole prend en charge spécifiquement les paramètres, ils sont donc complètement séparés de la requête.
Procédures stockées
Les procédures stockées n'interrompent pas nécessairement l'injection SQL. Certains connecteurs vous permettent de les appeler directement comme une instruction préparée. Mais ils sont souvent appelés à partir de SQL, où vous devez toujours transmettre des données en toute sécurité dans SQL. Il est également possible qu'une procédure stockée ait une injection SQL en interne, c'est donc dangereux même lorsqu'elle est appelée avec une instruction préparée. Je pense que ce conseil découle principalement de la confusion. Les responsables de la sécurité ne sont généralement pas des DBA et sont vagues sur la différence entre les instructions préparées, les requêtes paramétrées (un autre nom pour les instructions préparées) et les procédures stockées (quelque chose de différent). Et ce conseil a persisté car les procédures stockées présentent certains avantages en matière de sécurité. Ils vous permettent d'appliquer la logique métier au niveau de la base de données, soit pour permettre un accès direct sécurisé à la base de données, soit comme mécanisme de défense en profondeur.
Saisissez une syntaxe de requête sûre
La façon dont je recommande d'arrêter l'injection SQL est d'utiliser un mécanisme de requête de type sécurisé. Il y en a beaucoup, y compris la plupart des ORM (Hibernate, SQLAlchemy, etc.) ainsi que certaines bibliothèques / fonctionnalités non ORM comme LINQ ou jOOQ. Ceux-ci protègent contre l'injection SQL, fournissent un bon code esthétique, et il est également plus difficile de les utiliser de manière incorrecte et de se retrouver avec une injection SQL. Personnellement, je suis un grand fan des ORM, mais tout le monde ne l'est pas. Pour la sécurité, le point clé n'est pas que vous utilisez un ORM, mais que vous utilisez un système de requête de type sécurisé.
Ok, j'espère que cela clarifie les choses pour vous. Bonne question.
Un argument supplémentaire, non-InfoSec, est que les paramètres permettent potentiellement des requêtes plus rapides et moins d'utilisation de la mémoire.
En utilisant les paramètres, les données et la requête sont séparées, ce qui est particulièrement important lors de l'insertion de très grandes chaînes et champs binaires (la chaîne entière doit être traitée par la fonction d'échappement (qui l'étend et peut nécessiter la copie de la chaîne entière), puis à nouveau analysée par le moteur SQL, alors qu'un paramètre doit simplement être passé et non analysé) .
Certains connecteurs de base de données permettent également le découpage avec des paramètres, ce qui évite d'avoir à avoir la chaîne entière en mémoire à la fois. La segmentation à l'aide de la concaténation de chaînes n'est jamais une option, car les données font partie de la chaîne SQL et donc la chaîne SQL ne peut pas être analysée sans elle.
Comme il n'y a aucun avantage à utiliser la concaténation de chaînes avec une fonction d'échappement sur l'utilisation de paramètres, et il y en a plusieurs pour l'utilisation de paramètres sur la concaténation de chaînes, il est logique que nous poussions vers les paramètres. Cela a conduit à la dépréciation des fonctions d'échappement, ce qui introduit des problèmes de sécurité potentiels à l'avenir, ce qui renforce la nécessité de ne pas les utiliser.
Les procédures stockées et les instructions préparées limitent la portée potentielle en utilisant des paramètres. call GetUserWithName ('jrmoreno');
offre moins d’opportunité de jouer avec la requête, pas aucune opportunité. Notez que les procédures stockées paramétrées via des instructions préparées offrent encore moins call GetUserWithName (@userName);
que les procédures stockées simples.
De plus, l'utilisation de procédures stockées vous ouvre à un autre type de injection sql: sql dynamique dans la procédure stockée.
Quant à savoir pourquoi utiliser des instructions préparées, les instructions préparées sont un protocole binaire où les paramètres ont des types et des tailles fixes. Lorsqu'il est traité par le serveur, il est impossible pour un nombre d'être autre chose qu'un nombre ou pour une chaîne de s'étendre au-delà de ses limites et d'être traitée comme plus de commandes sql (tant que cette chaîne n'est pas utilisée pour créer un sql dynamique à l'intérieur le moteur sql).
Échapper une chaîne ne peut tout simplement pas vous donner le même niveau d'assurance.
Je peux avoir une politique pour toujours utiliser des procédures stockées et / ou des instructions préparées avec n'importe quelle base de données pour laquelle j'écris un logiciel un jour donné. Mon code c # (par exemple) a alors le même aspect quelle que soit la base de données choisie par mon employeur actuel, il est donc facile de le repérer lorsque je n'utilise pas d'instructions préparées, etc.
Les instructions préparées ne sont pas importantes, ce qui importe jamais en utilisant des opérateurs de chaîne pour créer le SQL à envoyer à la base de données.
mysql_real_escape_string ()
est un cas spatial qui n'est l'option que pour une base de données, donc comment apprendre quelque chose qui peut ne pas être utile pour la prochaine base de données avec laquelle je dois coder?
Les procédures stockées sont supérieures non seulement parce qu'elles constituent une défense efficace contre certaines attaques, y compris l'injection. Ils sont supérieurs parce que vous obtenez plus en faisant moins, et de manière plus sécurisée, avec une «preuve» de sécurité de facto. Alors que le simple échappement des chaînes est loin de fournir cette "preuve".
Comment cela?
Une chaîne de requête, même si les données sont correctement échappées, est un morceau de texte. Le texte est odieux par nature. Le texte est analysé, ce qui prend du temps et peut mal tourner de plusieurs manières, puis le moteur de base de données fait quelque chose . Qu'est ce que ça fait? Vous ne pouvez jamais être sûr à 100%.
Vous pourriez oublier d'appeler la fonction d'échappement de chaîne, ou la fonction d'échappement de chaîne pourrait être défectueuse d'une manière qui pourrait être exploitée. Peu probable, mais même sur une requête correctement échappée à 100%, il est en principe possible de faire quelque chose de différent. Oh bien sûr, il y a toujours un contrôle d'accès et autre pour limiter les dommages possibles, mais peu importe. Il n'en reste pas moins qu'en principe, vous lancez une chaîne au moteur de base de données qui n'est pas vraiment très différente de l'envoi de code exécutable, puis vous vous attendez à ce qu'il fasse quelque chose , ce qui, espérons-le, est exactement ce que vous voulez .
Les procédures stockées, par contre, encapsulent exactement une opération spécifiée, jamais rien de différent. Envoyez ce que vous voulez, l'opération réelle qui se produira est déjà définie.
Les procédures stockées sont, en principe, analysées et optimisées exactement une fois, et (éventuellement, pas nécessairement, mais du moins en théorie) compilées en une représentation particulièrement rapide (par exemple une forme de bytecode).
Alors ... vous avez oublié d'échapper une chaîne? On s'en fout. La fonction d'échappement de chaîne ne fonctionne pas correctement? On s'en fout. La procédure stockée ne peut jamais faire quoi que ce soit que vous ne lui avez pas demandé de faire à un moment antérieur. La pire chose à arriver est que quelqu'un essayant d'exploiter le serveur parvient à insérer des déchets comme son nom d'utilisateur ou autre. Qui s'en soucie.
Ainsi, vous bénéficiez d'une bien meilleure sécurité et le serveur doit également effectuer moins de travail (c'est-à-dire que les requêtes s'exécutent plus rapidement). Vous gagnez des deux côtés (ce qui est rare car vous avez généralement un compromis).
De plus, d'un point de vue purement "esthétique", pas que cela compte dans la pratique, les choses sont la façon dont ils devraient être . La base de données ne reçoit que des données et exécute une sorte de programme pour ce compte, à sa seule discrétion.
Au niveau stratégique, le problème est lié à la vigilance: lorsque nous écrivons du code, si nous devons faire quelque chose de plus pour garantir la prévention d'un problème de sécurité (ou de sûreté, ou toute autre forme de qualité / performance), alors nous devons être vigilants pour le faire.
C'est, je crois, le principal problème lié à l'utilisation d'une fonction «d'échappement».
Ce qui aggrave le problème, c'est que, parce que le La fonction 'escape' est exécutée en dehors de la construction / évaluation de la requête elle-même, il n'y a pas de moyen simple ou efficace de pouvoir auditer toutes les requêtes concernant la présence ou l'absence d'évasion.
Pour éviter cette faiblesse, il faut transformer la composition escape / sql en une fonction - qui est essentiellement ce qu'est une instruction préparée, ou utiliser une couche abstraite pour son sql (par exemple, une sorte de ORM).
En tant que singe SQL, aucune des deux options n'est merveilleuse. Surtout lorsqu'il s'agit de structures SQL complexes (par exemple, des tables de clés primaires à 3 champs avec plusieurs jointures à gauche, y compris en utilisant la même table deux fois, avec un alias: pas inhabituel pour les structures arborescentes).
Je ne parle pas théoriquement: c'est une expérience pratique. Nous avons commencé par utiliser des mécanismes d'échappement, puis nous nous sommes fait prendre - heureusement par un stylo. agence de test - qui a trouvé une évasion manquante (le cas en question était la valeur d'un cookie de session qui n'avait pas été échappé). Ils l'ont démontré en injectant "select sleep (10);" - une manière brillante de tester / démontrer les injections.
(En allant un peu hors sujet, il est presque impossible pour l'équipe de développement d'écrire des tests d'injection pour leur propre code. Nous avons tendance à voir les choses uniquement à partir de une position. C'est pourquoi l'entreprise recommande toujours des agences de test de stylo tierces et totalement indépendantes.)
Alors, oui. escape () en tant que système autonome est un moyen très faible d'assurer la protection contre les injections. Vous voulez toujours intégrer la protection dans le cadre de la construction de la requête, car il est important de ne pas dépendre de la vigilance - mais plutôt d'être forcé -par défaut- pour vous assurer que les attaques par injection SQL sont prises en compte.
Vous devez comprendre que mysql_real_escape_string ()
n'est pas une protection contre l'injection SQL. Il s'agit simplement d'un piratage destiné à réduire les dommages et à limiter les vecteurs d'attaque potentiels. Rien dans le nom ou dans la documentation officielle de mysql_real_escape_string ()
n'indique que cette fonction doit être utilisée pour empêcher l'injection SQL.
L'injection SQL se produit lorsque vous autorisez les données variables à faire partie de votre chaîne SQL. Lors de la création de SQL avec des données dynamiques, il est difficile de garantir que le SQL ne sera pas interrompu. Une entrée non autorisée pourrait interrompre le SQL et endommager vos données ou divulguer les informations.
SQL doit être constant, tout comme le reste de votre code. Autoriser la saisie de variables dans SQL est vulnérable à des attaques similaires à celles de eval ()
avec une entrée de variable. Vous ne pouvez jamais être sûr de ce qui est exécuté.
Pour éviter cela, vous devez vous assurer que le SQL est constant et ne change pas avec l'entrée. La solution à cela sont les espaces réservés. Vous avez paramétré les données et utilisez des espaces réservés aux endroits où les littéraux iront. Aucune fuite n'est nécessaire. Ensuite, vous préparez une telle déclaration et transmettez les données séparément pendant l'exécution. Le SQL est toujours le même et les données n'ont aucun moyen d'affecter le SQL et de modifier son comportement.
Les procédures stockées ne sont pas un moyen sûr d'empêcher les injections SQL. Similaire à échapper, il ne limite que les vecteurs d'attaque.