Comment un PreparedStatement évite ou empêche-t-il l'injection SQL?


122

Je sais que PreparedStatements évite / empêche l'injection SQL. Comment fait-il cela? La requête de formulaire finale construite à l'aide de PreparedStatements sera-t-elle une chaîne ou autre?


3
Techniquement, la spécification JDBC n'insiste pas sur le fait qu'il n'y a pas de failles d'injection SQL. Je ne connais aucun lecteur affecté.
Tom Hawtin - Tackline

3
@Jayesh Je suggère d'ajouter le contenu de votre blog comme réponse ici. La plupart des réponses indiquent simplement les différences entre la génération de requêtes SQL dynamiques et le stmt préparé. Ils n'abordent pas la question de savoir pourquoi les déclarations préparées fonctionnent mieux que votre blog.
Pavan Manjunath

1
Ajouté comme réponse, j'espère que cela aide.
Jayesh

Réponses:


78

Le problème avec l'injection SQL est qu'une entrée utilisateur est utilisée dans le cadre de l'instruction SQL. En utilisant des instructions préparées, vous pouvez forcer l'entrée utilisateur à être traitée comme le contenu d'un paramètre (et non comme une partie de la commande SQL).

Mais si vous n'utilisez pas l'entrée utilisateur comme paramètre pour votre instruction préparée, mais que vous créez plutôt votre commande SQL en joignant des chaînes ensemble, vous êtes toujours vulnérable aux injections SQL, même lorsque vous utilisez des instructions préparées.


1
Bien sûr, mais vous pouvez toujours coder en dur certains ou tous vos paramètres.
tangens

16
Exemple s'il vous plaît - Mais si vous n'utilisez pas l'entrée utilisateur comme paramètre pour votre instruction préparée, mais que vous construisez votre commande SQL en joignant des chaînes ensemble, vous êtes toujours vulnérable aux injections SQL même lorsque vous utilisez des instructions préparées.
david blaine

4
Les instructions préparées par FWIW ne sont pas une chose JDBC - elles sont une chose SQL. Vous pouvez préparer et exécuter des instructions préparées à partir d'une console SQL. PreparedStatement les prend simplement en charge depuis JDBC.
beldaz

198

Considérez deux façons de faire la même chose:

PreparedStatement stmt = conn.createStatement("INSERT INTO students VALUES('" + user + "')");
stmt.execute();

Ou

PreparedStatement stmt = conn.prepareStatement("INSERT INTO student VALUES(?)");
stmt.setString(1, user);
stmt.execute();

Si "utilisateur" provenait de l'entrée utilisateur et que l'entrée utilisateur était

Robert'); DROP TABLE students; --

Ensuite, dans un premier temps, vous seriez arrosé. Dans le second, vous seriez en sécurité et Little Bobby Tables serait inscrit dans votre école.


8
Donc, si j'ai bien compris, la requête du deuxième exemple qui sera exécutée serait en fait: INSERT INTO student VALUES ("Robert '); DROP TABLE student; -") - ou du moins quelque chose comme ça. Est-ce vrai?
Max

18
Non, dans le premier cas, vous obtiendrez cette déclaration. Dans le second, il insérerait "Robert '); DROP TABLE étudiants; -" dans la table utilisateur.
Paul Tomblin

3
C'est ce que je voulais dire, dans le deuxième exemple (celui "sûr"), la chaîne Robert '); Étudiants DROP TABLE; - sera enregistré dans le champ de la table des étudiants. Ai-je écrit autre chose? ;)
Max

7
Désolé, les citations imbriquées sont quelque chose que j'essaie d'éviter à cause d'une confusion comme celle-ci. C'est pourquoi j'aime PreparedStatements avec des paramètres.
Paul Tomblin

59
Petites tables Bobby. XD Great reference
Amalgovinus

129

Pour comprendre comment PreparedStatement empêche l'injection SQL, nous devons comprendre les phases d'exécution de la requête SQL.

1. Phase de compilation. 2. Phase d'exécution.

Chaque fois que le moteur de serveur SQL reçoit une requête, il doit passer par les phases ci-dessous,

