Estamos tendo outra discussão aqui no trabalho sobre o uso de consultas sql parametrizadas em nosso código. Temos dois lados na discussão: eu e alguns outros que dizem que devemos sempre usar parâmetros para proteção contra injeções de sql e os outros caras que acham que não é necessário. Em vez disso, eles desejam substituir apóstrofos únicos por dois apóstrofos em todas as strings para evitar injeções de sql. Nossos bancos de dados estão todos executando Sql Server 2005 ou 2008 e nossa base de código está executando em .NET framework 2.0.
Deixe-me dar um exemplo simples em C #:
Eu quero que usemos isso:
string sql = "SELECT * FROM Users WHERE Name=@name";
SqlCommand getUser = new SqlCommand(sql, connection);
getUser.Parameters.AddWithValue("@name", userName);
//... blabla - do something here, this is safe
Enquanto os outros caras querem fazer isso:
string sql = "SELECT * FROM Users WHERE Name=" + SafeDBString(name);
SqlCommand getUser = new SqlCommand(sql, connection);
//... blabla - are we safe now?
Onde a função SafeDBString é definida da seguinte forma:
string SafeDBString(string inputValue)
{
return "'" + inputValue.Replace("'", "''") + "'";
}
Agora, enquanto usarmos SafeDBString em todos os valores de string em nossas consultas, devemos estar seguros. Certo?
Existem dois motivos para usar a função SafeDBString. Primeiro, é a maneira como tem sido feito desde a idade da pedra e, segundo, é mais fácil depurar as instruções sql, pois você vê a consulta exata que é executada no banco de dados.
Então. Minha dúvida é se realmente é suficiente usar a função SafeDBString para evitar ataques de injeção de sql. Tenho tentado encontrar exemplos de código que violem essa medida de segurança, mas não consigo encontrar nenhum exemplo disso.
Há alguém aí que pode quebrar isso? Como você faria?
EDITAR: Para resumir as respostas até agora:
- Ninguém encontrou uma maneira de contornar o SafeDBString no Sql Server 2005 ou 2008 ainda. Isso é bom, eu acho?
- Várias respostas indicaram que você obtém um ganho de desempenho ao usar consultas parametrizadas. O motivo é que os planos de consulta podem ser reutilizados.
- Também concordamos que o uso de consultas parametrizadas fornece um código mais legível e mais fácil de manter
- Além disso, é mais fácil sempre usar parâmetros do que usar várias versões de SafeDBString, strings para conversões de números e strings para conversões de datas.
- Usando parâmetros, você obtém conversão automática de tipo, algo que é especialmente útil quando estamos trabalhando com datas ou números decimais.
- E, finalmente: não tente fazer a segurança sozinho, como escreveu JulianR. Os fornecedores de banco de dados gastam muito tempo e dinheiro com segurança. Não há como fazermos melhor e não há razão para tentarmos fazer o trabalho deles.
Portanto, embora ninguém tenha conseguido quebrar a segurança simples da função SafeDBString, recebi muitos outros bons argumentos. Obrigado!
fonte
Respostas:
Acho que a resposta correta é:
Não tente fazer a segurança sozinho . Use qualquer biblioteca confiável padrão da indústria disponível para o que você está tentando fazer, em vez de tentar fazer você mesmo. Quaisquer suposições que você fizer sobre segurança, podem estar incorretas. Por mais segura que sua abordagem possa parecer (e, na melhor das hipóteses, parece duvidosa), há o risco de você estar negligenciando algo e você realmente quer arriscar quando se trata de segurança?
Use parâmetros.
fonte
E então alguém usa "em vez de". Os parâmetros são, IMO, o único caminho seguro a seguir.
Também evita muitos problemas de i18n com datas / números; que data é 01/02/03? Quanto é 123.456? Seus servidores (app-server e db-server) concordam uns com os outros?
Se o fator de risco não os convence, que tal o desempenho? O RDBMS pode reutilizar o plano de consulta se você usar parâmetros, ajudando no desempenho. Não pode fazer isso apenas com a corda.
fonte
O argumento é impossível. Se você conseguir encontrar uma vulnerabilidade, seus colegas de trabalho apenas alterarão a função SafeDBString para contabilizá-la e, em seguida, pedirão que você prove que não é seguro novamente.
Visto que as consultas parametrizadas são uma prática recomendada de programação indiscutível, o ônus da prova deve recair sobre eles para declarar por que não estão usando um método que seja mais seguro e com melhor desempenho.
Se o problema for reescrever todo o código legado, o compromisso mais fácil seria usar consultas parametrizadas em todo o código novo e refatorar o código antigo para usá-los ao trabalhar nesse código.
Meu palpite é que a questão real é orgulho e teimosia, e não há muito mais que você possa fazer a respeito.
fonte
Em primeiro lugar, sua amostra para a versão "Substituir" está errada. Você precisa colocar apóstrofos ao redor do texto:
Portanto, essa é outra coisa que os parâmetros fazem por você: você não precisa se preocupar se um valor precisa ou não ser colocado entre aspas. Claro, você pode construir isso na função, mas então você precisa adicionar muita complexidade à função: como saber a diferença entre 'NULL' como nulo e 'NULL' como apenas uma string, ou entre um número e uma string que contém muitos dígitos. É apenas mais uma fonte de bugs.
Outra coisa é o desempenho: os planos de consulta parametrizados costumam ser melhor armazenados em cache do que os planos concatenados, portanto, talvez economizando uma etapa do servidor ao executar a consulta.
Além disso, escapar aspas simples não é bom o suficiente. Muitos produtos de banco de dados permitem métodos alternativos para caracteres de escape dos quais um invasor pode tirar vantagem. No MySQL, por exemplo, você também pode escapar de uma aspa simples com uma barra invertida. E então o seguinte valor de "nome" explodiria o MySQL apenas com a
SafeDBString()
função, porque quando você duplica a aspa simples, o primeiro ainda é escapado pela barra invertida, deixando o segundo "ativo":Além disso, JulianR traz um bom ponto abaixo: NUNCA tente fazer o trabalho de segurança sozinho. É tão fácil errar na programação de segurança de maneiras sutis que parecem funcionar, mesmo com testes completos. Então o tempo passa e um ano depois você descobre que seu sistema foi quebrado há seis meses e você nem sabia disso até então.
Sempre confie o máximo possível nas bibliotecas de segurança fornecidas para sua plataforma. Eles serão escritos por pessoas que ganham a vida com códigos de segurança, muito mais bem testados do que o que você pode gerenciar e atendidos pelo fornecedor se uma vulnerabilidade for encontrada.
fonte
Então eu diria:
1) Por que você está tentando reimplementar algo que está embutido? está lá, prontamente disponível, fácil de usar e já depurado em escala global. Se futuros bugs forem encontrados nele, eles serão corrigidos e disponibilizados para todos muito rapidamente, sem que você precise fazer nada.
2) Quais processos existem para garantir que você nunca perca uma chamada para SafeDBString? Perdê-lo em apenas um lugar pode abrir uma série de problemas. Quanto você vai observar essas coisas e considerar o quanto é desperdiçado esse esforço quando a resposta correta aceita é tão fácil de alcançar.
3) Você tem certeza de que cobriu todos os vetores de ataque que a Microsoft (a autora do banco de dados e da biblioteca de acesso) conhece em sua implementação de SafeDBString ...
4) Quão fácil é ler a estrutura do sql? O exemplo usa concatenação +, os parâmetros são muito parecidos com string.Format, que é mais legível.
Além disso, existem 2 maneiras de descobrir o que foi realmente executado - role sua própria função LogCommand, uma função simples sem preocupações de segurança , ou até mesmo olhe para um rastreio sql para descobrir o que o banco de dados pensa que está realmente acontecendo.
Nossa função LogCommand é simplesmente:
Certo ou errado, ele nos fornece as informações de que precisamos sem problemas de segurança.
fonte
Com consultas parametrizadas, você obtém mais do que proteção contra injeção de sql. Você também obtém um melhor potencial de cache do plano de execução. Se você usar o gerador de perfil de consulta do sql server, ainda poderá ver o 'sql exato que é executado no banco de dados', portanto, não está realmente perdendo nada em termos de depuração de suas instruções sql.
fonte
Usei ambas as abordagens para evitar ataques de injeção de SQL e definitivamente prefiro consultas parametrizadas. Quando usei consultas concatenadas, usei uma função de biblioteca para escapar das variáveis (como mysql_real_escape_string) e não teria certeza de ter coberto tudo em uma implementação proprietária (como parece que você também).
fonte
Você não pode fazer facilmente qualquer tipo de verificação da entrada do usuário sem usar parâmetros.
Se você usar as classes SQLCommand e SQLParameter para fazer suas chamadas de banco de dados, ainda poderá ver a consulta SQL que está sendo executada. Observe a propriedade CommandText do SQLCommand.
Eu sempre sou um pouco suspeito da abordagem "faça você mesmo" para prevenir a injeção de SQL quando as consultas parametrizadas são tão fáceis de usar. Em segundo lugar, só porque "sempre foi feito assim" não significa que seja a maneira certa de fazer isso.
fonte
Isso só é seguro se você tiver a garantia de que vai passar uma string.
E se você não estiver passando uma string em algum ponto? E se você passar apenas um número?
No final das contas se tornaria:
fonte
Eu usaria stored procedures ou funções para tudo, então a questão não surgiria.
Onde tenho que colocar SQL no código, uso parâmetros, que é a única coisa que faz sentido. Lembre aos dissidentes que existem hackers mais espertos do que eles e com melhor incentivo para quebrar o código que está tentando enganá-los. Usar parâmetros simplesmente não é possível e não é difícil.
fonte
Concordo amplamente nas questões de segurança.
Outra razão para usar parâmetros é para eficiência.
Os bancos de dados sempre compilarão sua consulta e a armazenarão em cache e, em seguida, reutilizarão a consulta armazenada em cache (que é obviamente mais rápida para as solicitações subsequentes). Se você usar parâmetros, mesmo se você usar parâmetros diferentes, o banco de dados reutilizará sua consulta em cache, uma vez que corresponde com base na string SQL antes de vincular os parâmetros.
No entanto, se você não vincular parâmetros, a string SQL muda a cada solicitação (que tem parâmetros diferentes) e nunca corresponderá ao que está em seu cache.
fonte
Pelas razões já indicadas, os parâmetros são uma ideia muito boa. Mas odiamos usá-los porque criar o parâmetro e atribuir seu nome a uma variável para uso posterior em uma consulta é uma destruição de cabeça tripla.
A classe a seguir envolve o construtor de strings que você normalmente usará para construir solicitações SQL. Ele permite que você escreva consultas paramaterizadas sem nunca ter que criar um parâmetro , para que você possa se concentrar no SQL. Seu código ficará assim ...
A legibilidade do código, espero que concorde, foi bastante aprimorada e a saída é uma consulta parametrizada adequada.
A classe é assim ...
fonte
Pelo pouco tempo que tive para investigar problemas de injeção de SQL, posso ver que tornar um valor 'seguro' também significa que você está fechando a porta para situações em que pode realmente querer apóstrofos em seus dados - e o nome de alguém , por exemplo, O'Reilly.
Isso deixa os parâmetros e procedimentos armazenados.
E sim, você deve sempre tentar implementar o código da melhor maneira que sabe agora - não apenas como sempre foi feito.
fonte
''
como um literal'
, então sua string será vista internamente como a sequência de caracteresO'Reilly
. Isso é o que o banco de dados armazenará, recuperará, comparará etc. Se você quiser mostrar ao usuário seus dados depois de escapar deles, mantenha uma cópia do lado do aplicativo de string sem escape.Aqui estão alguns artigos que você pode achar úteis para convencer seus colegas de trabalho.
http://www.sommarskog.se/dynamic_sql.html
http://unixwiz.net/techtips/sql-injection.html
Pessoalmente, prefiro nunca permitir que nenhum código dinâmico toque meu banco de dados, exigindo que todos os contatos sejam por meio de sps (e não um que use SQl dinâmico). Isso significa que nada além do que eu dei permissão aos usuários para fazer pode ser feito e que os usuários internos (exceto os poucos com acesso de produção para fins administrativos) não podem acessar diretamente minhas tabelas e criar confusão, roubar dados ou cometer fraudes. Se você executa um aplicativo financeiro, esse é o caminho mais seguro.
fonte
Pode ser quebrado, no entanto, os meios dependem das versões / patches exatos, etc.
Um que já foi mencionado é o bug de estouro / truncamento que pode ser explorado.
Outro meio futuro seria encontrar bugs semelhantes a outros bancos de dados - por exemplo, a pilha MySQL / PHP sofreu um problema de escape porque certas sequências UTF8 poderiam ser usadas para manipular a função de substituição - a função de substituição seria enganada para introduzir os caracteres de injeção.
No final do dia, o mecanismo de segurança substituto depende da funcionalidade esperada, mas não pretendida . Uma vez que a funcionalidade não era a finalidade pretendida do código, existe uma grande probabilidade de que alguma peculiaridade descoberta interrompa a funcionalidade esperada.
Se você tiver muito código legado, o método replace pode ser usado como um paliativo para evitar reescrita e testes demorados. Se você está escrevendo um novo código, não há desculpa.
fonte
Sempre use consultas parametrizadas sempre que possível. Às vezes, mesmo uma entrada simples sem o uso de quaisquer caracteres estranhos já pode criar uma injeção SQL se não for identificada como uma entrada para um campo no banco de dados.
Portanto, deixe o banco de dados fazer o seu trabalho de identificar a entrada em si, sem mencionar que também economiza muitos problemas quando você precisa inserir caracteres estranhos que, de outra forma, seriam escapados ou alterados. Ele pode até mesmo economizar um tempo de execução valioso no final, por não ter que calcular a entrada.
fonte
Não vi nenhuma outra resposta abordar esse lado do 'por que fazer você mesmo é ruim', mas considere um ataque de truncamento SQL .
Também existe a
QUOTENAME
função T-SQL que pode ser útil se você não conseguir convencê-los a usar parâmetros. Ele captura muitas (todas?) Das preocupações de qoute escapadas.fonte
2 anos depois , eu recidiveu ... Qualquer um que considere os parâmetros uma dor pode experimentar minha extensão VS, QueryFirst . Você edita sua solicitação em um arquivo .sql real (Validação, Intellisense). Para adicionar um parâmetro, basta digitá-lo diretamente em seu SQL, começando com o '@'. Ao salvar o arquivo, o QueryFirst gerará classes de wrapper para permitir que você execute a consulta e acesse os resultados. Ele pesquisará o tipo de banco de dados de seu parâmetro e o mapeará para um tipo .net, que você encontrará como uma entrada para os métodos Execute () gerados. Não poderia ser mais simples. Fazer isso da maneira certa é radicalmente mais rápido e fácil do que fazer de qualquer outra forma, e criar uma vulnerabilidade de injeção de sql se torna impossível, ou pelo menos perversamente difícil. Existem outras vantagens matadoras, como ser capaz de deletar colunas em seu banco de dados e ver imediatamente erros de compilação em seu aplicativo.
isenção de responsabilidade legal: eu escrevi QueryFirst
fonte
Aqui estão alguns motivos para usar consultas parametrizadas:
fonte
Havia poucas vulnerabilidades (não me lembro qual era o banco de dados) relacionadas ao estouro de buffer da instrução SQL.
O que eu quero dizer é que SQL-Injection é mais do que apenas "escapar da citação", e você não tem ideia do que virá a seguir.
fonte
Outra consideração importante é manter o controle de dados com e sem escape. Existem toneladas e toneladas de aplicativos, da Web e outros, que não parecem controlar adequadamente quando os dados estão em formato Unicode, e codificados, HTML formatados etc. É óbvio que se tornará difícil manter o controle de quais strings estão
''
codificadas e quais não estão.Também é um problema quando você acaba mudando o tipo de alguma variável - talvez fosse um número inteiro, mas agora é uma string. Agora você tem um problema.
fonte