Pourquoi l'injection SQL ne se produit-elle pas sur cette requête dans une procédure stockée?

18

J'ai effectué la procédure stockée suivante:

ALTER PROCEDURE usp_actorBirthdays (@nameString nvarchar(100), @actorgender nvarchar(100))
AS
SELECT ActorDOB, ActorName FROM tblActor
WHERE ActorName LIKE '%' + @nameString + '%'
AND ActorGender = @actorgender

Maintenant, j'ai essayé de faire quelque chose comme ça. Je me trompe peut-être, mais je veux être sûr qu'une telle procédure peut empêcher toute injection SQL:

EXEC usp_actorBirthdays 'Tom', 'Male; DROP TABLE tblActor'

L'image ci-dessous montre le SQL ci-dessus en cours d'exécution dans SSMS et les résultats affichés correctement au lieu d'une erreur:

entrez la description de l'image ici

Btw, j'ai ajouté cette partie après le point-virgule après l'exécution de la requête. Ensuite, je l'ai exécuté à nouveau, mais quand j'ai vérifié pour voir si la table tblActor existe ou non, elle était toujours là. Est-ce que je fais quelque chose de mal? Ou est-ce vraiment anti-injection? Je suppose que ce que j'essaie de demander ici aussi, c'est qu'il s'agit d'une procédure stockée comme celle-ci? Je vous remercie.

Ravi
la source
Avez-vous essayé celaEXEC usp_actorBirthdays 'Tom', 'Male''; DROP TABLE tblActor'
MarmiK

Réponses:

38

Ce code fonctionne correctement car il est:

  1. Paramétré, et
  2. Ne pas faire de SQL dynamique

Pour que l'injection SQL fonctionne, vous devez créer une chaîne de requête (ce que vous ne faites pas) et ne pas traduire les apostrophes simples ( ') en apostrophes d'échappement ('' ) (ceux-ci sont échappés via les paramètres d'entrée).

Dans votre tentative de passer une valeur "compromise", la 'Male; DROP TABLE tblActor'chaîne est juste cela, une chaîne ordinaire.

Maintenant, si vous faisiez quelque chose comme:

DECLARE @SQL NVARCHAR(MAX);

SET @SQL = N'SELECT fields FROM table WHERE field23 = '
          + @InputParam;

EXEC(@SQL);

alors cela serait sensible à l'injection SQL car cette requête n'est pas dans le contexte pré-analysé actuel; cette requête n'est qu'une autre chaîne pour le moment. Ainsi, la valeur de @InputParampourrait être '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;et cela pourrait poser un problème car cette requête serait rendue et exécutée comme suit:

SELECT fields FROM table WHERE field23 = '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;

C'est l'une (parmi plusieurs) raisons principales d'utiliser les procédures stockées: intrinsèquement plus sécurisées (enfin, tant que vous ne contournez pas cette sécurité en créant des requêtes comme je l'ai montré ci-dessus sans valider les valeurs des paramètres utilisés). Bien que si vous avez besoin de construire Dynamic SQL, la méthode préférée est de le paramétrer également en utilisant sp_executesql:

DECLARE @SQL NVARCHAR(MAX);

SET @SQL = N'SELECT fields FROM table WHERE field23 = @SomeDate_tmp';

EXEC sp_executesql
  @SQL,
  N'SomeDate_tmp DATETIME',
  @SomeDate_tmp = @InputParam;

En utilisant cette approche, une personne tentant de transmettre '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;un DATETIMEparamètre d'entrée obtiendrait une erreur lors de l'exécution de la procédure stockée. Ou même si la procédure stockée acceptait @InputParametercomme NVARCHAR(100), elle devrait être convertie DATETIMEen a pour passer à cet sp_executesqlappel. Et même si le paramètre dans Dynamic SQL est un type chaîne, entrant dans la procédure stockée en premier lieu, toute apostrophe unique serait automatiquement échappée vers une apostrophe double.

