Une attaque par injection SQL consiste à insérer un code malveillant dans les chaînes transmises ultérieurement à une instance de SQL Server en vue de leur analyse et de leur exécution. Les procédures qui permettent de créer des instructions SQL doivent être vérifiées et analysées à la recherche d'éventuelles failles autorisant cette injection, car SQL Server exécute toutes les requêtes syntaxiquement correctes qu'il reçoit. Même les données paramétrées peuvent être manipulées par un pirate compétent et déterminé.
Les injections SQL prennent principalement la forme d'insertions directes de code dans les variables d'entrée utilisateur qui sont concaténées avec des commandes SQL et exécutées. Des attaques par injection moins directes insèrent un code malveillant dans les chaînes destinées à être stockées dans une table ou en tant que métadonnées. Lorsque les chaînes stockées sont ensuite concaténées dans une commande SQL dynamique, le code nuisible est exécuté.
Le processus d'injection consiste à terminer prématurément une chaîne de texte et à ajouter une nouvelle commande. Étant donné que la commande insérée peut avoir d'autres chaînes ajoutées préalablement à son exécution, l'attaquant termine la chaîne injectée par une marque de commentaire « -- ». Le texte qui vient à la suite est ignoré au moment de l'exécution.
Le script suivant montre un exemple d'injection SQL simple. Il crée une requête SQL en concaténant des chaînes codées en dur avec une chaîne entrée par l'utilisateur :
L'utilisateur est invité à entrer le nom d'une ville. Si l'utilisateur entre Redmond, la requête assemblée par le script ressemble à ce qui suit :
Le point-virgule « ; » indique la fin d'une requête et le début d'une autre. Le double tiret « -- » signifie que le reste de la ligne courante est un commentaire et qu'elle doit être ignorée. Si le code modifié est syntaxiquement correct, il sera exécuté par le serveur. Lorsque SQL Server exécute cette instruction, SQL Server va d'abord sélectionner tous les enregistrements OrdersTable où ShipCity est Redmond. Ensuite, SQL Server supprime OrdersTable.
Tant que le code SQL injecté est syntaxiquement correct, il n'est pas possible de détecter par programme cette modification. Par conséquent, vous devez valider toutes les entrées utilisateur et vérifier très attentivement le code qui exécute les commandes SQL construites dans le serveur utilisé. Les méthodes de codage conseillées sont détaillées dans les sections suivantes de cette rubrique.
Examen du code à la recherche d'injection SQL
Vous devez examiner tout le code qui appelle EXECUTE, EXEC ou sp_executesql. Vous pouvez utiliser des requêtes similaires à l'exemple suivant pour faciliter l'identification des procédures contenant ces instructions. Cette requête recherche les 1, 2, 3 ou 4 espaces après les mots EXECUTE ou EXEC.
SELECT object_Name(id) FROM syscomments
WHERE UPPER(text) LIKE '%EXECUTE
(%'
OR UPPER(text) LIKE '%EXECUTE (%'
OR UPPER(text) LIKE '%EXECUTE (%'
OR UPPER(text) LIKE '%EXECUTE (%'
OR UPPER(text) LIKE '%EXEC (%'
OR UPPER(text) LIKE '%EXEC (%'
OR UPPER(text) LIKE '%EXEC (%'
OR UPPER(text) LIKE '%EXEC (%'
OR UPPER(text) LIKE '%SP_EXECUTESQL%'
Enveloppement de paramètres avec QUOTENAME() et REPLACE()
Dans chaque procédure stockée sélectionnée, vérifiez que toutes les variables utilisées dans du Transact-SQL dynamique sont traitées correctement. Les données issues des paramètres d'entrée de la procédure stockée ou lues à partir d'une table doivent être enveloppées dans QUOTENAME() ou REPLACE(). Gardez à l'esprit que la valeur de @variable transmise à QUOTENAME() est de type sysname et sa longueur maximale de 128 caractères.
|
@variable
|
Wrapper recommandé
|
|---|
|
Nom d'un sécurisable
|
QUOTENAME(@variable)
|
|
Chaîne de ≤ 128 caractères
|
QUOTENAME(@variable, '''')
|
|
Chaîne de > 128 caractères
|
REPLACE(@variable,'''', '''''')
|
Lorsque vous utilisez cette technique, une instruction SET peut être révisée comme suit :
--Before:
SET @temp = N'select * from authors where au_lname='''
+ @au_lname + N''''
--After:
SET @temp = N'select * from authors where au_lname='''
+ REPLACE(@au_lname,'''','''''') + N''''
Injection activée par la troncature des données
Tout code Transact-SQL dynamique affecté à une variable sera tronqué s'il est d'une taille supérieure à la mémoire tampon allouée à cette variable. Un attaquant capable de forcer la troncature d'une instruction en transmettant des chaînes inhabituellement longues à une procédure stockée peut manipuler le résultat. Par exemple, la procédure stockée créée par le script suivant court un risque d'injection permis par la troncature.
CREATE PROCEDURE sp_MySetPassword
@loginname sysname,
@old sysname,
@new sysname
AS
-- Declare variable.
-- Note that the buffer here is only 200 characters long.
DECLARE @command varchar(200)
-- Construct the dynamic Transact-SQL.
-- In the following statement, we need a total of 154 characters
-- to set the password of 'sa'.
-- 26 for UPDATE statement, 16 for WHERE clause, 4 for 'sa', and 2 for
-- quotation marks surrounded by QUOTENAME(@loginname):
-- 200 – 26 – 16 – 4 – 2 = 154.
-- But because @new is declared as a sysname, this variable can only hold
-- 128 characters.
-- We can overcome this by passing some single quotation marks in @new.
SET @command= 'update Users set password=' + QUOTENAME(@new, '''') + ' where username=' + QUOTENAME(@loginname, '''') + ' AND password = ' + QUOTENAME(@old, '''')
-- Execute the command.
EXEC (@command)
GO
En transmettant 154 caractères dans une mémoire tampon de 128 caractères, un attaquant peut définir un nouveau mot de passe pour sa sans connaître l'ancien.
EXEC sp_MySetPassword 'sa', 'dummy', '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012'''''''''''''''''''''''''''''''''''''''''''''''''''
Pour cette raison, vous devez utiliser une mémoire tampon de grande taille pour une variable de commande ou exécuter directement le Transact-SQL dynamique à l'intérieur de l'instruction EXECUTE.
Troncature lors de l'utilisation de QUOTENAME(@variable, '''') et REPLACE()
Les chaînes renvoyées par QUOTENAME() et REPLACE() seront tronquées en mode silencieux si elles dépassent l'espace alloué. La procédure stockée créée dans l'exemple suivant illustre les conséquences possibles.
CREATE PROCEDURE sp_MySetPassword
@loginname sysname,
@old sysname,
@new sysname
AS
-- Declare variables.
DECLARE @login sysname
DECLARE @newpassword sysname
DECLARE @oldpassword sysname
DECLARE @command varchar(2000)
-- In the following statements, the data stored in temp variables
-- will be truncated because the buffer size of @login, @oldpassword,
-- and @newpassword is only 128 characters, but QUOTENAME() can return
-- up to 258 characters.
SET @login = QUOTENAME(@loginname, '''')
SET @oldpassword = QUOTENAME(@old, '''')
SET @newpassword = QUOTENAME(@new, '''')
-- Construct the dynamic Transact-SQL.
-- If @new contains 128 characters, then @newpassword will be '123... n
-- where n is the 127th character.
-- Because the string returned by QUOTENAME() will be truncated,
-- it can be made to look like the following statement:
-- UPDATE Users SET password ='1234. . .[127] WHERE username=' -- other stuff here
SET @command = 'UPDATE Users set password = ' + @newpassword
+ ' where username =' + @login + ' AND password = ' + @oldpassword;
-- Execute the command.
EXEC (@command)
GO
Par conséquent, l'instruction suivante va définir les mots de passe de tous les utilisateurs à la valeur qui a été transmise dans le code précédent.
EXEC sp_MyProc '--', 'dummy', '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678'
Vous pouvez forcer la troncature de chaîne en dépassant l'espace de mémoire tampon alloué lors de l'utilisation de REPLACE(). La procédure stockée créée dans l'exemple suivant illustre les conséquences possibles.
CREATE PROCEDURE sp_MySetPassword
@loginname sysname,
@old sysname,
@new sysname
AS
-- Declare variables.
DECLARE @login sysname
DECLARE @newpassword sysname
DECLARE @oldpassword sysname
DECLARE @command varchar(2000)
-- In the following statements, data will be truncated because
-- the buffers allocated for @login, @oldpassword and @newpassword
-- can hold only 128 characters, but QUOTENAME() can return
-- up to 258 characters.
SET @login = REPLACE(@loginname, '''', '''''')
SET @oldpassword = REPLACE(@old, '''', '''''')
SET @newpassword = REPLACE(@new, '''', '''''')
-- Construct the dynamic Transact-SQL.
-- If @new contains 128 characters, @newpassword will be '123...n
-- where n is the 127th character.
-- Because the string returned by QUOTENAME() will be truncated, it
-- can be made to look like the following statement:
-- UPDATE Users SET password='1234…[127] WHERE username=' -- other stuff here
SET @command= 'update Users set password = ''' + @newpassword + ''' where username='''
+ @login + ''' AND password = ''' + @oldpassword + '''';
-- Execute the command.
EXEC (@command)
GO
Comme avec QUOTENAME(), la troncature de chaîne par REPLACE() peut être évitée en déclarant des variables temporaires de taille suffisante pour tous les cas. Dans la mesure du possible, vous devez appeler QUOTENAME() ou REPLACE() directement à l'intérieur du Transact-SQL dynamique. Sinon, vous pouvez calculer la taille requise de la mémoire tampon de la manière suivante. Pour @outbuffer = QUOTENAME(@input), la taille de @outbuffer doit être 2*(len(@input)+1). . Lorsque vous utilisez REPLACE() et des apostrophes doubles, comme dans l'exemple précédent, une mémoire tampon de 2*len(@input) est suffisante.
Le calcul suivant couvre tous les cas :
While len(@find_string) > 0, required buffer size =
round(len(@input)/len(@find_string),0) * len(@new_string)
+ (len(@input) % len(@find_string))
Troncature lors de l'utilisation de QUOTENAME(@variable, ']')
La troncature peut se produire quand le nom d'un sécurisable SQL Server est transmis à des instructions utilisant la forme QUOTENAME(@variable, ']'). L'exemple suivant illustre cela.
CREATE PROCEDURE sp_MyProc
@schemaname sysname,
@tablename sysname,
AS
-- Declare a variable as sysname. The variable will be 128 characters.
-- But @objectname actually must allow for 2*258+1 characters.
DECLARE @objectname sysname
SET @objectname = QUOTENAME(@schemaname)+'.'+ QUOTENAME(@tablename)
-- Do some operations.
GO
Lorsque vous concaténez des valeurs de type sysname, vous devez utiliser des variables temporaires de taille suffisante pour contenir le maximum de 128 caractères par valeur. Dans la mesure du possible, appelez QUOTENAME() directement à l'intérieur du Transact-SQL dynamique. Sinon, vous pouvez calculer la taille requise de la mémoire tampon comme expliqué dans la section précédente.