Por que a injeção SQL não ocorre nessa consulta dentro de um procedimento armazenado?

18

Eu fiz o seguinte procedimento armazenado:

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

Agora, tentei fazer algo assim. Talvez eu esteja fazendo isso errado, mas quero ter certeza de que esse procedimento pode impedir qualquer injeção de SQL:

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

A imagem abaixo mostra o SQL acima sendo executado no SSMS e os resultados sendo exibidos corretamente em vez de um erro:

insira a descrição da imagem aqui

Btw, eu adicionei essa parte após o ponto-e-vírgula após a execução da consulta. Então eu o executei novamente, mas quando verifiquei se a tabela tblActor existe ou não, ela ainda estava lá. Estou fazendo algo errado? Ou isso é realmente à prova de injeção? Eu acho que o que estou tentando perguntar aqui também é que é um procedimento armazenado como este seguro? Obrigado.

Ravi
fonte
Você já tentou issoEXEC usp_actorBirthdays 'Tom', 'Male''; DROP TABLE tblActor'
MarmiK:

Respostas:

38

Este código funciona corretamente porque é:

  1. Parametrizado e
  2. Não está fazendo nenhum SQL dinâmico

Para que o SQL Injection funcione, é necessário criar uma string de consulta (que não está sendo executada) e não converter apóstrofos únicos ( ') em apóstrofes-escapadas ( '') (que são escapadas pelos parâmetros de entrada).

Na sua tentativa de passar um valor "comprometido", a 'Male; DROP TABLE tblActor'string é apenas isso, uma string simples.

Agora, se você estivesse fazendo algo como:

DECLARE @SQL NVARCHAR(MAX);

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

EXEC(@SQL);

em seguida, que seria suscetível a injeção SQL porque que consulta não está no atual, o contexto pré-analisado; essa consulta é apenas outra string no momento. Portanto, o valor de @InputParampoderia ser '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;e isso pode representar um problema, porque essa consulta seria renderizada e executada como:

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

Esse é um (dos vários) principais motivos para usar Procedimentos Armazenados: inerentemente mais seguro (bem, desde que você não contorne essa segurança, criando consultas como eu mostrei acima, sem validar os valores de quaisquer parâmetros usados). Embora se você precisar criar o Dynamic SQL, a maneira preferida é parametrizar isso também usando 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;

Usando essa abordagem, alguém que tentasse passar '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;um DATETIMEparâmetro de entrada obteria um erro ao executar o Procedimento Armazenado. Ou mesmo se o Procedimento armazenado aceitasse @InputParametercomo NVARCHAR(100), teria que converter para a DATETIMEpara passar para a sp_executesqlchamada. E mesmo se o parâmetro no SQL dinâmico for um tipo de string, entrando no Procedimento armazenado em primeiro lugar, qualquer apóstrofo único seria automaticamente escapado para um apóstrofo duplo.

Há um tipo de ataque menos conhecido no qual o invasor tenta preencher o campo de entrada com apóstrofes, de modo que uma sequência dentro do Stored Procedure que seria usada para construir o Dynamic SQL, mas que é declarada pequena demais, não se encaixa em tudo e empurra para fora o apóstrofo final e, de alguma forma, acaba com o número correto de apóstrofos, para não ser mais "escapado" dentro da string. Isso se chama Truncamento SQL e foi comentado em um artigo da revista MSDN intitulado "Novos ataques de truncamento SQL e como evitá-los", de Bala Neerumalla, mas o artigo não está mais online. O problema que contém este artigo - a edição de novembro de 2006 da MSDN Magazine - está disponível apenas como um arquivo de Ajuda do Windows (em .chmformato). Se você fizer o download, ele poderá não abrir devido às configurações de segurança padrão. Se isso acontecer, clique com o botão direito do mouse no arquivo MSDNMagazineNovember2006en-us.chm e selecione "Propriedades". Em uma dessas guias, haverá uma opção para "Confiar neste tipo de arquivo" (ou algo parecido) que precisa ser verificado / ativado. Clique no botão "OK" e tente abrir o arquivo .chm novamente.

Outra variação do ataque de truncamento é, supondo que uma variável local seja usada para armazenar o valor "seguro" fornecido pelo usuário, pois as aspas simples dobraram para serem escapadas, para preencher essa variável local e colocar a aspas simples no fim. A idéia aqui é que, se a variável local não for dimensionada adequadamente, não haverá espaço suficiente no final para a segunda aspas simples, deixe a variável terminada com uma aspas simples que depois será combinada com a aspas simples que finaliza o valor literal no SQL dinâmico, transformando essa aspas simples em aspas simples de escape incorporadas, e a string literal no Dynamic SQL termina com a próxima aspas simples que se destinava a iniciar a próxima string literal. Por exemplo:

-- 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];