Phases d'exécution des requêtes

  1. Phase d'analyse et de normalisation: dans cette phase, la syntaxe et la sémantique de la requête sont vérifiées. Il vérifie si la table de références et les colonnes utilisées dans la requête existent ou non. Il a également beaucoup d'autres tâches à faire, mais n'allons pas dans les détails.

  2. Phase de compilation: Dans cette phase, les mots-clés utilisés dans la requête tels que select, from, where, etc. sont convertis en un format compréhensible par la machine. C'est la phase où la requête est interprétée et l'action correspondante à entreprendre est décidée. Il a également beaucoup d'autres tâches à faire, mais n'allons pas dans les détails.

  3. Plan d'optimisation des requêtes: dans cette phase, l'arbre de décision est créé pour trouver les moyens d'exécuter la requête. Il détecte le nombre de façons dont la requête peut être exécutée et le coût associé à chaque manière d'exécuter la requête. Il choisit le meilleur plan pour exécuter une requête.

  4. Cache: le meilleur plan sélectionné dans le plan d'optimisation des requêtes est stocké dans le cache, de sorte qu'à chaque fois que la même requête arrive, elle n'a pas à passer à nouveau par la phase 1, la phase 2 et la phase 3. Lors de la prochaine requête, elle sera vérifiée directement dans le cache et récupérée à partir de là pour être exécutée.

  5. Phase d'exécution: dans cette phase, la requête fournie est exécutée et les données sont renvoyées à l'utilisateur en tant ResultSetqu'objet.

Comportement de l'API PreparedStatement aux étapes ci-dessus

  1. Les PreparedStatements ne sont pas des requêtes SQL complètes et contiennent des espaces réservés, qui au moment de l'exécution sont remplacés par des données réelles fournies par l'utilisateur.

  2. Chaque fois qu'un PreparedStatment contenant des espaces réservés est transmis au moteur SQL Server, il passe par les phases ci-dessous

    1. Phase d'analyse et de normalisation
    2. Phase de compilation
    3. Plan d'optimisation des requêtes
    4. Cache (les requêtes compilées avec des espaces réservés sont stockées dans le cache.)

UPDATE user set username =? et mot de passe =? O id =?

  1. La requête ci-dessus sera analysée, compilée avec des espaces réservés comme traitement spécial, optimisée et mise en cache. La requête à ce stade est déjà compilée et convertie dans un format compréhensible par la machine. Nous pouvons donc dire que la requête stockée dans le cache est pré-compilée et que seuls les espaces réservés doivent être remplacés par des données fournies par l'utilisateur.

  2. Désormais, au moment de l'exécution, lorsque les données fournies par l'utilisateur arrivent, la requête pré-compilée est extraite du cache et les espaces réservés sont remplacés par des données fournies par l'utilisateur.

PréparerStatementWorking

(N'oubliez pas qu'une fois les espaces réservés remplacés par les données utilisateur, la requête finale n'est pas compilée / interprétée à nouveau et le moteur SQL Server traite les données utilisateur comme des données pures et non comme un SQL qui doit être analysé ou compilé à nouveau; c'est la beauté de PreparedStatement. )

Si la requête n'a pas à passer à nouveau par la phase de compilation, les données remplacées sur les espaces réservés sont traitées comme des données pures et n'ont aucune signification pour le moteur SQL Server et exécutent directement la requête.

Remarque: C'est la phase de compilation après la phase d'analyse, qui comprend / interprète la structure de la requête et lui donne un comportement significatif. Dans le cas de PreparedStatement, la requête n'est compilée qu'une seule fois et la requête compilée mise en cache est récupérée en permanence pour remplacer les données utilisateur et s'exécuter.

En raison de la fonctionnalité de compilation unique de PreparedStatement, il est exempt d'attaque par injection SQL.

Vous pouvez obtenir une explication détaillée avec un exemple ici: https://javabypatel.blogspot.com/2015/09/how-prepared-statement-in-java-prevents-sql-injection.html


3
belle explication
Dheeraj Joshi

4
Littéralement la réponse la plus complète sur la pièce HOW it works
jouell

C'était très utile. Merci pour l'explication détaillée.
Inconnu

26

Le SQL utilisé dans un PreparedStatement est précompilé sur le pilote. À partir de là, les paramètres sont envoyés au pilote sous forme de valeurs littérales et non de parties exécutables de SQL; donc aucun SQL ne peut être injecté à l'aide d'un paramètre. Un autre effet secondaire bénéfique de PreparedStatements (précompilation + envoi de paramètres uniquement) est l'amélioration des performances lors de l'exécution de l'instruction plusieurs fois, même avec des valeurs différentes pour les paramètres (en supposant que le pilote prend en charge PreparedStatements) car le pilote n'a pas à effectuer l'analyse SQL et la compilation chacun heure à laquelle les paramètres changent.


Il n'est pas nécessaire de le mettre en œuvre comme ça, et je pense que ce n'est souvent pas le cas.
Tom Hawtin - tackline

4
En fait, le SQL est généralement précompilé sur la base de données. Autrement dit, un plan d'exécution est préparé sur la base de données. Lorsque vous exécutez la requête, le plan est exécuté avec ces paramètres. L'avantage supplémentaire est que la même instruction peut être exécutée avec des paramètres différents sans que le processeur de requêtes ait à compiler un nouveau plan à chaque fois.
beldaz

3