Il existe un type d'attaque moins connu dans lequel l'attaquant essaie de remplir le champ d'entrée avec des apostrophes de sorte qu'une chaîne à l'intérieur de la procédure stockée qui serait utilisée pour construire le SQL dynamique mais qui est déclarée trop petite ne peut pas tout contenir et repousse l'apostrophe de fin et se retrouve en quelque sorte avec le nombre correct d'apostrophes afin de ne plus être "échappé" dans la chaîne. Cela s'appelle SQL Truncation et a été évoqué dans un article du magazine MSDN intitulé "Nouvelles attaques de troncature SQL et comment les éviter", par Bala Neerumalla, mais l'article n'est plus en ligne. Le problème contenant cet article - l' édition de novembre 2006 de MSDN Magazine - est uniquement disponible en tant que fichier d'aide Windows (en .chmformat). Si vous le téléchargez, il peut ne pas s'ouvrir en raison des paramètres de sécurité par défaut. Si cela se produit, faites un clic droit sur le fichier MSDNMagazineNovember2006en-us.chm et sélectionnez "Propriétés". Dans l'un de ces onglets, il y aura une option pour "Faire confiance à ce type de fichier" (ou quelque chose comme ça) qui doit être vérifiée / activée. Cliquez sur le bouton "OK", puis essayez à nouveau d'ouvrir le fichier .chm .

Une autre variante de l'attaque de troncature est, en supposant qu'une variable locale est utilisée pour stocker la valeur "sûre" fournie par l'utilisateur car elle a doublé les guillemets simples afin d'être échappée, de remplir cette variable locale et de placer la guillemet simple à la fin. L'idée ici est que si la variable locale n'est pas correctement dimensionnée, il n'y aura pas assez de place à la fin pour le deuxième guillemet simple, laissez la variable se terminant par un guillemet simple qui se combine ensuite avec le guillemet simple qui termine la valeur littérale dans le SQL dynamique, transformant ce guillemet simple de fin en un guillemet simple d'échappement incorporé, et le littéral de chaîne dans le SQL dynamique se termine ensuite par le guillemet simple suivant qui était destiné à commencer le littéral de chaîne suivant. Par exemple:

-- Parameters:
DECLARE @UserID      INT = 37,
        @NewPassword NVARCHAR(15) = N'Any Value ....''',
        @OldPassword NVARCHAR(15) = N';Injected SQL--';