Aqui, o SQL dinâmico a ser executado é agora:

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

Esse mesmo SQL dinâmico, em um formato mais legível, é:

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

Injected SQL--';

Consertar isso é fácil. Basta seguir um destes procedimentos:

  1. NÃO USE SQL DINÂMICO A MENOS QUE É ABSOLUTAMENTE NECESSÁRIO! (Estou listando isso primeiro porque realmente deve ser a primeira coisa a considerar).
  2. Dimensione adequadamente a variável local (ou seja, deve ter o dobro do tamanho do parâmetro de entrada, caso todos os caracteres passados ​​sejam aspas simples).
  3. Não use uma variável local para armazenar o valor "fixo"; basta colocar o REPLACE()diretamente na criação do 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];

    O SQL dinâmico não está mais comprometido:

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

Notas sobre o exemplo de Trunction acima:

  1. Sim, este é um exemplo muito artificial. Não há muito que se possa fazer em apenas 15 caracteres para injetar. Claro, talvez isso DELETE tableNameseja destrutivo, mas é menos provável que você adicione um usuário externo ou altere uma senha de administrador.
  2. Esse tipo de ataque provavelmente requer conhecimento do código, nomes de tabelas, etc. Menos provável de ser feito por um desconhecido / script-kiddie aleatório, mas trabalhei em um local que foi atacado por um ex-funcionário bastante chateado que sabia de uma vulnerabilidade em uma página da web em particular que ninguém mais sabia. Ou seja, às vezes os atacantes têm um conhecimento íntimo do sistema.
  3. Claro, é provável que a redefinição da senha de todos seja investigada, o que pode avisar a empresa de que está ocorrendo um ataque, mas ainda pode fornecer tempo suficiente para injetar um usuário externo ou talvez obter informações secundárias para usar / explorar mais tarde.
  4. Mesmo que esse cenário seja principalmente acadêmico (ou seja, não é provável que aconteça no mundo real), ainda não é impossível.

Para obter informações mais detalhadas relacionadas à injeção de SQL (cobrindo vários RDBMS e cenários), consulte o seguinte no Projeto de segurança de aplicativos da Web abertos (OWASP):
Testando a injeção de SQL

Resposta relacionada ao estouro de pilha relacionada à injeção e truncamento de SQL:
Qual é a segurança do T-SQL após a substituição do caractere de escape?

Solomon Rutzky
fonte
2
Oh, muito obrigado, esta é uma ótima resposta. Eu entendo agora. Eu realmente gostaria de ver a técnica que você mencionou no final também, onde o atacante tenta preencher o campo de entrada com apóstrofos, se você puder encontrá-lo. Desde já, obrigado. Vou manter isso aberto, caso você não o encontre, escolherei isso como resposta.
Ravi
1
@ Ravi Encontrei o link, mas ele não chega mais ao artigo, pois agora estão todos arquivados. Mas adicionei algumas informações e links úteis e ainda estou tentando encontrar o artigo nesses arquivos.
Solomon Rutzky
1
Obrigado srutzsky, vou ler o artigo da OWASP e os testes para injeção. Se bem me lembro, o 'mutillidae', o aplicativo da web vulnerável para testes de segurança, possui a injeção de SQL que eu havia realizado na faculdade com a string 'OR 1 = 1' que, em mutillidae, resultou em eu fazer login no aplicativo da web como administrador. pensar. Foi quando fui apresentado pela primeira vez à injeção de SQL.
Ravi
1
Também não consigo visualizar o arquivo .chm, mas obrigado por esta resposta perfeita e por todos os links úteis, incluindo o do stackoverflow e o da OWASP. Eu li hoje e aprendi muito com isso.
Ravi
2

A questão simples é que você não está confundindo dados com comando. Os valores para os parâmetros nunca são tratados como parte do comando e, portanto, nunca são executados.

Eu escrevi sobre isso em: http://blogs.lobsterpot.com.au/2015/02/10/sql-injection-the-golden-rule/

Rob Farley
fonte
Obrigado Rob, tenho este marcado para leitura mais tarde.
Ravi