Do meu ponto de vista, os ataques de injeção de SQL podem ser evitados por:
- Triagem cuidadosa, filtragem, entrada de codificação (antes da inserção no SQL)
- Usando instruções preparadas / consultas parametrizadas
Suponho que existem prós e contras para cada um, mas por que o nº 2 decolou e se tornou considerado mais ou menos a maneira de fato de impedir ataques de injeção? É apenas mais seguro e menos propenso a erros ou houve outros fatores?
Pelo que entendi, se o item 1 for usado corretamente e todas as advertências forem atendidas, ele poderá ser tão eficaz quanto o item 2.
Higienização, filtragem e codificação
Houve alguma confusão da minha parte entre o que significa higienizar , filtrar e codificar . Eu direi que, para meus propósitos, todas as opções acima podem ser consideradas para a opção 1. Nesse caso, eu entendo que a limpeza e a filtragem têm o potencial de modificar ou descartar dados de entrada, enquanto a codificação preserva os dados como estão , mas os codifica. corretamente para evitar ataques de injeção. Acredito que a fuga de dados pode ser considerada uma forma de codificá-los.
Consultas com parâmetros versus biblioteca de codificação
Existem respostas onde conceitos parameterized queries
e encoding libraries
que são tratados de forma intercambiável. Corrija-me se estiver errado, mas tenho a impressão de que são diferentes.
Meu entendimento é que encoding libraries
, por melhores que sejam, sempre têm o potencial de modificar o "Programa" do SQL, porque estão fazendo alterações no próprio SQL, antes de ser enviado ao RDBMS.
Parameterized queries
por outro lado, envie o programa SQL para o RDBMS, que otimiza a consulta, define o plano de execução da consulta, seleciona índices a serem usados etc., e depois conecta os dados, como a última etapa dentro do RDBMS em si.
Biblioteca de codificação
data -> (encoding library)
|
v
SQL -> (SQL + encoded data) -> RDBMS (execution plan defined) -> execute statement
Consulta parametrizada
data
|
v
SQL -> RDBMS (query execution plan defined) -> data -> execute statement
Importância histórica
Algumas respostas mencionam que, historicamente, as consultas parametrizadas (PQ) foram criadas por motivos de desempenho e antes que os ataques de injeção direcionados a problemas de codificação se tornassem populares. Em algum momento, ficou claro que o PQ também era bastante eficaz contra ataques de injeção. Para manter o espírito da minha pergunta, por que o PQ permaneceu o método de escolha e por que floresceu acima da maioria dos outros métodos quando se trata de impedir ataques de injeção de SQL?
Respostas:
O problema é que o nº 1 exige que você efetivamente analise e interprete a totalidade da variante SQL em que está trabalhando, para saber se está fazendo algo que não deveria. E mantenha esse código atualizado enquanto atualiza seu banco de dados. Em todos os lugares você aceita entradas para suas consultas. E não estragar tudo.
Então, sim, esse tipo de coisa interromperia os ataques de injeção de SQL, mas é absurdamente mais caro de implementar.
fonte
null
uma sequência ou número e agir de acordo. Isso é muito bom para segurança. E mesmo se você executar a consulta uma vez, o mecanismo do banco de dados já a otimizará. Melhor ainda se estiver em cache!Porque a opção 1 não é uma solução. Triagem e filtragem significa rejeitar ou remover entrada inválida. Mas qualquer entrada pode ser válida. Por exemplo, apóstrofo é um caractere válido no nome "O'Malley". Só precisa ser codificado corretamente antes de ser usado no SQL, que é o que as instruções preparadas fazem.
Depois de adicionar a nota, parece que você está basicamente perguntando por que usar uma função de biblioteca padrão em vez de escrever seu próprio código funcionalmente semelhante do zero? Você sempre deve preferir soluções de biblioteca padrão a escrever seu próprio código. É menos trabalho e mais sustentável. Esse é o caso de qualquer funcionalidade, mas especialmente para algo que é sensível à segurança, não faz absolutamente sentido reinventar a roda por conta própria.
fonte
O\'Malley
está usando a barra para escapar da cotação para inserção adequada (pelo menos em alguns bancos de dados). No MS SQL ou no Access, ele pode ser escapado com uma cotação adicionalO''Malley
. Não é muito portátil se você tiver que fazer isso sozinho.Se você está tentando fazer o processamento de strings, não está realmente gerando uma consulta SQL. Você está gerando uma sequência que pode produzir uma consulta SQL. Há um nível de indireção que abre muito espaço para erros e bugs. É realmente um tanto surpreendente, dado que na maioria dos contextos estamos felizes em interagir com algo programaticamente. Por exemplo, se temos alguma estrutura de lista e queremos adicionar um item, geralmente não fazemos:
Se alguém sugerir isso, você responderia com razão que é bastante ridículo e que isso deve ser feito:
Isso interage com a estrutura de dados em seu nível conceitual. Ele não introduz nenhuma dependência de como essa estrutura pode ser impressa ou analisada. Essas são decisões completamente ortogonais.
Sua primeira abordagem é como a primeira amostra (apenas um pouco pior): você está assumindo que pode programaticamente construir a string que será analisada corretamente como a consulta que você deseja. Isso depende do analisador e de um monte de lógica de processamento de strings.
A segunda abordagem do uso de consultas preparadas é muito mais parecida com a segunda amostra. Ao usar uma consulta preparada, você essencialmente analisa uma pseudo-consulta legal, mas possui alguns espaços reservados e, em seguida, usa uma API para substituir corretamente alguns valores. Você não envolve mais o processo de análise e não precisa se preocupar com nenhum processamento de string.
Em geral, é muito mais fácil e muito menos propenso a erros interagir com as coisas em seu nível conceitual. Uma consulta não é uma sequência, é o que você obtém quando analisa uma sequência ou constrói uma programaticamente (ou qualquer outro método que permita criar uma).
Há uma boa analogia aqui entre macros no estilo C, que substituem texto simples, e macros no estilo Lisp, que geram arbitrariamente códigos. Com macros de estilo C, você pode substituir o texto no código-fonte, o que significa que você pode introduzir erros sintáticos ou comportamento enganoso. Com as macros Lisp, você está gerando código da forma que o compilador a processa (ou seja, você está retornando as estruturas de dados reais que o compilador processa, não o texto que o leitor precisa processar antes que o compilador possa acessá-lo) . Com uma macro Lisp, você não pode gerar algo que seria um erro de análise. Por exemplo, você não pode gerar (deixe ((ab) a .
Mesmo com as macros Lisp, você ainda pode gerar um código incorreto, porque não precisa necessariamente conhecer a estrutura que deveria estar lá. Por exemplo, em Lisp, (let ((ab)) a) significa "estabelecer uma nova ligação lexical da variável a ao valor da variável b e, em seguida, retorne o valor de a" e (let (ab) a) significa "estabeleça novas ligações lexicais das variáveis aeb e inicialize as duas para zero e, em seguida, retorne o valor de a." Ambos são sintaticamente corretos, mas significam coisas diferentes. Para evitar esse problema, você pode usar mais funções de reconhecimento semântico e fazer algo como:
Com algo assim, é impossível retornar algo sintaticamente inválido e é muito mais difícil retornar algo que acidentalmente não é o que você queria.
fonte
Ajuda que a opção 2 seja geralmente considerada uma prática recomendada, porque o banco de dados pode armazenar em cache a versão não parametrizada da consulta. Consultas parametrizadas antecedem a questão da injeção de SQL por vários anos (eu acredito), acontece que você pode matar dois coelhos com uma cajadada só.
fonte
Simplesmente disse: Eles não o fizeram. Sua declaração:
é fundamentalmente falho. As consultas parametrizadas existem há muito mais tempo do que a injeção de SQL é pelo menos amplamente conhecida. Eles geralmente foram desenvolvidos como uma maneira de evitar a concentração de strings na funcionalidade usual "formulário para pesquisa" que os aplicativos LOB (Line of Business) possuem. Muitos - MUITOS - anos depois, alguém encontrou um problema de segurança com a manipulação de strings.
Lembro-me de fazer SQL há 25 anos (quando a Internet NÃO era amplamente usada - estava apenas começando) e lembro de fazer SQL vs. IBM DB5 IIRC versão 5 - e que já tinham consultas parametrizadas.
fonte
Além de todas as outras boas respostas:
A razão pela qual o nº 2 é melhor é porque ele separa seus dados do seu código. No nº 1, seus dados fazem parte do seu código e é daí que vêm todas as coisas ruins. Com o nº 1, você obtém sua consulta e precisa executar etapas adicionais para garantir que sua consulta entenda seus dados como dados, enquanto que no nº 2 você obtém seu código e seu código e seus dados são dados.
fonte
As consultas parametrizadas, além de fornecerem defesa contra injeção de SQL, geralmente têm um benefício adicional de serem compiladas apenas uma vez e executadas várias vezes com parâmetros diferentes.
Do ponto de banco de dados SQL de vista
select * from employees where last_name = 'Smith'
eselect * from employees where last_name = 'Fisher'
são muito diferentes e, portanto, requerem separado de análise, compilação e otimização. Eles também ocuparão slots separados na área de memória dedicada ao armazenamento de instruções compiladas. Em um sistema muito carregado com um grande número de consultas semelhantes que têm parâmetros diferentes, a computação e a sobrecarga de memória podem ser substanciais.Posteriormente, o uso de consultas parametrizadas geralmente oferece grandes vantagens de desempenho.
fonte
prepare
geralmente é bem diferente de um nível SQL realprepare
).SELECT * FROM employees WHERE last_name IN (?, ?)
eSELECT * FROM employees WHERE last_name IN (?, ?, ?, ?, ?, ?)
.Espere mas por quê?
A opção 1 significa que você deve escrever rotinas de limpeza para qualquer tipo de entrada, enquanto a opção 2 é menos suscetível a erros e menos código para você escrever / testar / manter.
Certamente, "cuidar de todas as advertências" pode ser mais complexo do que você pensa, e sua linguagem (por exemplo, Java PreparedStatement) tem mais informações do que você pensa.
Instruções preparadas ou consultas parametrizadas são pré-compiladas no servidor de banco de dados, portanto, quando os parâmetros são definidos, nenhuma concatenação SQL é feita porque a consulta não é mais uma sequência SQL. Uma vantagem adicional é que o RDBMS armazena em cache a consulta e as chamadas subseqüentes são consideradas o mesmo SQL, mesmo quando os valores dos parâmetros variam, enquanto no SQL concatenado toda vez que a consulta é executada com valores diferentes, a consulta é diferente e o RDBMS precisa analisá-la. , crie o plano de execução novamente etc.
fonte
Vamos imaginar como seria uma abordagem ideal de "higienizar, filtrar e codificar".
A limpeza e a filtragem podem fazer sentido no contexto de um aplicativo específico, mas, no final das contas, ambas se resumem a dizer "você não pode colocar esses dados no banco de dados". Para o seu aplicativo, isso pode ser uma boa ideia, mas não é algo que você pode recomendar como solução geral, pois haverá aplicativos que precisam ser capazes de armazenar caracteres arbitrários no banco de dados.
Então isso deixa a codificação. Você pode começar por ter uma função que codifica as strings adicionando caracteres de escape, para que você possa substituí-los em si mesmo. Como bancos de dados diferentes precisam de caracteres diferentes que escapam (em alguns bancos de dados, ambos
\'
e''
são seqüências de escape válidas para'
, mas não em outros), essa função precisa ser fornecida pelo fornecedor do banco de dados.Mas nem todas as variáveis são seqüências de caracteres. Às vezes, você precisa substituir um número inteiro ou uma data. Eles são representados de maneira diferente às seqüências de caracteres, portanto, você precisa de métodos de codificação diferentes (novamente, eles precisam ser específicos para o fornecedor do banco de dados) e precisa substituí-los na consulta de maneiras diferentes.
Portanto, talvez as coisas fiquem mais fáceis se o banco de dados também substituir por você - ele já sabe quais tipos a consulta espera, e como codificar dados com segurança, e como substituí-los na sua consulta com segurança, para que você não precise se preocupar com isso. no seu código.
Neste ponto, apenas reinventamos as consultas parametrizadas.
E, quando as consultas são parametrizadas, elas abrem novas oportunidades, como otimizações de desempenho e monitoramento simplificado.
É difícil fazer codificação correta, e a codificação feita corretamente é indistinguível da parametrização.
Se você realmente gosta de interpolação de string como uma forma de consultas de construção, há um par de idiomas (Scala e ES2015 vêm à mente) que têm interpolação de string pluggable, por isso não são bibliotecas que permitem que você escrever consultas parametrizadas que se parecem com interpolação de string, mas estão seguros contra injeção de SQL - portanto, na sintaxe do ES2015:
fonte
Na opção 1, você está trabalhando com um conjunto de entradas size = infinito que está tentando mapear para um tamanho de saída muito grande. Na opção 2, você limitou sua entrada ao que escolher. Em outras palavras:
De acordo com outras respostas, também parece haver alguns benefícios de desempenho ao limitar seu escopo para longe do infinito e para algo gerenciável.
fonte
Um modelo mental útil do SQL (especialmente dialetos modernos) é que cada instrução ou consulta SQL é um programa. Em um programa executável binário nativo, os tipos mais perigosos de vulnerabilidades de segurança são excedentes, nos quais um invasor pode substituir ou modificar o código do programa com instruções diferentes.
Uma vulnerabilidade de injeção SQL é isomórfica a um estouro de buffer em uma linguagem como C. A história mostrou que os estouros de buffer são extremamente difíceis de evitar - mesmo o código extremamente crítico sujeito à revisão aberta geralmente contém essas vulnerabilidades.
Um aspecto importante da abordagem moderna para solucionar vulnerabilidades de estouro é o uso de mecanismos de hardware e SO para marcar partes específicas da memória como não executáveis e marcar outras partes da memória como somente leitura. (Consulte o artigo da Wikipedia sobre Proteção de espaço executável , por exemplo.) Dessa forma, mesmo que um invasor possa modificar dados, o invasor não pode fazer com que seus dados injetados sejam tratados como código.
Portanto, se uma vulnerabilidade de injeção de SQL é equivalente a um estouro de buffer, qual é o equivalente de SQL a um bit NX ou a páginas de memória somente leitura? A resposta é: instruções preparadas , que incluem consultas parametrizadas mais mecanismos semelhantes para solicitações que não são de consulta. A instrução preparada é compilada com certas partes marcadas como somente leitura, para que um invasor não possa alterar essas partes do programa e outras partes marcadas como dados não executáveis (os parâmetros da instrução preparada), nos quais o invasor pode injetar dados, mas que nunca será tratado como código de programa, eliminando assim a maior parte do potencial de abuso.
Certamente, higienizar a entrada do usuário é bom, mas para estar realmente seguro, você precisa ser paranóico (ou, equivalente, pensar como um invasor). Uma superfície de controle fora do texto do programa é a maneira de fazer isso, e instruções preparadas fornecem essa superfície de controle para SQL. Portanto, não surpreende que declarações preparadas e, portanto, consultas parametrizadas, sejam a abordagem recomendada pela grande maioria dos profissionais de segurança.
fonte
Eu já escrevi sobre isso aqui: https://stackoverflow.com/questions/6786034/can-parameterized-statement-stop-all-sql-injection/33033576#33033576
Mas, apenas para simplificar:
A maneira como as consultas parametrizadas funcionam é que o sqlQuery é enviado como uma consulta e o banco de dados sabe exatamente o que essa consulta fará, e só então inserirá o nome de usuário e as senhas apenas como valores. Isso significa que eles não podem efetuar a consulta, porque o banco de dados já sabe o que a consulta fará. Portanto, nesse caso, ele procuraria um nome de usuário "Ninguém OU 1 = 1 '-" e uma senha em branco, que deve aparecer como falsa.
Porém, essa não é uma solução completa e a validação de entrada ainda precisará ser feita, pois isso não afetará outros problemas, como ataques XSS, pois você ainda pode colocar o javascript no banco de dados. Se isso for lido em uma página, ele será exibido como javascript normal, dependendo de qualquer validação de saída. Então, a melhor coisa a fazer ainda é usar a validação de entrada, mas usar consultas parametrizadas ou procedimentos armazenados para interromper qualquer ataque SQL
fonte
Eu nunca usei SQL. Mas obviamente você ouve quais problemas as pessoas têm e os desenvolvedores de SQL tiveram problemas com essa coisa de "injeção de SQL". Durante muito tempo, não consegui descobrir. E então percebi que as pessoas estavam criando instruções SQL, instruções de origem SQL textuais reais, concatenando seqüências de caracteres, algumas das quais inseridas por um usuário. E meu primeiro pensamento nessa realização foi choque. Choque total. Pensei: como alguém pode ser tão ridiculamente estúpido e criar declarações em qualquer linguagem de programação como essa? Para um desenvolvedor de C, C ++, Java ou Swift, isso é loucura total.
Dito isso, não é muito difícil escrever uma função C que use uma string C como argumento e produza uma string diferente que se pareça exatamente com uma literal de string no código-fonte C que representa a mesma string. Por exemplo, essa função converteria abc em "abc" e "abc" em "\" abc \ "" e "\" abc \ "" em "\" \\ "abc \\" \ "". (Bem, se isso parece errado para você, é html. Estava certo quando eu o digitei, mas não quando é exibido.) E uma vez que a função C é escrita, não é difícil gerar código fonte C onde o texto de um campo de entrada fornecido pelo usuário é transformado em um literal de string C. Isso não é difícil de proteger. Por que os desenvolvedores de SQL não usariam essa abordagem como uma maneira de evitar injeções de SQL está além de mim.
"Higienizar" é uma abordagem totalmente falha. A falha fatal é que ela torna certas entradas do usuário ilegais. Você acaba com um banco de dados em que um campo de texto genérico não pode conter texto como; Solte a tabela ou o que você usaria em uma injeção SQL para causar danos. Acho isso inaceitável. Se um banco de dados armazena texto, ele deve poder armazenar qualquer texto. E a falha prática é que o desinfetante parece não acertar :-(
Obviamente, consultas parametrizadas são o que qualquer programador usando uma linguagem compilada estaria esperando. Torna a vida muito mais fácil: você tem alguma entrada de string e nem se importa em convertê-la em uma string SQL, mas apenas a transmite como parâmetro, sem chance de nenhum caractere dessa string causar dano.
Portanto, do ponto de um desenvolvedor que usa linguagens compiladas, higienizar é algo que nunca me ocorreria. A necessidade de higienização é insana. Consultas parametrizadas são a solução óbvia para o problema.
(Achei a resposta de Josip interessante. Ele basicamente diz que, com consultas parametrizadas, você pode interromper qualquer ataque contra o SQL, mas pode ter um texto em seu banco de dados usado para criar uma injeção de JavaScript :-( Bem, temos o mesmo problema novamente , e não sei se o Javascript tem uma solução para isso.
fonte
O principal problema é que os hackers encontraram maneiras de cercar o saneamento, enquanto as consultas parametrizadas eram um procedimento existente que funcionava perfeitamente com os benefícios extras de desempenho e memória.
Algumas pessoas simplificam o problema como "são apenas aspas simples e duplas", mas os hackers encontraram maneiras inteligentes de evitar a detecção, como usar codificações diferentes ou usar funções de banco de dados.
De qualquer forma, você só precisava esquecer uma única sequência para criar uma violação de dados catastrófica. Os hackers foram capazes de automatizar scripts para baixar o banco de dados completo com uma série ou consultas. Se o software for conhecido como um pacote de código aberto ou um famoso conjunto de negócios, você poderá simplesmente anexar a tabela de usuários e senhas.
Por outro lado, o uso de consultas concatenadas era apenas uma questão de aprender a usar e se acostumar.
fonte