Je suppose que ce sera une chaîne. Mais les paramètres d'entrée seront envoyés à la base de données et les conversions / conversions appropriées seront appliquées avant la création d'une instruction SQL réelle.

Pour vous donner un exemple, il pourrait essayer de voir si CAST / Conversion fonctionne.
Si cela fonctionne, cela pourrait en créer une déclaration finale.

   SELECT * From MyTable WHERE param = CAST('10; DROP TABLE Other' AS varchar(30))

Essayez un exemple avec une instruction SQL acceptant un paramètre numérique.
Maintenant, essayez de transmettre une variable de chaîne (avec un contenu numérique acceptable comme paramètre numérique). Cela soulève-t-il une erreur?

Maintenant, essayez de passer une variable de chaîne (avec un contenu qui n'est pas acceptable en tant que paramètre numérique). Voyez ce qui se passe?


3

La déclaration préparée est plus sûre. Il convertira un paramètre dans le type spécifié.

Par exemple stmt.setString(1, user);, convertira le userparamètre en chaîne.

Supposons que le paramètre contienne une chaîne SQL contenant une commande exécutable : l'utilisation d'une instruction préparée ne le permettra pas.

Il ajoute un métacaractère (aka conversion automatique) à cela.

Cela le rend plus sûr.


2

Injection SQL: lorsque l'utilisateur a la possibilité de saisir quelque chose qui pourrait faire partie de l'instruction SQL

Par exemple:

Requête de chaîne = "INSÉRER DANS LES VALEURS DES ÉTUDIANTS ('" + utilisateur + "')"

lorsque l'utilisateur saisit «Robert»); Étudiants DROP TABLE; - "comme entrée, cela provoque une injection SQL

Comment une déclaration préparée empêche cela?

Requête de chaîne = "INSÉRER DANS LES VALEURS DES élèves ('" + ": nom" + "')"

parameters.addValue ("nom", utilisateur);

=> lorsque l'utilisateur saisit à nouveau «Robert»); Étudiants DROP TABLE; - «, la chaîne d'entrée est précompilée sur le pilote en tant que valeurs littérales et je suppose qu'elle peut être castée comme:

CAST («Robert»); Étudiants DROP TABLE; - 'AS varchar (30))

Donc à la fin, la chaîne sera littéralement insérée comme nom de la table.

http://blog.linguiming.com/index.php/2018/01/10/why-prepared-statement-avoids-sql-injection/


1
Si je ne me trompe pas, la partie CAST(‘Robert’);de CAST(‘Robert’); DROP TABLE students; –‘ AS varchar(30))se briserait, puis elle abandonnerait la table si c'était le cas. Cela arrête l'injection, donc je pense que l'exemple n'est tout simplement pas assez complet pour expliquer le scénario.
Héctor Álvarez

1

Affirmation préparée:

1) La précompilation et la mise en cache côté base de données de l'instruction SQL permettent une exécution globalement plus rapide et la possibilité de réutiliser la même instruction SQL par lots.

2) Prévention automatique des attaques par injection SQL par échappement intégré des guillemets et autres caractères spéciaux. Notez que cela nécessite que vous utilisiez l'une des méthodes SetXxx () PreparedStatement pour définir la valeur.


1

Comme expliqué dans cet article , le PreparedStatementseul ne vous aide pas si vous êtes toujours en train de concaténer des chaînes.

Par exemple, un attaquant non autorisé peut toujours effectuer les opérations suivantes:

  • appeler une fonction de veille pour que toutes vos connexions à la base de données soient occupées, rendant ainsi votre application indisponible
  • extraction de données sensibles de la base de données
  • contournement de l'authentification utilisateur

Non seulement SQL, mais même JPQL ou HQL peuvent être compromis si vous n'utilisez pas de paramètres de liaison.

En fin de compte, vous ne devez jamais utiliser la concaténation de chaînes lors de la création d'instructions SQL. Utilisez une API dédiée à cet effet:


1
Merci d'avoir souligné l'importance d'utiliser la liaison de paramètres, plutôt que PreparedStatement seul. Cependant, votre réponse semble impliquer que l'utilisation d'une API dédiée est nécessaire pour se protéger contre l'injection SQL. Puisque ce n'est pas le cas et que l'utilisation de PreparedStatement avec la liaison de paramètres fonctionne également, voudriez-vous reformuler?
Wild Pottok

-3

Dans les déclarations préparées, l'utilisateur est obligé de saisir des données en tant que paramètres. Si l'utilisateur entre des instructions vulnérables telles que DROP TABLE ou SELECT * FROM USERS, les données ne seront pas affectées car elles seront considérées comme des paramètres de l'instruction SQL


Même réponse que la réponse sélectionnée avec moins de précisions.
Julien Maret
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.