Posso proteger contra injeção de SQL escapando aspas simples e em torno da entrada do usuário com aspas simples?

139

Percebo que as consultas SQL parametrizadas são a maneira ideal de higienizar a entrada do usuário ao criar consultas que contenham entrada do usuário, mas estou me perguntando o que há de errado em receber entradas do usuário e escapar de aspas simples e cercar toda a cadeia de caracteres com aspas simples. Aqui está o código:

sSanitizedInput = "'" & Replace(sInput, "'", "''") & "'"

As aspas simples inseridas pelo usuário são substituídas por aspas duplas, o que elimina a capacidade do usuário de finalizar a sequência, para que qualquer outra coisa que eles digitem, como ponto-e-vírgula, sinais de porcentagem etc. fará parte da sequência e não é realmente executado como parte do comando.

Estamos usando o Microsoft SQL Server 2000, para o qual acredito que as aspas simples são o único delimitador de strings e a única maneira de escapar do delimitador de strings, portanto, não há como executar nada que o usuário digitar.

Não vejo nenhuma maneira de iniciar um ataque de injeção de SQL contra isso, mas percebo que, se fosse tão à prova de balas quanto me parece, alguém já teria pensado nisso e seria uma prática comum.

O que há de errado com este código? Existe uma maneira de obter um ataque de injeção de SQL além dessa técnica de higienização? Exemplo de entrada do usuário que explora essa técnica seria muito útil.


ATUALIZAR:

Ainda não conheço nenhuma maneira de iniciar efetivamente um ataque de injeção de SQL contra esse código. Algumas pessoas sugeriram que uma barra invertida escaparia de uma aspas simples e deixaria a outra finalizar a string para que o restante da string fosse executado como parte do comando SQL, e eu percebo que esse método funcionaria para injetar SQL no um banco de dados MySQL, mas no SQL Server 2000 a única maneira (que eu consegui encontrar) de escapar de uma aspas simples é com outra aspas simples; barras invertidas não o farão.

E, a menos que haja uma maneira de interromper o escape da aspas simples, nada do restante da entrada do usuário será executado porque tudo será considerado como uma sequência contígua.

Entendo que existem maneiras melhores de higienizar as entradas, mas estou realmente mais interessado em saber por que o método que forneci acima não funcionará. Se alguém souber de alguma maneira específica de montar um ataque de injeção SQL contra esse método de higienização, eu adoraria vê-lo.

Patrick
fonte
17
@BryanH Admitir não entender como a sabedoria comumente aceita se aplica a um caso específico e pedir um exemplo sobre esse caso específico não é arrogância, é humildade. Ficar aborrecido quando alguém pede um exemplo de por que a sabedoria comumente aceita está correta, por outro lado, pode parecer arrogante. Raciocinar com exemplos específicos geralmente é uma ótima maneira de investigar e aprender. O modo como o OP lidou com essa dúvida foi muito útil para minha compreensão do assunto, especialmente quando ele explicou a resposta que encontrou.
SantiBailors
@patrik Acabei de descobrir isso, pois estou trabalhando no mesmo pedaço de código, mas tentando escapar da string e aninhar uma consulta. Você já descobriu isso?
precisa saber é o seguinte
1
@ 3therk1ll é melhor não tentar, você é melhor fora de usar SQL parametrizado: blog.codinghorror.com/...
Patrick
@ Patrick, estou me aproximando do ponto de vista dos atacantes!
precisa saber é o seguinte

Respostas:

87

Primeiro de tudo, é apenas uma prática ruim. A validação de entrada é sempre necessária, mas também é sempre duvidosa.
Pior ainda, a validação da lista negra é sempre problemática, é muito melhor definir explícita e estritamente quais valores / formatos você aceita. É certo que isso nem sempre é possível - mas, em certa medida, sempre deve ser feito.
Alguns trabalhos de pesquisa sobre o assunto:

A questão é que qualquer lista negra que você faça (e listas de permissões muito permissivas) pode ser ignorada. O último link para o meu artigo mostra situações em que até mesmo as aspas podem ser contornadas.

Mesmo que essas situações não se apliquem a você, ainda é uma má idéia. Além disso, a menos que seu aplicativo seja trivialmente pequeno, você terá que lidar com manutenção e, talvez, com uma certa quantidade de governança: como garantir que tudo seja feito corretamente, em todos os lugares o tempo todo?

