Este código funciona corretamente porque é:
- Parametrizado e
- 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 @InputParam
poderia 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 DATETIME
parâmetro de entrada obteria um erro ao executar o Procedimento Armazenado. Ou mesmo se o Procedimento armazenado aceitasse @InputParameter
como NVARCHAR(100)
, teria que converter para a DATETIME
para passar para a sp_executesql
chamada. 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:
- 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).
- 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).
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:
- Sim, este é um exemplo muito artificial. Não há muito que se possa fazer em apenas 15 caracteres para injetar. Claro, talvez isso
DELETE tableName
seja destrutivo, mas é menos provável que você adicione um usuário externo ou altere uma senha de administrador.
- 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.
- 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.
- 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?
EXEC usp_actorBirthdays 'Tom', 'Male''; DROP TABLE tblActor'