Como um PreparedStatement evita ou impede a injeção de SQL?

122

Eu sei que o PreparedStatements evita / impede a injeção de SQL. Como isso acontece? A consulta final do formulário criada usando PreparedStatements será uma sequência ou não?

Prabhu R
fonte
3
Tecnicamente, a especificação JDBC não insiste que não há falhas na injeção de SQL. Não conheço nenhuma unidade afetada.
Tom Hawtin - tackline
3
@ Jayesh Sugiro adicionar o conteúdo do seu blog como resposta aqui. A maioria das respostas diz apenas as diferenças entre a geração dinâmica de consultas SQL e o stmt preparado. Eles não estão abordando a questão de por que as declarações preparadas funcionam melhor que o seu blog.
Pavan Manjunath
1
Adicionado como resposta, espero que ajude.
Jayesh

Respostas:

78

O problema com a injeção SQL é que uma entrada do usuário é usada como parte da instrução SQL. Usando instruções preparadas, você pode forçar a entrada do usuário a ser tratada como o conteúdo de um parâmetro (e não como parte do comando SQL).

Mas se você não usar a entrada do usuário como parâmetro para sua instrução preparada, mas construir seu comando SQL juntando cadeias, ainda estará vulnerável às injeções de SQL, mesmo ao usar instruções preparadas.

tangens
fonte
1
Claro, mas você ainda pode codificar alguns ou todos os seus parâmetros.
tangens 17/10/09
16
Exemplo por favor - Mas se você não usar a entrada do usuário como parâmetro para sua instrução preparada, mas construir seu comando SQL juntando cadeias, você ainda estará vulnerável a injeções de SQL, mesmo ao usar instruções preparadas.
David blaine
4
FWIW Instruções preparadas não são uma coisa JDBC - elas são uma coisa SQL. Você pode preparar e executar instruções preparadas de dentro de um console SQL. PreparedStatement apenas os suporta de dentro do JDBC.
beldaz
198

Considere duas maneiras de fazer a mesma coisa:

PreparedStatement stmt = conn.createStatement("INSERT INTO students VALUES('" + user + "')");
stmt.execute();

Ou

PreparedStatement stmt = conn.prepareStatement("INSERT INTO student VALUES(?)");
stmt.setString(1, user);
stmt.execute();

Se "usuário" veio da entrada do usuário e a entrada do usuário foi

Robert'); DROP TABLE students; --

Então, em primeira instância, você seria processado. No segundo, você estaria seguro e Little Bobby Tables seria registrado para sua escola.

Paul Tomblin
fonte
8
Portanto, se eu entendi direito, a consulta no segundo exemplo que será executado seria realmente: INSERIR EM VALORES DE ALUNO ("Robert '); DROP TABLE alunos; -") - ou pelo menos algo assim. Isso é verdade?
Max
18
Não, na PRIMEIRA instância, você obteria essa declaração. No segundo, ele inseria "Robert '); alunos DROP TABLE; -" na tabela de usuários.
21139 Paul Tomblin
3
Foi o que eu quis dizer, no segundo exemplo (o "seguro"), a string Robert '); Alunos da DROP TABLE; - serão salvos no campo na tabela do aluno. Eu escrevi outra coisa? ;)
Max
7
Desculpe, aninhar aspas é algo que tento evitar por causa de uma confusão como essa. É por isso que eu gosto de PreparedStatements com parâmetros.
21139 Paul Tomblin
59
Mesas de Bobby pequenas. XD Great reference
Amalgovinus 29/03
128

Para entender como o PreparedStatement impede a injeção de SQL, precisamos entender as fases da execução da Consulta ao SQL.

1. Fase de compilação. 2. Fase de execução.

Sempre que o mecanismo do SQL Server recebe uma consulta, ele precisa passar pelas fases abaixo,