A maneira correta de fazer isso:

  • Validação da lista de permissões: tipo, comprimento, formato ou valores aceitos
  • Se você deseja entrar na lista negra, vá em frente. O escape de aspas é bom, mas dentro do contexto das outras atenuações.
  • Use objetos Command e Parameter para preparar e validar
  • Chame apenas consultas parametrizadas.
  • Melhor ainda, use procedimentos armazenados exclusivamente.
  • Evite usar SQL dinâmico e não use concatenação de strings para criar consultas.
  • Se você estiver usando SPs, também poderá limitar as permissões no banco de dados para executar apenas os SPs necessários e não acessar tabelas diretamente.
  • você também pode verificar facilmente se toda a base de código acessa apenas o banco de dados por meio de SPs ...
Ávido
fonte
2
Quando usada corretamente, a concatenação dinâmica de SQL e string pode ser usada com segurança com consultas parametrizadas (ou seja, com em sp_executesqlvez de EXEC). Ou seja, você pode gerar dinamicamente sua instrução SQL, desde que nenhum texto concatenado venha do usuário. Isso também traz benefícios de desempenho; sp_executesqlsuporta cache.
18710 Brian
2
@ Brian, bem duh :). Mas, na realidade, com que frequência você vê programadores fazer isso? Além disso, o cenário típico em que o SQL dinâmico é "necessário" requer a entrada do usuário como parte da consulta (supostamente). Se você pudesse executar sp_executesql, normalmente não precisaria do sql dinâmico.
AviD 18/07/10
Finalmente, me deparei com uma situação que me fez perceber que é possível usar o unicode para passar rapidamente pela substituição da string. O texto de entrada foi digitado no Word, que alterou o apóstrofo da versão direta para um apóstrofo "encaracolado" (que se parece mais com uma vírgula), que não foi afetado pela substituição da cadeia, mas foi tratado como um delimitador de cadeia pelo SQL Servidor. Obrigado pela resposta AviD (e todos os outros)!
Patrick
1
@ElRonnoco certeza, mas eu não desconto que, desde que eu já vi isso nos selvagens mais vezes do que você pensa ...
Avid
1
@AviD Atualizei o link para o PDF de Contrabando de SQL que você escreveu para a única versão que encontrei on-line ... informe-nos se houver outro local para o seu artigo.
Michael Fredrickson
41

Ok, esta resposta está relacionada à atualização da pergunta:

"Se alguém souber de alguma maneira específica de montar um ataque de injeção SQL contra esse método de higienização, eu adoraria vê-lo."

Agora, além da fuga de barra invertida do MySQL - e levando em consideração que estamos falando sobre o MSSQL, existem três maneiras possíveis de o SQL ainda injetar seu código

sSanitizedInput = "'" & Replace (sInput, "'", "''") & "'"

Leve em consideração que nem todos serão válidos o tempo todo e dependem muito do seu código real:

  1. Injeção de SQL de segunda ordem - se uma consulta SQL for reconstruída com base nos dados recuperados do banco de dados após o escape , os dados serão concatenados sem escape e poderão ser indiretamente injetados em SQL. Vejo
  2. Truncamento de strings - (um pouco mais complicado) - O cenário é que você tem dois campos, digamos um nome de usuário e senha, e o SQL concatena os dois. E ambos os campos (ou apenas o primeiro) têm um limite rígido de comprimento. Por exemplo, o nome de usuário é limitado a 20 caracteres. Digamos que você tenha este código:
username = left(Replace(sInput, "'", "''"), 20)

Então, o que você obtém - é o nome de usuário, escapou e, em seguida, aparou para 20 caracteres. O problema aqui - colocarei minha citação no 20º caractere (por exemplo, depois dos 19 anos), e sua citação de escape será cortada (no 21º caractere). Então o SQL

sSQL = "select * from USERS where username = '" + username + "'  and password = '" + password + "'"

combinado com o nome de usuário malformado acima mencionado, a senha já estará fora das cotações e conterá apenas a carga diretamente.
3. Contrabando de Unicode - Em certas situações, é possível transmitir um caractere de alto nível unicode que se parece com uma citação, mas não é - até chegar ao banco de dados, onde de repente está . Como não é uma citação quando você a validar, será fácil ... Consulte minha resposta anterior para obter mais detalhes e link para a pesquisa original.

Ávido
fonte
28

Em poucas palavras: nunca faça uma consulta escapando a si mesma. Você é obrigado a entender algo errado. Em vez disso, use consultas parametrizadas ou, se não puder fazer isso por algum motivo, use uma biblioteca existente que faça isso por você. Não há razão para fazer você mesmo.