-- Stored Proc:
DECLARE @SQL NVARCHAR(MAX),
        @NewPassword_fixed NVARCHAR(15) = REPLACE(@NewPassword, N'''', N''''''),
        @OldPassword_fixed NVARCHAR(15) = REPLACE(@OldPassword, N'''', N'''''');

SELECT @NewPassword AS [@NewPassword],
       REPLACE(@NewPassword, N'''', N'''''') AS [REPLACE output],
       @NewPassword_fixed AS [@NewPassword_fixed];
/*
@NewPassword          REPLACE output          @NewPassword_fixed
Any Value ....'       Any Value ....''        Any Value ....'
*/

SELECT @OldPassword AS [@OldPassword],
       REPLACE(@OldPassword, N'''', N'''''') AS [REPLACE output],
       @OldPassword_fixed AS [@OldPassword_fixed];
/*
@OldPassword          REPLACE output          @OldPassword_fixed
;Injected SQL--       ;Injected SQL--         ;Injected SQL--
*/

SET @SQL = N'UPDATE dbo.TableName SET [Password] = N'''
           + @NewPassword_fixed + N''' WHERE [TableNameID] = '
           + CONVERT(NVARCHAR(10), @UserID) + N' AND [Password] = N'''
           + @OldPassword_fixed + N''';';

SELECT @SQL AS [Injected];

Ici, le Dynamic SQL à exécuter est maintenant:

UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';

Ce même Dynamic SQL, dans un format plus lisible, est:

UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';

Injected SQL--';

Il est facile de résoudre ce problème. Effectuez simplement l'une des opérations suivantes:

  1. NE PAS UTILISER DYNAMIC SQL SAUF SI ABSOLUMENT NÉCESSAIRE! (J'énumère cela en premier parce que cela devrait vraiment être la première chose à considérer).
  2. Dimensionnez correctement la variable locale (c'est-à-dire qu'elle devrait être deux fois plus grande que le paramètre d'entrée, juste au cas où tous les caractères transmis seraient des guillemets simples).
  3. N'utilisez pas de variable locale pour stocker la valeur "fixe"; il suffit de mettre REPLACE()directement dans la création du Dynamic SQL:

    SET @SQL = N'UPDATE dbo.TableName SET [Password] = N'''
               + REPLACE(@NewPassword, N'''', N'''''') + N''' WHERE [TableNameID] = '
               + CONVERT(NVARCHAR(10), @UserID) + N' AND [Password] = N'''
               + REPLACE(@OldPassword, N'''', N'''''') + N''';';
    
    SELECT @SQL AS [No SQL Injection here];

    Le Dynamic SQL n'est plus compromis:

    UPDATE dbo.TableName SET [Password] = N'Any Value ....''' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';

Remarques sur l'exemple Trunction ci-dessus:

  1. Oui, c'est un exemple très artificiel. Il n'y a pas grand chose à faire en seulement 15 caractères à injecter. Bien sûr, peut-être un DELETE tableNameêtre destructeur, mais moins susceptible d'ajouter un utilisateur détourné ou de changer un mot de passe administrateur.
  2. Ce type d'attaque nécessite probablement la connaissance du code, des noms de table, etc. Moins susceptible d'être fait par un inconnu / script-kiddie aléatoire, mais j'ai travaillé à un endroit qui a été attaqué par un ancien employé plutôt contrarié qui connaissait une vulnérabilité dans une page Web particulière que personne d'autre n'était au courant. Cela signifie que les attaquants ont parfois une connaissance intime du système.
  3. Bien sûr, la réinitialisation du mot de passe de tout le monde fera probablement l'objet d'une enquête, ce qui pourrait indiquer à l'entreprise qu'une attaque se produit, mais cela pourrait encore donner suffisamment de temps pour injecter un utilisateur de porte dérobée ou peut-être obtenir des informations secondaires à utiliser / exploiter plus tard.
  4. Même si ce scénario est principalement académique (c'est-à-dire peu susceptible de se produire dans le monde réel), il n'est toujours pas impossible.

Pour des informations plus détaillées sur l'injection SQL (couvrant divers SGBDR et scénarios), veuillez consulter les éléments suivants du projet de sécurité des applications Web ouvertes (OWASP):
Test de l'injection SQL

Réponse de dépassement de pile associée à l'injection SQL et à la troncature SQL:
Dans quelle mesure T-SQL est-il sûr après avoir remplacé le caractère d'échappement?

Solomon Rutzky
la source
2
Oh, merci beaucoup, c'est une excellente réponse. Je comprends maintenant. J'aimerais vraiment voir la technique que vous avez mentionnée à la fin, où l'attaquant essaie de remplir le champ de saisie avec des apostrophes, si vous pouvez le trouver. Merci d'avance. Je vais garder cela ouvert, au cas où vous ne le trouveriez pas, je choisirai ceci comme réponse.
Ravi
1
@Ravi J'ai trouvé le lien mais il ne parvient plus à l'article car ils sont tous maintenant archivés. Mais j'ai ajouté quelques informations et liens utiles et j'essaie toujours de trouver l'article dans ces archives.
Solomon Rutzky
1
Merci srutzsky, je vais lire l'article OWASP et les tests d'injection. Si je me souviens bien, 'mutillidae', l'application web vulnérable pour les tests de sécurité, a une injection SQL que j'avais effectuée au collège avec la chaîne 'OR 1 = 1' qui, chez mutillidae, m'a conduit à me connecter à l'application web en tant qu'administrateur I pense. C'est à ce moment-là que j'ai découvert l'injection SQL.
Ravi
1
Je ne suis pas en mesure d'afficher le fichier .chm aussi, mais merci pour cette réponse parfaite et pour tous les liens utiles, y compris celui de stackoverflow et celui de OWASP. Je l'ai lu aujourd'hui et j'en ai beaucoup appris.
Ravi
2

La simple question est que vous ne confondez pas du tout les données avec la commande. Les valeurs des paramètres ne sont jamais traitées comme faisant partie de la commande et ne sont donc jamais exécutées.

J'ai blogué à ce sujet sur: http://blogs.lobsterpot.com.au/2015/02/10/sql-injection-the-golden-rule/

Rob Farley
la source
Merci Rob, j'ai celui-ci en signet pour lecture plus tard dans la journée.
Ravi