Fases de execução da consulta

  1. Fase de análise e normalização: nesta fase, a consulta é verificada quanto à sintaxe e semântica. Ele verifica se as tabelas e colunas de referência usadas na consulta existem ou não. Ele também tem muitas outras tarefas, mas não vamos detalhar.

  2. Fase de compilação: nesta fase, as palavras-chave usadas em consultas como select, from, where etc são convertidas em formato compreensível por máquina. Essa é a fase em que a consulta é interpretada e a ação correspondente a ser tomada é decidida. Ele também tem muitas outras tarefas, mas não vamos detalhar.

  3. Plano de otimização de consulta: nesta fase, a Árvore de decisão é criada para encontrar as maneiras pelas quais a consulta pode ser executada. Ele descobre o número de maneiras pelas quais a consulta pode ser executada e o custo associado a cada maneira de executar a Consulta. Ele escolhe o melhor plano para executar uma consulta.

  4. Cache: o melhor plano selecionado no plano de otimização de consulta é armazenado em cache, para que, sempre que a próxima consulta entre na mesma consulta, ela não precise passar pela Fase 1, Fase 2 e Fase 3 novamente. Quando a consulta da próxima vez chegar, ela será verificada diretamente no cache e retirada de lá para ser executada.

  5. Fase de execução: nesta fase, a consulta fornecida é executada e os dados são retornados ao usuário como ResultSetobjeto.

Comportamento da API PreparedStatement nas etapas acima

  1. PreparedStatements não são consultas SQL completas e contém espaços reservados, que em tempo de execução são substituídos por dados reais fornecidos pelo usuário.

  2. Sempre que qualquer PreparedStatment que contém espaços reservados é passado para o mecanismo do SQL Server, ele passa pelas fases abaixo

    1. Fase de análise e normalização
    2. Fase de compilação
    3. Plano de otimização de consulta
    4. Cache (a consulta compilada com espaços reservados é armazenada no cache.)

UPDATE usuário definido nome de usuário =? e senha =? ONDE id =?

  1. A consulta acima será analisada, compilada com espaços reservados como tratamento especial, otimizada e será armazenada em cache. A consulta nesse estágio já está compilada e convertida em formato compreensível por máquina. Portanto, podemos dizer que a consulta armazenada no cache é pré-compilada e apenas os espaços reservados precisam ser substituídos pelos dados fornecidos pelo usuário.

  2. Agora, no tempo de execução, quando os dados fornecidos pelo usuário são recebidos, a Consulta Pré-Compilada é selecionada no Cache e os espaços reservados são substituídos pelos dados fornecidos pelo usuário.

PrepareStatementWorking

(Lembre-se, depois que os marcadores de posição são substituídos pelos dados do usuário, a consulta final não é compilada / interpretada novamente e o mecanismo do SQL Server trata os dados do usuário como dados puros e não como um SQL que precisa ser analisado ou compilado novamente; essa é a beleza do PreparedStatement. )

Se a consulta não precisar passar pela fase de compilação novamente, todos os dados substituídos nos espaços reservados serão tratados como dados puros e não terão significado para o mecanismo do SQL Server e executarão a consulta diretamente.

Nota: É a fase de compilação após a fase de análise que compreende / interpreta a estrutura da consulta e fornece um comportamento significativo a ela. No caso de PreparedStatement, a consulta é compilada apenas uma vez e a consulta compilada em cache é selecionada o tempo todo para substituir os dados do usuário e executar.

Devido ao recurso de compilação único do PreparedStatement, ele está livre de ataques de injeção de SQL.

Você pode obter explicações detalhadas com o exemplo aqui: https://javabypatel.blogspot.com/2015/09/how-prepared-statement-in-java-prevents-sql-injection.html

Jayesh
fonte
3
boa explicação #
Dheeraj Joshi
4
Literalmente a resposta completa mais sobre o como funciona peça
jouell
Foi muito útil. Obrigado pela explicação detalhada.
Unknown
26

O SQL usado em um PreparedStatement é pré-compilado no driver. A partir desse momento, os parâmetros são enviados ao driver como valores literais e não como partes executáveis ​​do SQL; portanto, nenhum SQL pode ser injetado usando um parâmetro. Outro efeito colateral benéfico de PreparedStatements (pré-compilação + envio apenas de parâmetros) é um desempenho aprimorado ao executar a instrução várias vezes, mesmo com valores diferentes para os parâmetros (supondo que o driver suporte PreparedStatements), pois o driver não precisa executar a análise e compilação do SQL cada tempo em que os parâmetros mudam.

Travis Heseman
fonte
Não precisa ser implementado assim, e acredito que muitas vezes não é.
Tom Hawtin - tackline
4
Na verdade, o SQL normalmente é pré-compilado no banco de dados. Ou seja, um plano de execução é preparado no banco de dados. Quando você executa a consulta, o plano é executado com esses parâmetros. O benefício extra é que a mesma instrução pode ser executada com parâmetros diferentes sem que o processador de consultas precise compilar um novo plano a cada vez.
beldaz
3