Nick Johnson
fonte
2
E se você tiver que lidar com algo como "tabelas do Google Fusion", onde, no futuro, não há nenhuma biblioteca de abstração disponível que suporte seu dialeto? O que você sugeriria?
Systempuntoout
20

Sei que isso passou muito tempo depois que a pergunta foi feita, mas ..

Uma maneira de iniciar um ataque ao procedimento 'citar o argumento' é com o truncamento de strings. De acordo com o MSDN, no SQL Server 2000 SP4 (e no SQL Server 2005 SP1), uma seqüência muito longa será silenciosamente truncada.

Quando você cita uma sequência, ela aumenta de tamanho. Todo apóstrofo é repetido. Isso pode ser usado para enviar partes do SQL para fora do buffer. Assim, você pode efetivamente aparar partes de uma cláusula where.

Isso provavelmente seria útil principalmente em um cenário de página 'user admin', no qual você poderia abusar da instrução 'update' para não fazer todas as verificações que deveria fazer.

Portanto, se você decidir citar todos os argumentos, saiba o que acontece com os tamanhos das strings e verifique se você não fica truncado.

Eu recomendaria ir com parâmetros. Sempre. Só queria poder aplicar isso no banco de dados. E, como efeito colateral, é mais provável que você obtenha melhores hits do cache, porque mais instruções parecem iguais. (Isso certamente era verdade no Oracle 8)

Jørn Jensen
fonte
1
Depois de postar, decidi que o post do AviD cobre isso, e com mais detalhes. Espero que meu post ainda ajude alguém.
Jørn Jensen
10

Eu usei essa técnica ao lidar com a funcionalidade de 'pesquisa avançada', onde a criação de uma consulta a partir do zero era a única resposta viável. (Exemplo: permita ao usuário procurar produtos com base em um conjunto ilimitado de restrições nos atributos do produto, exibindo colunas e seus valores permitidos como controles da GUI para reduzir o limite de aprendizado para os usuários.)

Por si só, é seguro AFAIK. Como outro respondente apontou, no entanto, talvez você também precise lidar com o escape de backspace (embora não seja ao passar a consulta para o SQL Server usando ADO ou ADO.NET, pelo menos - não pode garantir todos os bancos de dados ou tecnologias).

O problema é que você realmente precisa ter certeza de quais sequências contêm entrada do usuário (sempre potencialmente maliciosas) e quais são consultas SQL válidas. Uma das armadilhas é se você usar valores do banco de dados - esses valores foram originalmente fornecidos pelo usuário? Nesse caso, eles também devem ser escapados. Minha resposta é tentar limpar o mais tarde possível (mas não mais tarde!), Ao construir a consulta SQL.

No entanto, na maioria dos casos, a ligação de parâmetros é o caminho a seguir - é apenas mais simples.

Pontus Gagge
fonte
2
Você ainda pode usar a substituição de parâmetro, mesmo se estiver criando suas próprias consultas.
24412 Nick Johnson
1
Você deve criar a sequência de instruções SQL do zero, mas ainda usar a substituição de parâmetros.
JeeBee
Não, NUNCA crie suas instruções SQL do zero.
Avid
8

Saneamento de entrada não é algo que você queira fazer pela metade. Use todo o seu rabo. Use expressões regulares nos campos de texto. TenteCast seus numéricos para o tipo numérico apropriado e relate um erro de validação se não funcionar. É muito fácil procurar padrões de ataque em sua entrada, como '-. Suponha que toda a entrada do usuário seja hostil.

tom.dietrich
fonte
4
E quando você sente falta desse ONE caso em uma entrada, você fica surpreso.
BryanH
4
"Algumas pessoas, quando confrontadas com um problema, pensam:" Eu sei, usarei expressões regulares. "Agora elas têm dois problemas."
MickeyfAgain_BeforeExitOfSO
1
@mickeyf Eu sei que esse é um sentimento comum, mas honestamente as expressões regulares são incríveis quando você as cumprimenta.
tom.dietrich
@ tom.dietrich Depende sempre da situação da vida real. F.ex. A sintaxe regexpr não é padrão, portanto, em geral, desaconselho o uso de regexpr em contextos em que diferentes sistemas são integrados para trabalharem juntos. Isso ocorre porque diferentes mecanismos regexpr avaliam regexprs de maneira diferente e, mais importante, esse fato difícil geralmente é subestimado ou ignorado, o que pode levar os desenvolvedores a não se importarem com essas incompatibilidades até serem mordidos. Existem muitas dessas incompatibilidades; ver f.ex. regular-expressions.info/shorthand.html (pesquise flavorsnessa página).
SantiBailors
6

