Lorsque l’on parle de traitements réalisés en SQL dynamique, l’un des risques souvent présentés est l’injection de code. Voici un papier qui vous présentera un petit exemple simple d’injection de code SQL.
Tout d’abord, qu’est-ce que le SQL dynamique ? Le SQL dynamique consiste à construire une requête SQL sous forme de chaîne de caractères, et à ensuite demander au système d’exécuter le code indiqué dans cette chaîne. Ainsi, on peut suivant les conditions fonctionnelles venir ou pas rajouter telle jointure ou tel de condition.
Prenons un exemple dans lequel nous nous appuierons sur la base de données AdventureWorks (http://msftdbprodsamples.codeplex.com/).
Imaginons par exemple que l’on cherche à réaliser une procédure stockée qui recherche les commandes (disons par exemple le contenu de la table Sales.SalesOrderHeader) suivant la ville (colonne City de la table Person.Address), la province (colonne Name de Person.StateProvince) et le pays (colonne Name de la table Person.CountryRegion). Toutefois, chacun des critères pourra être facultatif.
Commençons par implémenter complètement une procédure stockée de recherche qui intègre tous les paramètres.
CREATE PROCEDURE ps_Recherche1 (@City nvarchar(30) = Null, @StateProvinceName nvarchar(50) = Null, @CountryRegionName nvarchar(50) = Null) AS BEGIN SELECT soh.* FROM Sales.SalesOrderHeader soh JOIN Sales.CustomerAddress cua on cua.CustomerId=soh.CustomerId JOIN Person.[Address] adr on adr.AddressID=cua.AddressID JOIN Person.StateProvince sp on sp.StateProvinceId=adr.StateProvinceId JOIN Person.CountryRegion cr on cr.CountryRegionCode=sp.CountryRegionCode WHERE (@City is null or @City=adr.City) AND (@StateProvinceName is null or @StateProvinceName=sp.Name) AND (@CountryRegionName is null or @CountryRegionName=cr.Name) END
Lors d’un appel de la procédure avec seulement la paramètre @City précisé, nous nous apercevons que le plan d’exécution effectue tout de même les jointures sur les tables Person.StateProvince et Person.CountryRegion, alors que nos paramètres d’entrée de procédure nous indiquent que nous n’avons pas de critère à vérifier sur ces tables.
exec ps_Recherche1 @City='Bothell'
Le rôle du SQL dynamique est d’adapter l’instruction aux paramètres d’entrée. Pour cela, la requête est construite sous forme de chaîne de caractères, et c’est cette chaîne qui est ensuite exécutée.
CREATE PROCEDURE ps_Recherche2 (@City nvarchar(30) = Null, @StateProvinceName nvarchar(50) = Null, @CountryRegionName nvarchar(50) = Null) AS BEGIN declare @requete nvarchar(max) -- Partie fixe select @requete = 'SELECT soh.* FROM Sales.SalesOrderHeader soh JOIN Sales.CustomerAddress cua on cua.CustomerId=soh.CustomerId ' -- Jointures conditionnelles if coalesce(@City,@StateProvinceName,@CountryRegionName) is not null select @requete+='JOIN Person.[Address] adr on adr.AddressID=cua.AddressID ' if isnull(@StateProvinceName,@CountryRegionName) is not null select @requete+='JOIN Person.StateProvince sp on sp.StateProvinceId=adr.StateProvinceId ' if @CountryRegionName is not null select @requete+='JOIN Person.CountryRegion cr on cr.CountryRegionCode=sp.CountryRegionCode ' -- Critères select @requete+='WHERE 1=1 ' if @City is not null select @requete+='AND adr.City=''' + @City + ''' ' if @StateProvinceName is not null select @requete+='AND sp.Name=''' + @StateProvinceName + ''' ' if @CountryRegionName is not null select @requete+='AND cr.Name=''' + @CountryRegionName + ''' ' -- Lancement de la requête exec sp_executesql @requete END
Et cette fois-ci, lorsque notre critère ne concerne que la ville, les tables inutilisées ne font pas partie du plan d’exécution.
exec ps_Recherche2 @City='Bothell'
A noter qu’une autre syntaxe d’usage de sp_executesql est disponible, s’appuyant sur une notion de paramètres. Et même si elle est beaucoup plus sûre et recommandée (nous le verrons plus bas), elle n’est pas toujours utilisée et l’inclusion du contenu des paramètres directement dans la chaîne de requête est encore souvent utilisée.
Voici donc pour l’explication de la notion de SQL dynamique. En apparence, cela semble donc parfait, permettant à la procédure d’optimiser les jointures et ainsi les performances d’exécution en fonction des paramètres d’entrée.
Mais revers de la médaille, cela peut en fait représenter une porte d’entrée dérobée pour des personnes mal intentionnées.
Prenons ainsi une procédure stockée qui utiliserait du SQL dynamique pour récupérer la contenu de la table Contact pour une personne fournissant ses noms, prénoms et mot de passe.
create procedure Recherche3 (@Nom nvarchar(50), @Prenom nvarchar(50), @Pwd varchar(10)) AS BEGIN declare @requete nvarchar(max) if @Nom is null or @Prenom is null or @Pwd is null BEGIN raiserror('Paramètres manquants',11,1) return END select @requete = 'SELECT * from Person.Contact WHERE 1=1 ' select @requete += 'AND LastName=''' + @Nom + ''' ' select @requete += 'AND FirstName=''' + @Prenom + ''' ' select @requete += 'AND PasswordSalt=''' + @Pwd + ''' ' exec sp_executesql @requete END
Lorsque nous l’interrogeons, il faut que les paramètres soient justes, sinon la ligne d’identification n’est pas renvoyée.
Mais néanmoins, un petit tour de passe-passe peut nous permettre d’obtenir à moindres frais beaucoup plus d’informations que ce à quoi nous devrions avoir droit…
En effet, cette chaîne un peu bizarre au niveau du mot de passe nous donne une requête assez particulière…
Voici de que l’on appelle injection de code SQL. Ce qui était censé être des données au milieu d’une chaîne de caractères vient en fait prendre pleinement un rôle syntaxique au milieu de l’instruction SQL, et en dévie le résultat selon les souhaits de la personne malintentionnée.
Pour cet exemple simple, plusieurs techniques de contournements sont possible (doubler les quottes dans les chaînes reçues, passer par sp_executesql avec des paramètres, …). Mais d’une manière générale, retenez qu’il convient toujours, lorsque l’on s’appuie sur du SQL dynamique, de bien penser aux risques d’injection de code.