Sie sind auf Seite 1von 8

Avant propos▲

Au cours de cet article nous allons étudier l'utilisation de requêtes paramétrées dans le cadre
d'une application ASP.Net codée en VB.Net, il est évident que cela peut être étendu aux autres
types d'applications et langages .Net moyennant quelques petites adaptations mineures. Les
requêtes paramétrées avec SqlParameter (ce qui est ici le cas) fonctionnent avec SQL Server
mais aussi avec MSDE

1. Introduction▲
Une vulnérabilité des applications ASP.Net utilisant une base de données est le fait que
l'utilisateur puisse exécuter des commandes SQL alors que théoriquement cela devrait lui être
impossible. Ce type d'attaque peut potentiellement permettre à un utilisateur de compromettre
l'intégrité des données et de la base elle-même. Cette vulnérabilité est connue sous le nom
d'injection SQL. Ce problème de sécurité se pose, par exemple, lorsqu'un utilisateur rempli
des champs d'un formulaire dont le contenu sera concaténé directement à l'intérieur des
requêtes envoyées à la base de données.

2. Les attaques de type injection SQL▲


Pour vous montrer l'intérêt d'employer des requêtes SQL paramétrées, il est utile de voir à
quel point la sécurité des données peut être compromise si l'on n'applique pas quelques
principes simples.

Prenons, pour cela, un exemple concret. Imaginons que votre application contienne un
formulaire permettant de faire une recherche dans votre base de données en retournant les
enregistrements correspondants à des critères établis par l'utilisateur. De plus, admettons qu'un
champ permette à l'utilisateur d'entrer un texte au clavier pour déclarer à l'application ce qu'il
recherche. L'utilisateur normal va taper les mots clés en relation avec ce qu'il recherche, mais
là où il y a danger, c'est qu'un utilisateur malveillant va non pas taper de simples mots clés
mais des instructions SQL dans le but de modifier ou détruire vos données.

En effet, sans l'emploi de requêtes paramétrées, il y a de grandes chances que le code de la


requête dans votre application soit :

Sélectionnez
Select * From maTable Where monChamp =
'les_mots_clés_entrés_par_l'utilisateur' ;

Pour l'utilisateur malveillant souhaitant effacer des données c'est alors un jeu d'enfant… Dans
le champ de votre formulaire il va par exemple saisir des commandes SQL, ou encore
employer des apostrophes, ou même des points virgules permettant ainsi d'ajouter de
nouvelles instructions à la première requête transmise par l'application. Voici concrètement à
quoi va ressembler dans le code une telle attaque :

Dans votre application, vous utilisez une requête avec concaténation de ce type :
Sélectionnez
maRequete = "SELECT nom, prenom FROM maTable Where identifiant = '" &
maTextBox.Text & "'"

L'utilisateur malveillant va alors saisir dans maTextBox :

Sélectionnez
15' ; DELETE FROM maTable WHERE identifiant <>'';

La requête complète, dans votre application, va alors être :

Sélectionnez
maRequete = "SELECT nom, prenom FROM maTable Where identifiant = '15' ;
DELETE FROM maTable WHERE identifiant <>'';"

Cela va avoir pour conséquence directe d'effacer tous les enregistrements de votre table…
Cela bien entendu si le compte utilisé pour la requête n'est pas autorisé à faire seulement des
SELECT sur la table concernée, cela est malheureusement très souvent le cas pour des raisons
de commodité et de " qui peut le plus peut le moins "… ainsi l'on se débarrasse vite fait d'une
gestion fine et efficace des droits ; en utilisant tout le temps le compte SA pour faire de
simples SELECT…

Bien entendu, cela suppose également que l'utilisateur malveillant connaisse un minimum la
structure de votre base de données, mais il ne faut absolument pas sous estimer ce risque,
surtout que tout le monde a toujours tendance à avoir des structures similaires.

J'ai pris un exemple volontairement simpliste, mais il est clair que vu la richesse des
différentes versions du langage SQL employées avec les bases de données actuelles les
attaques peuvent être beaucoup plus subtiles.