De qualquer maneira, é uma má ideia, como você parece saber.

Que tal algo como escapar da citação em uma string como esta: \ '

Sua substituição resultaria em: \ ''

Se a barra invertida escapar da primeira cotação, a segunda cotação encerrará a sequência.

WW.
fonte
3
Obrigado pela resposta! Eu sei que o ataque funcionaria para um banco de dados mySQL, mas tenho certeza de que o MS SQL Server não aceitará uma barra invertida como caractere de escape (tentei). Várias pesquisas no Google não revelaram outros caracteres de escape, o que realmente me fez pensar por que isso não funcionaria.
Patrick Patrick
6

Resposta simples: Funcionará às vezes, mas não o tempo todo. Você deseja usar a validação da lista branca em tudo o que faz, mas sei que isso nem sempre é possível, então você é forçado a seguir a melhor lista negra de palpites. Da mesma forma, você deseja usar procs armazenados parametrizados em tudo , mas mais uma vez, isso nem sempre é possível, então você é forçado a usar sp_execute com parâmetros.

Existem maneiras de contornar qualquer lista negra utilizável que você possa criar (e algumas listas brancas também).

Um artigo decente está aqui: http://www.owasp.org/index.php/Top_10_2007-A2

Se você precisar fazer isso como uma solução rápida, a fim de dar tempo para obter uma solução real, faça-o. Mas não pense que você está seguro.

Caractere inválido
fonte
6

Existem duas maneiras de fazer isso, sem exceções, para proteger-se das injeções de SQL; declarações preparadas ou procedimentos armazenados pré-determinados.

olle
fonte
4

Se você tiver consultas parametrizadas disponíveis, deverá usá-las o tempo todo. Basta uma consulta deslizar pela rede e seu banco de dados está em risco.

Kev
fonte
4

Sim, isso deve funcionar até que alguém execute SET QUOTED_IDENTIFIER OFF e use aspas duplas em você.

Editar: não é tão simples como não permitir que o usuário mal-intencionado desative os identificadores citados:

O driver ODBC do SQL Server Native Client e o provedor OLE DB do SQL Server Native Client para SQL Server definem automaticamente QUOTED_IDENTIFIER como ON ao conectar. Isso pode ser configurado em fontes de dados ODBC, em atributos de conexão ODBC ou em propriedades de conexão OLE DB. O padrão para SET QUOTED_IDENTIFIER está desativado para conexões de aplicativos da biblioteca de banco de dados.

Quando um procedimento armazenado é criado, as configurações SET QUOTED_IDENTIFIER e SET ANSI_NULLS são capturadas e usadas para chamadas subseqüentes desse procedimento armazenado .

SET QUOTED_IDENTIFIER também corresponde à configuração QUOTED_IDENTIFER de ALTER DATABASE.

SET QUOTED_IDENTIFIER é definido no momento da análise . Definir no momento da análise significa que, se a instrução SET estiver presente no lote ou no procedimento armazenado, ela entrará em vigor, independentemente de a execução do código realmente atingir esse ponto; e a instrução SET entra em vigor antes de qualquer instrução ser executada.

Existem várias maneiras de ativar o QUOTED_IDENTIFIER sem você necessariamente saber. É certo que essa não é a arma que você procura, mas é uma superfície de ataque bastante grande. Claro, se você também escapou de aspas duplas - então estamos de volta onde começamos. ;)

Mark Brackett
fonte
1
Isso poderia funcionar, mas, novamente, como eles poderiam executar esse código quando todas as entradas do usuário estão entre aspas simples? Uma linha específica de código que seria capaz de injetar SQL no código acima seria muito útil. Obrigado!
224 Patrick
4

Sua defesa falharia se:

  • a consulta está esperando um número em vez de uma sequência
  • havia outra maneira de representar aspas simples, incluindo:
    • uma sequência de escape como \ 039
    • um caractere unicode

(no último caso, teria que ser algo que foi expandido somente após a substituição)

AJ.
fonte
4

Patrick, você está adicionando aspas simples em TODAS as entradas, até mesmo entradas numéricas? Se você tiver entrada numérica, mas não estiver colocando aspas simples, poderá fazer uma exposição.

Rob Kraft
fonte
1

Que código feio seria todo esse sanitização da entrada do usuário! Em seguida, o StringBuilder desajeitado para a instrução SQL. O método de instrução preparado resulta em um código muito mais limpo, e os benefícios da Injeção SQL são uma adição muito boa.

