Injection de Code SQL

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.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Etes-vous un robot ? *Chargement du capcha...

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.