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

172

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

A 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 escapados corretamente. Se o modelo de instrução original não for derivado de entrada externa, a injeção SQL não poderá ocorrer.

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

Aan
fonte

Respostas:

290

A ideia é muito simples - a consulta e os dados são enviados ao servidor de banco de dados separadamente .
Isso é tudo.

A raiz do problema de injeção de SQL está na mistura do código e dos dados.

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

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

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";

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 parte do programa, para que os dados possam alterar o programa e, dependendo dos dados transmitidos, teremos uma saída regular ou uma tabela usersexcluída.

Embora, no caso de declarações preparadas, não alteremos nosso programa, ele permanece intacto.
Esse é o ponto.

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 espaço reservado.

Observe que exatamente a mesma consulta é enviada ao servidor, sem dados! E então estamos enviando os dados com a segunda solicitação, essencialmente separados da própria consulta:

$db->execute($data);

portanto, não pode alterar nosso programa e causar algum dano.
Muito simples - não é?

A única coisa que devo acrescentar é que sempre omitido em todos os manuais:

As instruções preparadas podem proteger apenas literais de dados , mas não podem ser usadas com nenhuma outra parte da consulta.
Assim, uma vez que precisamos adicionar, digamos, um identificador dinâmico - um nome de campo, por exemplo - as instruções preparadas não podem nos ajudar. Eu expliquei o assunto recentemente , então não vou me repetir.

Seu senso comum
fonte
2
"por exemplo, por padrão, a DOP não usa instruções preparadas" - não é exatamente verdade, porque a DOP emula instruções preparadas apenas para drivers que não suportam esse recurso.
pinepain
3
@ zaq178miami: "O DOP emula instruções preparadas apenas para drivers que não suportam o recurso" - não é exatamente verdade. O MySQL suporta instruções preparadas já há algum tempo. O driver PDO também. Mas, no entanto, as consultas MySQL ainda foram preparadas pelo DOP por padrão, da última vez que verifiquei.
Chao
9
O que é diferente entre $spoiled_data = "1; DROP TABLE users;"-> $query = "SELECT * FROM users where id=$spoiled_data";, comparado com: $db->prepare("SELECT * FROM users where id=?");-> $data = "1; DROP TABLE users;"-> $db->execute($data);. Eles não farão a mesma coisa?
precisa saber é o seguinte
14
@Juha Untinen Os dados podem ser qualquer coisa. Não analisará os dados. Esse é o DATA, não o comando. Portanto, mesmo que os dados $ contenham comandos sql, eles não serão executados. Além disso, se o ID for um número, o conteúdo da sequência gerará um relatório ou valor zero.
Soley
21

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 classe Inject é vulnerável à injeção de SQL. A consulta é colada dinamicamente junto com a entrada do usuário. O objetivo da consulta era mostrar informações sobre Bob. Salário ou bônus, com base nas informações do usuário. Mas o usuário mal-intencionado manipula a entrada que corrompe a consulta, aplicando o equivalente a uma cláusula 'ou true' na 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 o uso normal e o segundo com a injeção maliciosa:

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 concatenação de string da entrada do usuário. Ele não é apenas vulnerável à injeção, mas também tem implicações de armazenamento em cache no servidor (a instrução é alterada, é menos provável obter um cache de instrução SQL, enquanto o exemplo de ligação está sempre executando a mesma instrução).

Aqui está um exemplo de ligação 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 entrada do exemplo anterior mostra que o código malicioso não funciona porque não há paymentType correspondente a essa sequência:

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=?
Glenn
fonte
O uso de uma instrução preparada do programa que se conecta ao banco de dados tem o mesmo efeito que o uso de uma instrução preparada que faz parte do banco de dados? Por exemplo, o Postgres tem sua própria declaração preparada e o uso impediria a injeção de SQL? postgresql.org/docs/9.2/static/sql-prepare.html
Celeritas
@Celeritas Não tenho uma resposta definitiva para o Postgresql. Observando os documentos, parece que o efeito é o mesmo. PREPAREcria uma instrução nomeada fixa que já está analisada (ou seja, a instrução não será mais alterada, independentemente da entrada), enquanto EXECUTEexecutará a instrução nomeada vinculando os parâmetros. Como PREPAREsó tem duração de sessão, parece realmente que foi feito por motivos de desempenho, não para impedir a injeção via scripts psql. Para acesso ao psql, poderia conceder permissões aos procedimentos armazenados e vincular os parâmetros nos procs.
Glenn
@Celeritas Tentei o código acima usando o PostgreSQL 11.1 no x86_64 e o exemplo do SQLi acima funcionou.
Krishna Pandey
15