Também por que reinventar a roda?

JeeBee
fonte
1

Em vez de alterar uma única citação para (como parece) duas aspas simples, por que não alterá-lo para um apóstrofo, uma citação ou removê-lo completamente?

De qualquer forma, é meio que um desdém ... especialmente quando você legitimamente tem coisas (como nomes) que podem usar aspas simples ...

NOTA: Seu método também pressupõe que todos os que trabalham no seu aplicativo sempre se lembrem de higienizar as entradas antes que elas atinjam o banco de dados, o que provavelmente não é realista na maioria das vezes.

Kevin Fairchild
fonte
Votado para baixo porque a resposta não aborda a pergunta. A pergunta é sobre como escapar seqüências de caracteres no SQL. Quando você escapa de uma sequência arbitrária (como o questionador está tentando fazer, para lidar com dados não autorizados), você não pode simplesmente substituir caracteres problemáticos por outros arbitrários; que corrompe dados. (Além disso, uma única citação É um apóstrofo (pelo menos em ASCII)).
andrewf
-1

Embora você possa encontrar uma solução que funcione para seqüências de caracteres, para predicados numéricos, você também precisa garantir que eles estejam apenas passando números (verificação simples: é possível analisá-la como int / double / decimal?).

É muito trabalho extra.

Joseph Daigle
fonte
-2

Pode funcionar, mas parece um pouco chato para mim. Eu recomendaria verificar se cada string é válida, testando-a contra uma expressão regular.

Roubar
fonte
-3

Sim, você pode, se ...

Depois de estudar o tópico, acho que a entrada higienizada como você sugeriu é segura, mas apenas sob estas regras:

  1. você nunca permite que os valores de string provenientes dos usuários se tornem algo além de literais de string (ou seja, evite dar a opção de configuração: "Digite nomes / expressões adicionais da coluna SQL aqui:"). Tipos de valor diferentes de cadeias (números, datas, ...): converta-os em seus tipos de dados nativos e forneça uma rotina para literal SQL de cada tipo de dados.

    • Instruções SQL são problemáticas para validar
  2. você usa nvarchar/ ncharcolumns (e literalmente as sequências de prefixo com N) OU valores-limite que entram em varchar/ charcolumns somente para caracteres ASCII (por exemplo, lançam uma exceção ao criar a instrução SQL)

    • Dessa forma, você evitará a conversão automática de apóstrofes de CHAR (700) para CHAR (39) (e talvez outros hacks Unicode similares)
  3. você sempre valida o comprimento do valor para caber no comprimento real da coluna (lance uma exceção se for mais longa)

    • havia um defeito conhecido no SQL Server que permitia ignorar o erro SQL gerado no truncamento (levando ao truncamento silencioso)
  4. você garante que SET QUOTED_IDENTIFIERestá sempreON

    • cuidado, é aplicado em tempo de análise, ou seja, mesmo em seções inacessíveis do código

Cumprindo estes 4 pontos, você deve estar seguro. Se você violar algum deles, será aberta uma maneira de injeção de SQL.

miroxlav
fonte
1
É como se você não tivesse lido todas as outras respostas para essa pergunta de 8 anos , já que muitas dessas respostas apontam que o método dele falha na interrupção da injeção se o invasor simplesmente usa caracteres unicode.
Hogan
@ Hogan - eu fiz, mas acho que há um valor extra na minha pergunta. Tenho muita experiência e testes por trás do que escrevi. Sei que usar parâmetros de consulta é melhor, mas também entendo perfeitamente a situação em que alguém deve evitá-la devido a várias razões (por exemplo, a exigência do empregador de manter a maneira antiga). Nesse caso, acho que minha resposta é muito abrangente e tem maior valor do que as respostas dizendo "simplesmente não faça isso", porque mostra o caminho. Mostre-me outras respostas aqui que mostram o mesmo caminho e considerarei excluir as minhas.
22616 miroxlav
Ok, quando (não se) o seu sistema ficar comprometido, volte e exclua esta resposta .... ou você pode usar uma consulta parametrizada.
Hogan
@ Hogan - Não tenho nenhum problema em fazê-lo :) Mas atualmente eu afirmo que não há uma maneira conhecida de contornar isso se você mantiver as 4 regras que eu publiquei. Se você realmente acha que há uma maneira de contornar isso, basta indicar onde.
22616 miroxlav
Mau conselho. qualquer interpolação pode ser derrotada.
quer