Vous comprenez, désormais, à quel point votre application peut être en danger face une
attaque de type SQL Injection. Il faut donc proscrire la concaténation de chaînes dans les
requêtes SQL de vos applications ASP.Net et n'employer que des requêtes paramétrées ou des
procédures stockées.

3. Les requêtes paramétrées▲


Une requête paramétrée est, en fait, une commande SQL comme les autres sauf que l'on lui
passe une collection de paramètres dont le type et la taille ont été définis. Les paramètres dans
la requête sont nommés avec un @ comme premier caractère.

En voilà un exemple :
Sélectionnez
SELECT nom FROM Clients WHERE Nom = @nomClient
Il faut aussi, préalablement à l'exécution de la requête, définir la taille et le type des
paramètres, mais nous verrons cela un peu plus tard.

Attention : toutes les commandes T-SQL ne fonctionneront pas avec un paramètre, par
exemple "Use @ma_base" va soulever une exception, il faut absolument que le paramètre soit
utilisé dans une expression (par exemple xxx = @param ou zzz * @param). Ceci n'est
embêtant que dans quelques situations, et ne remet pas en question l'utilité des requêtes
paramétrées.

4. Les classes du .Net framework▲


Les classes qui sont concernées sont les suivantes :
SqlParameter est la classe qui permet de gérer ces paramètres, sa méthode la plus utilisée est
Add, en effet c'est elle qui permet d'ajouter les paramètres à nôtre requête.

Entre en jeu aussi les classes SqlCommand, SqlConnection qui permettent de définir des
commandes T-SQL et de se connecter à SQL Server, plus largement les classes concernées
sont celles de l'espace de noms System.Data.SqlClient. Cet espace de noms est le fournisseur
de données .Net pour Microsoft SQL Server®.

Remarque : pour utiliser toutes ces fonctionnalités il faut importer l'espace de noms cité
précédemment en ajoutant la ligne de code Imports System.Data.SqlClient, cet espace
contient la classe SqlParameter qui nous intéresse ici.

5. Les requêtes paramétrées dans le code▲


L'exemple, ci-dessous, vous montre ce qu'est une requête paramétrée, elle permet d'insérer des
données dans une base de données

Le code complet
Sélectionnez

Dim maConnexionSQLsrv As SqlConnection


Dim myCommand As SqlCommand
Dim maRequete, ConnexionSQL As String

'Récupération de la chaine de connexion au serveur de base de


données
ConnexionSQL =
ConfigurationSettings.AppSettings("ConnectionStringSQLsrv")
maConnexionSQLsrv = New SqlConnection(ConnexionSQL)

'Requete mise a jour de la table client


maRequete = "INSERT INTO [article] ( [idclient],[idarticle],
[prixarticle]) VALUES (@id_article, @prix_article,
@nom_article)"

'Création de la commande SQL


myCommand = New SqlCommand(maRequete, maConnexionSQLsrv)

'Création et décalartion des paramètres


With myCommand.Parameters
.Add(New SqlParameter("@id_article", SqlDbType.Int, 4))
.Add(New SqlParameter("@prix_article", SqlDbType.Float, 8))
.Add(New SqlParameter("@nom_article", SqlDbType.VarChar, 25))
End With

'Attribution des valeurs aux paramètres


With myCommand
.Parameters("@id_article").Value = CInt(maTextBox1.Text)
.Parameters("@prix_article").Value = CDbl(maTextBox2.Text)
.Parameters("@nom_article").Value = maTextBox3.Text
End With

Try

'Execution de la requête
myCommand.Connection.Open()
myCommand.ExecuteNonQuery()
myCommand.Connection.Close()

Catch ex As SqlException

Response.Write(ex.Message)

End Try

Maintenant, il ne suffit pas d'avoir ce code et de le copier, il faut surtout le comprendre. Il


n'est pas nécessaire d'expliquer les premières lignes en effet elles sont exactement les mêmes
que pour une requête non paramétrée. Là où cela diffère c'est lorsque l'on déclare les
paramètres.