Basicamente, com instruções preparadas, os dados provenientes de um hacker em potencial são tratados como dados - e não há como eles serem misturados com o aplicativo SQL e / ou interpretados como SQL (o que pode acontecer quando os dados passados ​​são colocados diretamente no seu aplicativo). 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 vêm de um formulário posteriormente - naquele momento, a consulta é realmente executada.

Mais informações excelentes aqui:

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

Jose
fonte
6

Li as respostas e ainda senti a necessidade de enfatizar o ponto principal que ilumina a essência das Declarações Preparadas. Considere duas maneiras de consultar o banco de dados em que a entrada do usuário está envolvida:

Abordagem ingênua

Um concatena a entrada do usuário com alguma string SQL parcial para gerar uma instrução SQL. Nesse caso, o usuário pode incorporar comandos SQL maliciosos, que serão enviados ao banco de dados para execução.

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

Por exemplo, a entrada maliciosa do usuário pode levar a SQLStringser igual a"SELECT * FROM CUSTOMERS WHERE NAME='James';DROP TABLE CUSTOMERS;'

Devido ao usuário mal-intencionado, SQLStringcontém 2 instruções, sendo que a segunda ( "DROP TABLE CUSTOMERS") causará danos.

Declarações Preparadas

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

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

N.Vegeta
fonte
Realmente? A resposta aceita não diz exatamente isso?
Seu senso comum
@ Seu senso comum A resposta aceita é preenchida com muitas informações valiosas, mas me fez pensar no que implicam os detalhes de implementação da separação dos dados e da consulta. Considerando que focar no ponto em que os dados injetados maliciosamente (se houver) nunca seria executado atinge a cabeça da unha.
N.Vegeta
E quais "detalhes de implementação" são fornecidos na sua resposta que não estão lá?
Seu senso comum
se você tentar ver de onde eu venho, perceberá que meu argumento é o seguinte: O breve desejo de ver os detalhes da implementação surgiu da necessidade de entender o motivo explícito pelo qual a entrada mal-intencionada do usuário não causaria prejuízo. Não há muita necessidade de ver os detalhes da implementação. É por isso que perceber que os detalhes da implementação eram tais que, em nenhum momento o SQL inserido com intuito malicioso seria executado, enviava a mensagem para casa. Sua resposta responde à pergunta, como (conforme solicitado) ?, mas eu imagino que outras pessoas (como eu) ficariam satisfeitas com uma resposta sucinta ao porquê?
N.Vegeta
Considere isso um enriquecimento que elucida o ponto crucial, e não como uma crítica implícita (recenlty percebeu quem era o autor da resposta aceita).
N.Vegeta
5

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 para que o DBMS os utilize como parâmetros de consulta para execução (parametrização). O DBMS não usa os dados que você vincula como um complemento para a 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 seu relacionamento com o DBMS evita isso.

wulfgarpro
fonte
4

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

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

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

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

E o restante é comentado depois --, para que nunca seja executado e ignorado como usando o exemplo de instrução preparada 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();

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

lloydom
fonte
3

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

Tudo é tratado para você.

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

Vamos supor que você tenha isso em um Servlet, certo. Se uma pessoa malévola passou um valor ruim para 'filtro', você pode invadir seu banco de dados.

MeBigFatGuy
fonte
0

Causa Raiz # 1 - O Problema do Delimitador

A injeção de sql é possível porque usamos aspas para delimitar seqüências de caracteres e também para fazer parte delas, tornando impossível às vezes interpretá-las. 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. As consultas de estrutura fazem isso.

Causa raiz # 2 - A natureza humana, as pessoas são astutas e algumas astuciosas 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ê comete um erro em uma consulta estruturada, isso não torna seu sistema vulnerável à injeção de sql. Se você não estiver usando consultas estruturadas, erros podem gerar vulnerabilidade de injeção de sql.

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

Consultas estruturadas resolvem o problema do delimitador, colocando comandos sql em uma instrução e colocando os dados em uma instrução de programação separada. As instruções de programação criam a separação necessária.

Consultas estruturadas ajudam a impedir 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 o erro humano normal nessas abordagens geralmente leva a pelo menos alguma exposição à injeção de sql. As consultas estruturadas são protegidas contra falhas da injeção de sql. Você pode cometer todos os erros do mundo, quase, com consultas estruturadas, iguais a qualquer outra programação, mas nenhuma delas pode ser transformada em um sistema assumido pela 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, aí está, as causas da injeção de sql e as consultas estruturadas da natureza que as tornam impossíveis quando usadas.

DanAllen
fonte