Eu acho que vai ser uma corda. Mas os parâmetros de entrada serão enviados ao banco de dados e as conversões / conversão apropriadas serão aplicadas antes da criação de uma instrução SQL real.

Para dar um exemplo, ele pode tentar e ver se o CAST / Conversion funciona.
Se funcionar, poderá criar uma declaração final.

   SELECT * From MyTable WHERE param = CAST('10; DROP TABLE Other' AS varchar(30))

Tente um exemplo com uma instrução SQL aceitando um parâmetro numérico.
Agora, tente passar uma variável de string (com conteúdo numérico aceitável como parâmetro numérico). Isso gera algum erro?

Agora, tente passar uma variável de sequência (com conteúdo que não é aceitável como parâmetro numérico). Veja o que acontece?

shahkalpesh
fonte
3

A declaração preparada é mais segura. Ele converterá um parâmetro para o tipo especificado.

Por exemplo stmt.setString(1, user);, converterá o userparâmetro em uma String.

Suponha que o parâmetro contenha uma string SQL contendo um comando executável : o uso de uma instrução preparada não permitirá isso.

Ele adiciona metacaractere (também conhecido como conversão automática) a isso.

Isso torna mais seguro.

Guru R Handa
fonte
2

Injeção de SQL: quando o usuário tem a chance de inserir algo que possa fazer parte da instrução sql

Por exemplo:

Consulta de seqüência de caracteres = "INSERIR EM VALORES DOS ALUNOS ('” + usuário + "')"

quando o usuário digita "Robert '); Alunos da DROP TABLE; - ”como entrada, causa injeção SQL

Como a declaração preparada impede isso?

Consulta de seqüência de caracteres = "INSERIR EM VALORES DOS ALUNOS ('” + ": nome" + "')"

parameters.addValue ("nome", usuário);

=> quando o usuário inserir novamente “Robert '); Alunos da DROP TABLE; - “, a string de entrada é pré-compilada no driver como valores literais e acho que pode ser convertida como:

CAST ('Robert'); Alunos da DROP TABLE; - «AS varchar (30))

Portanto, no final, a string será literalmente inserida como o nome da tabela.

http://blog.linguiming.com/index.php/2018/01/10/why-prepared-statement-avoids-sql-injection/

jack
fonte
1
Se não me engano, a parte CAST(‘Robert’);de CAST(‘Robert’); DROP TABLE students; –‘ AS varchar(30))iria quebrar, então ele iria continuar a cair da mesa, se fosse esse o caso. Ele interrompe a injeção, então acredito que o exemplo não está completo o suficiente para explicar o cenário.
Héctor Álvarez
1

Declaração preparada:

1) A pré-compilação e o armazenamento em cache do lado do banco de dados da instrução SQL levam à execução geral mais rápida e à capacidade de reutilizar a mesma instrução SQL em lotes.

2) Prevenção automática de ataques de injeção de SQL por escape interno de aspas e outros caracteres especiais. Observe que isso requer que você use qualquer um dos métodos setedxx PreparedStatement () para definir o valor.

Mukesh Kumar
fonte
1

Conforme explicado neste post , o PreparedStatementsozinho não ajuda se você ainda estiver concatenando Strings.

Por exemplo, um invasor não autorizado ainda pode fazer o seguinte:

  • chame uma função de suspensão para que todas as suas conexões com o banco de dados fiquem ocupadas, tornando seu aplicativo indisponível
  • extraindo dados confidenciais do banco de dados
  • ignorando a autenticação do usuário

Não apenas o SQL, mas também o JPQL ou o HQL podem ser comprometidos se você não estiver usando parâmetros de ligação.

Bottom line, você nunca deve usar concatenação de seqüência de caracteres ao criar instruções SQL. Use uma API dedicada para esse fim:

Vlad Mihalcea
fonte
1
Obrigado por apontar a importância de usar a ligação de parâmetro, em vez de PreparedStatement sozinho. Sua resposta, no entanto, parece sugerir que o uso de uma API dedicada é necessário para proteger contra injeção de SQL. Como esse não é o caso, e o uso de PreparedStatement com ligação de parâmetro também funciona, você gostaria de reformular?
Wild Pottok
-3

Nas declarações preparadas, o usuário é forçado a inserir dados como parâmetros. Se o usuário digitar algumas instruções vulneráveis ​​como DROP TABLE ou SELECT * FROM USERS, os dados não serão afetados, pois serão considerados parâmetros da instrução SQL

Shreyas K
fonte
Mesma resposta que a resposta selecionada com menos precisões.
Julien Maret