Les paramètres sont, en fait, ajoutés à myCommand qui est un objet sqlCommand, c'est-à-dire
une chaîne d'instructions en Transact-SQL qui doit être exécutée par SQL Server. Pour ajouter
un paramètre à un objet sqlCommand, rien de plus simple, il suffit d'ajouter le code suivant :

Sélectionnez
maCommande.Parameters.Add(New SqlParameter("@mon_paramètre",
le_type_du_paramètre))

Si notre paramètre se nomme param1 et qu'il est de type Integer voilà ce que va donner notre
ligne :

Sélectionnez
maCommande.Parameters.Add(New SqlParameter("@param1", SqlDbType.Int, 4))

Les paramètres peuvent êtres de types différents, voilà une liste exhaustive des types existants :

Type Description
BigInt Entier signé 64 bits.
Array de type Byte. Flux de données binaires de longueur
Binary
fixe comptant entre 1 et 8 000 octets.
Valeur numérique non signée pouvant être égale à 0, 1 ou
Bit
une référence null (Nothing dans Visual Basic).
Flux de caractères non Unicode de longueur fixe comptant
Char
entre 1 et 8 000 caractères.
DateTime Données de date et d'heure dont la valeur est comprise
entre le 1er janvier 1753 et le 31 décembre 9999, avec une
précision de 3,33 millisecondes.
Valeur numérique fixe de précision et d'échelle comprise
Decimal
entre -10^38 -1 et 10^38 -1.
Nombre en virgule flottante compris entre -1,79E +308 et
Float
1,79E +308.
Image Array de type Byte. Flux de données binaires de longueur
variable comptant entre 0 et 2 31 -1 (ou 2 147 483 647)
octets.
Int Entier signé 32 bits.
Valeur monétaire comprise entre -2 63 (ou -922 337 203
685 477,5808) et 2 63 -1 (ou +922 337 203 685
Money
477,5807), avec une précision d'un dix millième d'unité
monétaire.
Flux de caractères Unicode de longueur fixe comptant
Nchar
entre 1 et 4 000 caractères.
Ntext Flux de données Unicode de longueur variable dont la
longueur maximale est égale à 2 30 - 1 (ou 1 073 741 823)
caractères.
Flux de caractères Unicode de longueur variable comptant
entre 1 et 4 000 caractères. Remarque : la conversion
implicite échoue si une chaîne compte plus de 4 000
NvarChar
caractères. Définissez l'objet de manière explicite lorsque
vous utilisez des chaînes comptant plus de 4 000
caractères.
Nombre en virgule flottante compris entre -3,40E +38 et
Real
3,40E +38.
SmallDateTime Données de date et d'heure dont la valeur est comprise
entre le 1er janvier 1900 et le 6 juin 2079, avec une
précision d'une minute.
SmallInt Entier signé 16 bits.
SmallMoney Valeur monétaire comprise entre -214 748,3648 et +214
748,3647, avec une précision d'un dix millième d'unité
monétaire.
Text Flux de données non Unicode de longueur variable dont la
longueur maximale est égale à 2 31 -1 (ou 2 147 483 647)
caractères.
Array de type Byte. Nombres binaires générés
automatiquement et garantis comme étant uniques dans
Timestamp une base de données. timestamp sert généralement de
mécanisme d'insertion d'informations de version dans les
lignes de tables. La taille de stockage est égale à 8 octets.
TinyInt Entier non signé 8 bits.
UniqueIdentifier GUID (Identificateur global unique).
Array de type Byte. Flux de données binaires de longueur
variable comptant entre 1 et 8 000 octets. Remarque : la
conversion implicite échoue si un tableau d'octets compte
VarBinary
plus de 8 000 octets. Définissez l'objet de manière
explicite lorsque vous utilisez des tableaux d'octets
comptant plus de 8 000 octets.
Flux de caractères non Unicode de longueur variable
VarChar
comptant entre 1 et 8 000 caractères.
Type de données spécial pouvant contenir des données
numériques, de chaîne, binaires ou de date, ainsi que les
Variant
valeurs Empty et Null SQL Server. Il s'agit du type
supposé.

