Como as instruções preparadas podem proteger contra ataques de injeção de SQL?

Como as instruções preparadas nos ajudam a evitar ataques de injeção de SQL ?

Wikipedia diz:

As instruções preparadas são resilientes à injeção de SQL, porque os valores dos parâmetros, que são transmitidos posteriormente usando um protocolo diferente, não precisam ser corretamente ignorados. Se o modelo da instrução original não for derivado da input externa, a injeção SQL não poderá ocorrer.

Eu não consigo ver a razão muito bem. O que seria uma explicação simples em inglês fácil e alguns exemplos?

    A ideia é muito simples – a consulta e os dados são enviados para o servidor de database separadamente .
    Isso é tudo.

    A raiz do problema de injeção de SQL é a mistura do código e dos dados.

    Na verdade, nossa consulta SQL é um programa legítimo . E estamos criando um programa desse tipo dinamicamente, adicionando alguns dados na hora. Assim, esses dados podem interferir no código do programa e até mesmo alterá-lo, como todo exemplo de injeção SQL mostra (todos os exemplos em PHP / Mysql):

    $expected_data = 1; $query = "SELECT * FROM users where id=$expected_data"; 

    irá produzir uma consulta regular

     SELECT * FROM users where id=1 

    enquanto esse código

     $spoiled_data = "1; DROP TABLE users;" $query = "SELECT * FROM users where id=$spoiled_data"; 

    irá produzir uma sequência maliciosa

     SELECT * FROM users where id=1; DROP TABLE users; 

    Funciona porque estamos adicionando os dados diretamente ao corpo do programa e ele se torna uma parte do programa, portanto os dados podem alterar o programa e, dependendo dos dados transmitidos, teremos uma saída regular ou uma tabela excluída.

    Enquanto no caso de declarações preparadas não alteramos nosso programa, ele permanece intacto
    Essa é a questão.

    Estamos enviando um programa para o servidor primeiro

     $db->prepare("SELECT * FROM users where id=?"); 

    onde os dados são substituídos por alguma variável chamada parâmetro ou um espaço reservado.

    Observe que a mesma consulta está sendo enviada ao servidor, sem nenhum dado nele! E então estamos enviando os dados com a segunda solicitação, essencialmente separados da consulta em si:

     $db->execute($data); 

    então, não pode alterar nosso programa e causar qualquer dano.
    Muito simples – não é?

    No entanto, vale a pena observar que nem toda vez que você está usando um espaço reservado, ele é processado como uma instrução preparada .

    Um espaço reservado é uma idéia geral para replace os dados reais por uma variável para o processamento futuro (veja printf() por exemplo), enquanto uma instrução preparada é o único subconjunto dela.

    Existem casos (notavelmente o PDO no PHP pode fazer isso) quando uma instrução preparada pode ser emulada, e uma consulta é realmente composta junto com dados e enviada para o servidor em uma requisição. Mas é importante entender que essa abordagem é igualmente segura , porque todos os dados são formatados corretamente de acordo com o tipo e, portanto, nada de errado poderia acontecer.

    A única coisa que tenho que adicionar é sempre omitida em todos os manuais:

    As instruções preparadas podem proteger apenas dados , mas não podem defender o programa em si .
    Então, uma vez que temos que adicionar, digamos, um identificador dynamic – um nome de campo, por exemplo, declarações preparadas não podem nos ajudar. Eu expliquei o assunto recentemente , então não vou me repetir.

    Aqui está o SQL para configurar um exemplo:

     CREATE TABLE employee(name varchar, paymentType varchar, amount bigint); INSERT INTO employee VALUES('Aaron', 'salary', 100); INSERT INTO employee VALUES('Aaron', 'bonus', 50); INSERT INTO employee VALUES('Bob', 'salary', 50); INSERT INTO employee VALUES('Bob', 'bonus', 0); 

    A class Inject é vulnerável à injeção de SQL. A consulta é colada dinamicamente junto com a input do usuário. A intenção da consulta era mostrar informações sobre Bob. Ou salário ou bônus, com base na input do usuário. Mas o usuário mal-intencionado manipula a input corrompendo a consulta, adicionando o equivalente a um ‘ou true’ à cláusula where para que tudo seja retornado, incluindo as informações sobre Aaron que deveriam estar ocultas.

     import java.sql.*; public class Inject { public static void main(String[] args) throws SQLException { String url = "jdbc:postgresql://localhost/postgres?user=user&password=pwd"; Connection conn = DriverManager.getConnection(url); Statement stmt = conn.createStatement(); String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='" + args[0] + "'"; System.out.println(sql); ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount")); } } } 

    Executando isso, o primeiro caso é com uso normal e o segundo com a injeção mal-intencionada:

     c:\temp>java Inject salary SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary' salary 50 c:\temp>java Inject "salary' OR 'a'!='b" SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary' OR 'a'!='b' salary 100 bonus 50 salary 50 bonus 0 

    Você não deve construir suas instruções SQL com a concatenação de strings de input do usuário. Não apenas é vulnerável à injeção, mas também tem implicações de cache no servidor (a instrução muda, portanto, é menos provável obter um cache de instrução SQL, enquanto o exemplo de binding está sempre executando a mesma instrução).

    Aqui está um exemplo de binding para evitar esse tipo de injeção:

     import java.sql.*; public class Bind { public static void main(String[] args) throws SQLException { String url = "jdbc:postgresql://localhost/postgres?user=postgres&password=postgres"; Connection conn = DriverManager.getConnection(url); String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?"; System.out.println(sql); PreparedStatement stmt = conn.prepareStatement(sql); stmt.setString(1, args[0]); ResultSet rs = stmt.executeQuery(); while (rs.next()) { System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount")); } } } 

    Executar isso com a mesma input do exemplo anterior mostra que o código mal-intencionado não funciona porque não há paymentType correspondente a essa string:

     c:\temp>java Bind salary SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=? salary 50 c:\temp>java Bind "salary' OR 'a'!='b" SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=? 

    Basicamente, com instruções preparadas, os dados vindos de um hacker em potencial são tratados como dados – e não há como serem misturados com seu aplicativo SQL e / ou serem interpretados como SQL (o que pode acontecer quando os dados passados ​​são colocados diretamente em seu aplicação SQL).

    Isso ocorre porque as instruções preparadas “preparam” a consulta SQL primeiro para encontrar um plano de consulta eficiente e enviam os valores reais que, presumivelmente, chegam de um formulário mais tarde – nesse momento, a consulta é realmente executada.

    Mais ótimas informações aqui:

    Instruções preparadas e injeção de SQL

    No SQL Server , usar uma instrução preparada é definitivamente à prova de injeção porque os parâmetros de input não formam a consulta. Isso significa que a consulta executada não é uma consulta dinâmica. Exemplo de uma declaração vulnerável de injeção de SQL.

     string sqlquery = "select * from table where username='" + inputusername +"' and password='" + pass + "'"; 

    Agora, se o valor na variável inoutusername for algo como ‘ou 1 = 1 -, essa consulta agora se torna:

     select * from table where username='a' or 1=1 -- and password=asda 

    E o resto é comentado depois -- , por isso nunca é executado e ignorado como usando o exemplo preparado como abaixo.

     Sqlcommand command = new sqlcommand("select * from table where username = @userinput and password=@pass"); command.Parameters.Add(new SqlParameter("@userinput", 100)); command.Parameters.Add(new SqlParameter("@pass", 100)); command.prepare(); 

    Então, na verdade, você não pode enviar outro parâmetro, evitando assim a injeção de SQL …

    A frase chave need not be correctly escaped . Isso significa que você não precisa se preocupar com pessoas tentando jogar traços, apóstrofos, citações, etc …

    Tudo é tratado por você.

    Quando você cria e envia uma instrução preparada para o DBMS, ela é armazenada como a consulta SQL para execução.

    Posteriormente, você vincula seus dados à consulta, de modo que o DBMS use esses dados como parâmetros de consulta para execução (parametrização). O DBMS não usa os dados vinculados como complementares à consulta SQL já compilada; são simplesmente os dados.

    Isso significa que é fundamentalmente impossível executar a injeção de SQL usando instruções preparadas. A própria natureza das declarações preparadas e sua relação com o DBMS impede isso.

     ResultSet rs = statement.executeQuery("select * from foo where value = " + httpRequest.getParameter("filter"); 

    Vamos supor que você tenha isso em um Servlet. Se uma pessoa mal-intencionada passou um valor ruim para “filtro”, você pode hackear seu database.

    Eu li as respostas e ainda senti a necessidade de enfatizar o ponto chave que ilumina a essência das Declarações Preparadas. Considere duas maneiras de consultar o database em que a input do usuário está envolvida:

    Abordagem Ingênua

    Um concatena a input do usuário com alguma cadeia SQL parcial para gerar uma instrução SQL. Nesse caso, o usuário pode incorporar comandos SQL maliciosos, que serão então enviados ao database para execução.

     String SQLString = "SELECT * FROM CUSTOMERS WHERE NAME='"+userInput+"'" 

    Por exemplo, a input do usuário mal-intencionado pode fazer com que SQLString seja igual a "SELECT * FROM CUSTOMERS WHERE NAME='James';DROP TABLE CUSTOMERS;'

    Devido ao usuário mal-intencionado, SQLString contém 2 instruções, onde o segundo ( "DROP TABLE CUSTOMERS" ) causará danos.

    Declarações Preparadas

    Nesse caso, devido à separação da consulta e dos dados, a input do usuário nunca é tratada como uma instrução SQL e , portanto, nunca é executada . É por esse motivo que qualquer código SQL mal-intencionado injetado não causaria danos. Portanto, os "DROP TABLE CUSTOMERS" nunca seriam executados no caso acima.

    Em poucas palavras, com instruções preparadas, códigos maliciosos introduzidos via input do usuário não serão executados!

    Causa Raiz # 1 – O Problema Delimitador

    A injeção SQL é possível porque usamos aspas para delimitar as strings e também para ser partes de strings, tornando impossível interpretá-las às vezes. Se tivéssemos delimitadores que não pudessem ser usados ​​em dados de string, a injeção de sql nunca teria acontecido. Resolver o problema do delimitador elimina o problema de injeção de sql. Consultas de estrutura fazem isso.

    Causa Raiz # 2 – A Natureza Humana, as Pessoas são Astutas e Algumas Pessoas Astutas São Maliciosas e Todas as Pessoas Cometem Erros

    A outra causa raiz da injeção de sql é a natureza humana. Pessoas, incluindo programadores, cometem erros. Quando você cometer um erro em uma consulta estruturada, isso não tornará seu sistema vulnerável à injeção de sql. Se você não estiver usando consultas estruturadas, os erros podem gerar vulnerabilidade de injeção de SQL.

    Como as consultas estruturadas resolvem as causas principais da injeção SQL

    Consultas Estruturadas Resolva o Problema do Delimitador, colocando comandos sql em uma instrução e colocando os dados em uma instrução de programação separada. Instruções de programação criam a separação necessária.

    Consultas estruturadas ajudam a evitar que erros humanos criem falhas críticas de segurança. Com relação aos humanos cometendo erros, a injeção de SQL não pode acontecer quando as consultas de estrutura são usadas. Existem maneiras de impedir a injeção de SQL que não envolvem consultas estruturadas, mas erros humanos normais nessas abordagens geralmente levam a pelo menos alguma exposição à injeção de SQL. Consultas estruturadas são fail safe de injeção de sql. Você pode cometer quase todos os erros do mundo, com consultas estruturadas, como qualquer outra programação, mas nenhuma que você possa fazer pode ser transformada em um sistema tomado por injeção de SQL. É por isso que as pessoas gostam de dizer que este é o caminho certo para evitar a injeção de sql.

    Então, você tem isso, as causas da injeção de SQL e as consultas estruturadas da natureza que as tornam impossíveis quando são usadas.