{"id":242,"date":"2011-04-14T00:00:35","date_gmt":"2011-04-14T00:00:35","guid":{"rendered":"http:\/\/www.sqlserver.fr\/blog\/?p=242"},"modified":"2026-05-02T14:31:22","modified_gmt":"2026-05-02T12:31:22","slug":"injection-de-code-sql","status":"publish","type":"post","link":"https:\/\/www.sqlserver.fr\/blog\/injection-de-code-sql\/","title":{"rendered":"Injection de Code SQL"},"content":{"rendered":"<p>Lorsque l\u2019on parle de traitements r\u00e9alis\u00e9s en SQL dynamique, l\u2019un des risques souvent pr\u00e9sent\u00e9s est l\u2019injection de code. Voici un papier qui vous pr\u00e9sentera un petit exemple simple d\u2019injection de code SQL.<!--more--><\/p>\n<p>Tout d\u2019abord, qu\u2019est-ce que le SQL dynamique ? Le SQL dynamique consiste \u00e0 construire une requ\u00eate SQL sous forme de cha\u00eene de caract\u00e8res, et \u00e0 ensuite demander au syst\u00e8me d\u2019ex\u00e9cuter le code indiqu\u00e9 dans cette cha\u00eene. Ainsi, on peut suivant les conditions fonctionnelles venir ou pas rajouter telle jointure ou tel de condition.<\/p>\n<p>Prenons un exemple dans lequel nous nous appuierons sur la base de donn\u00e9es AdventureWorks (<a href=\"https:\/\/github.com\/Microsoft\/sql-server-samples\/releases\/tag\/adventureworks\">http:\/\/msftdbprodsamples.codeplex.com\/<\/a>).<\/p>\n<p style=\"text-align: center;\"><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/Schema1.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-260\" title=\"Schema\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/Schema1.jpg\" alt=\"\" width=\"620\" height=\"531\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/Schema1.jpg 768w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/Schema1-300x257.jpg 300w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><\/p>\n<p>Imaginons par exemple que l\u2019on cherche \u00e0 r\u00e9aliser une proc\u00e9dure stock\u00e9e 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\u00e8res pourra \u00eatre facultatif.<br \/>\nCommen\u00e7ons par impl\u00e9menter compl\u00e8tement une proc\u00e9dure stock\u00e9e de recherche qui int\u00e8gre tous les param\u00e8tres.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">CREATE PROCEDURE ps_Recherche1 (@City nvarchar(30) = Null,\r\n\t\t\t\t\t\t\t\t@StateProvinceName nvarchar(50) = Null,\r\n\t\t\t\t\t\t\t\t@CountryRegionName nvarchar(50) = Null)\r\nAS\r\nBEGIN\r\nSELECT soh.*\r\nFROM Sales.SalesOrderHeader soh\r\n\tJOIN Sales.CustomerAddress cua on cua.CustomerId=soh.CustomerId\r\n\tJOIN Person.[Address] adr on adr.AddressID=cua.AddressID\r\n\tJOIN Person.StateProvince sp on sp.StateProvinceId=adr.StateProvinceId\r\n\tJOIN Person.CountryRegion cr on cr.CountryRegionCode=sp.CountryRegionCode\r\nWHERE (@City is null or @City=adr.City)\r\n\tAND (@StateProvinceName is null or @StateProvinceName=sp.Name)\r\n\tAND (@CountryRegionName is null or @CountryRegionName=cr.Name)\r\nEND<\/pre>\n<p>Lors d\u2019un appel de la proc\u00e9dure avec seulement la param\u00e8tre @City pr\u00e9cis\u00e9, nous nous apercevons que le plan d\u2019ex\u00e9cution effectue tout de m\u00eame les jointures sur les tables Person.StateProvince et Person.CountryRegion, alors que nos param\u00e8tres d\u2019entr\u00e9e de proc\u00e9dure nous indiquent que nous n\u2019avons pas de crit\u00e8re \u00e0 v\u00e9rifier sur ces tables.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">exec ps_Recherche1 @City='Bothell'<\/pre>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/PlanComplet.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-244\" title=\"PlanComplet\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/PlanComplet-1024x357.jpg\" alt=\"\" width=\"620\" height=\"216\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/PlanComplet-1024x357.jpg 1024w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/PlanComplet-300x104.jpg 300w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/PlanComplet.jpg 1391w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><br \/>\nLe r\u00f4le du SQL dynamique est d\u2019adapter l\u2019instruction aux param\u00e8tres d\u2019entr\u00e9e. Pour cela, la requ\u00eate est construite sous forme de cha\u00eene de caract\u00e8res, et c\u2019est cette cha\u00eene qui est ensuite ex\u00e9cut\u00e9e.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">CREATE PROCEDURE ps_Recherche2 (@City nvarchar(30) = Null,\r\n\t\t\t\t\t\t\t\t@StateProvinceName nvarchar(50) = Null,\r\n\t\t\t\t\t\t\t\t@CountryRegionName nvarchar(50) = Null)\r\nAS\r\nBEGIN\r\n\tdeclare @requete nvarchar(max)\r\n\r\n\t-- Partie fixe\r\n\tselect @requete = 'SELECT soh.*\r\n\t\tFROM Sales.SalesOrderHeader soh\r\n\t\tJOIN Sales.CustomerAddress cua on cua.CustomerId=soh.CustomerId\r\n\t\t'\r\n\r\n\t-- Jointures conditionnelles\r\n\tif coalesce(@City,@StateProvinceName,@CountryRegionName) is not null\r\n\t\tselect @requete+='JOIN Person.[Address] adr on adr.AddressID=cua.AddressID\r\n\t\t'\r\n\tif isnull(@StateProvinceName,@CountryRegionName) is not null\r\n\t\tselect @requete+='JOIN Person.StateProvince sp on sp.StateProvinceId=adr.StateProvinceId\r\n\t\t'\r\n\tif @CountryRegionName is not null\r\n\t\tselect @requete+='JOIN Person.CountryRegion cr on cr.CountryRegionCode=sp.CountryRegionCode\r\n\t\t'\r\n\r\n\t-- Crit\u00e8res\r\n\tselect @requete+='WHERE 1=1 '\r\n\r\n\tif @City is not null\r\n\t\tselect @requete+='AND adr.City=''' + @City + ''' '\r\n\r\n\tif @StateProvinceName is not null\r\n\t\tselect @requete+='AND sp.Name=''' + @StateProvinceName + ''' '\r\n\r\n\tif @CountryRegionName is not null\r\n\t\tselect @requete+='AND cr.Name=''' + @CountryRegionName + ''' '\r\n\r\n\t-- Lancement de la requ\u00eate\r\n\texec sp_executesql @requete\r\n\r\nEND<\/pre>\n<p>Et cette fois-ci, lorsque notre crit\u00e8re ne concerne que la ville, les tables inutilis\u00e9es ne font pas partie du plan d\u2019ex\u00e9cution.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">exec ps_Recherche2 @City='Bothell'<\/pre>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/PlanReduit.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-245\" title=\"PlanReduit\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/PlanReduit-1024x339.jpg\" alt=\"\" width=\"620\" height=\"205\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/PlanReduit-1024x339.jpg 1024w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/PlanReduit-300x99.jpg 300w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/PlanReduit.jpg 1035w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><br \/>\nA noter qu\u2019une autre syntaxe d\u2019usage de sp_executesql est disponible, s\u2019appuyant sur une notion de param\u00e8tres. Et m\u00eame si elle est beaucoup plus s\u00fbre et recommand\u00e9e (nous le verrons plus bas), elle n\u2019est pas toujours utilis\u00e9e et l\u2019inclusion du contenu des param\u00e8tres directement dans la cha\u00eene de requ\u00eate est encore souvent utilis\u00e9e.<\/p>\n<p>Voici donc pour l\u2019explication de la notion de SQL dynamique. En apparence, cela semble donc parfait, permettant \u00e0 la proc\u00e9dure d\u2019optimiser les jointures et ainsi les performances d\u2019ex\u00e9cution en fonction des param\u00e8tres d\u2019entr\u00e9e.<br \/>\nMais revers de la m\u00e9daille, cela peut en fait repr\u00e9senter une porte d\u2019entr\u00e9e d\u00e9rob\u00e9e pour des personnes mal intentionn\u00e9es.<br \/>\nPrenons ainsi une proc\u00e9dure stock\u00e9e qui utiliserait du SQL dynamique pour r\u00e9cup\u00e9rer la contenu de la table Contact pour une personne fournissant ses noms, pr\u00e9noms et mot de passe.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">create procedure Recherche3 (@Nom nvarchar(50),\r\n\t\t\t\t\t\t\t@Prenom nvarchar(50),\r\n\t\t\t\t\t\t\t@Pwd varchar(10))\r\nAS\r\nBEGIN\r\n\tdeclare @requete nvarchar(max)\r\n\tif @Nom is null or @Prenom is null or @Pwd is null\r\n\tBEGIN\r\n\t\traiserror('Param\u00e8tres manquants',11,1)\r\n\t\treturn\r\n\tEND\r\n\tselect @requete = 'SELECT * from Person.Contact WHERE 1=1 '\r\n\tselect @requete += 'AND LastName=''' + @Nom + ''' '\r\n\tselect @requete += 'AND FirstName=''' + @Prenom + ''' '\r\n\tselect @requete += 'AND PasswordSalt=''' + @Pwd + ''' '\r\n\r\n\texec sp_executesql @requete\r\n\r\nEND<\/pre>\n<p style=\"text-align: left;\">Lorsque nous l\u2019interrogeons, il faut que les param\u00e8tres soient justes, sinon la ligne d\u2019identification n\u2019est pas renvoy\u00e9e.<br \/>\n<a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/Resultat1.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-246\" title=\"Resultat1\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/Resultat1-1024x221.jpg\" alt=\"\" width=\"620\" height=\"133\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/Resultat1-1024x221.jpg 1024w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/Resultat1-300x64.jpg 300w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/Resultat1.jpg 1401w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><br \/>\nMais n\u00e9anmoins, un petit tour de passe-passe peut nous permettre d\u2019obtenir \u00e0 moindres frais beaucoup plus d\u2019informations que ce \u00e0 quoi nous devrions avoir droit\u2026<br \/>\n<a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/Resultat2.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-247\" title=\"Resultat2\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/Resultat2-1024x320.jpg\" alt=\"\" width=\"620\" height=\"193\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/Resultat2-1024x320.jpg 1024w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/Resultat2-300x93.jpg 300w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/Resultat2.jpg 1395w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><br \/>\nEn effet, cette cha\u00eene un peu bizarre au niveau du mot de passe nous donne une requ\u00eate assez particuli\u00e8re\u2026<br \/>\n<a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/PlanScan.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-248\" title=\"PlanScan\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/PlanScan.jpg\" alt=\"\" width=\"620\" height=\"94\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/PlanScan.jpg 957w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/PlanScan-300x45.jpg 300w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><br \/>\nVoici de que l\u2019on appelle injection de code SQL. Ce qui \u00e9tait cens\u00e9 \u00eatre des donn\u00e9es au milieu d\u2019une cha\u00eene de caract\u00e8res vient en fait prendre pleinement un r\u00f4le syntaxique au milieu de l\u2019instruction SQL, et en d\u00e9vie le r\u00e9sultat selon les souhaits de la personne malintentionn\u00e9e.<br \/>\nPour cet exemple simple, plusieurs techniques de contournements sont possible (doubler les quottes dans les cha\u00eenes re\u00e7ues, passer par sp_executesql avec des param\u00e8tres, \u2026). Mais d\u2019une mani\u00e8re g\u00e9n\u00e9rale, retenez qu\u2019il convient toujours, lorsque l\u2019on s\u2019appuie sur du SQL dynamique, de bien penser aux risques d\u2019injection de code.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Lorsque l\u2019on parle de traitements r\u00e9alis\u00e9s en SQL dynamique, l\u2019un des risques souvent pr\u00e9sent\u00e9s est l\u2019injection de code. Voici un papier qui vous pr\u00e9sentera un petit exemple simple d\u2019injection de code SQL.<\/p>\n","protected":false},"author":7,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-242","post","type-post","status-publish","format-standard","hentry","category-article_sql"],"_links":{"self":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts\/242","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/comments?post=242"}],"version-history":[{"count":29,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts\/242\/revisions"}],"predecessor-version":[{"id":1966,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts\/242\/revisions\/1966"}],"wp:attachment":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/media?parent=242"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/categories?post=242"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/tags?post=242"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}