Après avoir déterminé le nom et le type, il faut déterminer la taille. Pour les chaînes, la taille
détermine le nombre de caractères, sinon elle détermine sa taille en octets. Comme nous
pouvons le voir ci-dessous, notre chaîne VarChar pourra avoir une longueur maximale de 25
caractères.

Sélectionnez
.Add(New SqlParameter("@nom_article", SqlDbType.VarChar, 25))

Ou encore ici notre entier sera codé sur 4 octets.

Sélectionnez
.Add(New SqlParameter("@id_article", SqlDbType.Int, 4))

Maintenant que nous avons typé et dimensionné le paramètre, il faut lui attribuer une valeur,
ici nous récupérons le texte contenu dans maTextBox1.

Sélectionnez
myCommand.Parameters("@id_article").Value = CInt(maTextBox1.Text)
myCommand.Parameters("@prix_article").Value = CDbl(maTextBox2.Text)
myCommand.Parameters("@nom_article").Value = maTextBox3.Text

Nous voyons que pour @id_article et @prix_article, il a fallu réaliser un cast sur ce que nous
avons récupéré, en effet, nous récupérons deux chaînes de caractères que nous voulons
transformer respectivement en données de type entier et double. Ce qui explique l'emploi de
CInt() et CDbl()

Et enfin, la dernière étape est tout simplement l'exécution de la requête précédée de


l'ouverture de la connexion avec le serveur de base de données et suivie de sa fermeture.

Sélectionnez
myCommand.Connection.Open()
myCommand.ExecuteNonQuery()
myCommand.Connection.Close()

6. Les avantages et inconvénients▲


Les avantages :

 Sécurité : comme nous l'avons vu, c'est une excellente protection contre les attaques de
type SQL Injection.
 Rapidité : une requête paramétrée est plus rapide dans la majorité des cas qu'une
requête " standard " car les données sont compilées directement avec le code au lieu
d'être ajoutées dans la base de données au dernier moment juste avant l'exécution de la
requête.
 Lisibilité : une requête paramétrée est quand même beaucoup plus lisible qu'une
requête avec des caractères de concaténation dans tous les sens, surtout quand celle-ci
est complexe.

Les inconvénients :

Même si le fait d'employer des requêtes paramétrées est un atout très important au niveau de
la sécurité, il ne faut pourtant pas croire que cela est suffisant. Il est indispensable de
configurer correctement sa base de données, avec des comptes ayant des accès en adéquation
avec leurs besoins, par exemple ne pas utiliser le compte SA pour faire un SELECT sur une
table (par ailleurs, il ne faut jamais utiliser le compte SA dans une application, sauf nécessité
absolue).

De plus, même si nos paramètres sont typés, cela ne signifie pas qu'il y a un contrôle du type
avant l'exécution de la requête, en clair si dans un champ devant contenir un entier, un
utilisateur saisit 12,43, il va alors y avoir un plantage pur et simple. Il faut donc avoir une
gestion des erreurs qui soit efficace avec pourquoi pas, une vérification côté client (si le
navigateur le permet) et côté serveur des données. La validation de formulaire étant très bien
implémentée dans le Framework .Net, ces opérations ne sont pas difficiles à réaliser et
apportent sécurité et fiabilité à votre application.

Donc, pour résumer, il n'y a pas de réel inconvénient, le seul est que l'on pourrait croire que
l'emploi de requêtes paramétrées est suffisant, mais attention cela n'est pas le cas.

7. Conclusion▲
L'emploi de requêtes paramétrées ne signifie bien évidemment pas qu'il n'est plus nécessaire
de se préoccuper de la sécurité au niveau de la base de données elle-même ! Elle est toujours
aussi importante comme nous venons de le voir juste précédemment. Mais cependant nous
pouvons dire qu'au plan de la sécurité les requêtes paramétrées sont un atout important. La
concaténation de chaînes dans les requêtes SQL est à proscrire dès que cela vous est possible.

Ressources▲
Documentation relative à la classe SQLparameter : Cliquez ici

Documentation relative à l'espace de nom System.Data.SqlClient : Cliquez ici

Un grand merci à Freegreg, Morpheus et Bidou pour leur aide.