Como posso prevenir a injecção de SQL no PHP?

Se a entrada do Utilizador for inserida sem modificação numa consulta SQL, então a aplicação torna-se vulnerável a injecção SQL, como no seguinte exemplo:

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

Isso é porque o utilizador pode introduzir algo como value'); DROP TABLE table;--, e a consulta torna-se:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
O que se pode fazer para evitar que isto aconteça?

Author: Andrew G. Johnson, 2008-09-12

28 answers

Use declarações preparadas e consultas parametrizadas. estas são declarações SQL que são enviadas e processadas pelo servidor de banco de dados separadamente de quaisquer parâmetros. Desta forma, é impossível para um atacante injectar SQL malicioso.

Você basicamente tem duas opções para conseguir isso:

  1. Utilizando DOP (para qualquer controlador de base de dados suportado):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute([ 'name' => $name ]);
    
    foreach ($stmt as $row) {
        // Do something with $row
    }
    
  2. Usando MySQLi (para MySQL):

    $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
    $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
    
    $stmt->execute();
    
    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
        // Do something with $row
    }
    

Se estiver a ligar-se a uma base de dados diferente do MySQL, existe uma segunda opção específica do controlador a que se pode referir (por exemplo, pg_prepare() e pg_execute() para o PostgreSQL). A DOP é a opção universal.


Configurar correctamente a ligação

Note que ao usar PDO para aceder a uma base de dados MySQL real as declarações preparadas não são usadas por omissão . Para corrigir isso você tem que desativar a emulação de preparado financeiro. Um exemplo de criação de uma ligação com DOP é:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'password');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

No exemplo acima, o modo de erro não é estritamente necessário, mas é aconselhável adicioná-lo. Desta forma, o script não vai parar com um Fatal Error quando algo corre mal. E isso dá ao Desenvolvedor a chance de catch qualquer erro(S) que são throw n Como PDOException s.

O que é obrigatório é, no entanto, a primeira linha {[[12]}, que diz à DOP para desactivar as declarações preparadas emuladas e para utilizar declarações preparadas. Isto garante que a declaração e os valores não são processados pelo PHP antes de enviá-lo para o servidor MySQL (dando a um possível atacante nenhuma chance de injetar SQL malicioso).

Embora você possa definir o charset nas opções do construtor, é importante notar que os "antigos" versões do PHP (antes de 5.3.6) silenciosamente ignorado o parâmetro charset no DSN.


Explicação

A declaração SQL que passas para prepare é processado e compilado pelo servidor de banco de dados. Ao indicar os parâmetros (quer um ? quer um parâmetro nomeado como :name no exemplo acima), você diz ao motor de base de dados onde deseja filtrar. Então, quando você ligar para execute, a instrução preparada é combinada com os valores dos parâmetros que você especificar.

O importante aqui é que os valores dos parâmetros são combinados com a declaração compilada, não com uma string SQL. A injeção de SQL funciona enganando o roteiro para incluir maliciosos strings quando cria SQL para enviar para a base de dados. Então, ao enviar o SQL real separadamente dos parâmetros, você limita o risco de acabar com algo que você não pretendia.

Todos os parâmetros que enviar ao usar uma declaração preparada serão apenas tratados como strings (embora o motor de banco de dados possa fazer alguma optimização para que os parâmetros possam acabar como números também, é claro). No exemplo acima, se a variável $name contiver 'Sarah'; DELETE FROM employees o resultado seria simplesmente uma busca pela string "'Sarah'; DELETE FROM employees", and you will not end up with an empty table .

Outro benefício de usar declarações preparadas é que se você executar a mesma declaração muitas vezes na mesma sessão, ela só será processada e compilada uma vez, dando-lhe alguns ganhos de velocidade.

Oh, e uma vez que perguntou como fazê-lo para uma inserção, eis um exemplo (usando DOP):

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute([ 'column' => $unsafeValue ]);

As declarações preparadas podem ser utilizadas para consultas dinâmicas?

Enquanto ainda pode usar declarações preparadas para os parâmetros da consulta, a estrutura da consulta dinâmica em si não pode ser parametrizada e certas funcionalidades da consulta não podem ser parametrizadas.

Para estes cenários específicos, a melhor coisa a fazer é usar um filtro whitelist que restrinja os valores possíveis.

// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}
 9176
Author: PeeHaa, 2020-06-06 15:28:28

Aviso Depreciado: O código de amostra desta resposta (como o código de amostra da pergunta) usa a extensão de PHP MySQL, que foi depreciada em PHP 5.5.0 e removida inteiramente em PHP 7.0.

Aviso de segurança: esta resposta não está em conformidade com as melhores práticas de segurança. escapar é inadequado para prevenir a injecção de SQL , utilizar instruções preparadas em vez disso. Use a estratégia delineada abaixo por sua conta e risco. [Também, mysql_real_escape_string() foi removido. em PHP 7.)

Se estiver a usar uma versão recente do PHP, a opção {[[4]} descrita abaixo deixará de estar disponível (embora mysqli::escape_string Seja um equivalente moderno). Actualmente, a opção mysql_real_escape_string só faria sentido para o código legado numa versão antiga do PHP.


Você tem duas opções-escapar dos caracteres especiais no seu unsafe_variable, ou usando uma consulta parametrizada. Ambos o protegeriam da injecção de SQL. A consulta parametrizada é considerada a melhor prática, mas irá necessita de mudar para uma extensão MySQL mais recente em PHP antes de poder usá-la.

Vamos cobrir a corda de impacto inferior escapando primeiro.
//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

Ver também os detalhes do mysql_real_escape_string função.

Para usar a consulta parametrizada, você precisa usar MySQLi em vez das funções MySQL. Para reescrever o seu exemplo, precisaríamos de algo como o seguinte.

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

A função chave que você vai querer ler lá seria mysqli::prepare.

Também, como outros sugeriram, pode ser útil / mais fácil subir uma camada de abstracção com algo comoDOP .

Por favor, note que o caso que perguntou é bastante simples e que casos mais complexos podem requerer abordagens mais complexas. Em especial:

  • Se quiser alterar a estrutura do SQL com base na entrada do utilizador, as consultas parametrizadas não irão ajudar, e a fuga necessária não é coberta por mysql_real_escape_string. Neste tipo de caso, você seria melhor passar a entrada do Usuário através de uma lista branca para garantir que apenas os valores "seguros" são permitidos através.
  • se usar inteiros da entrada do utilizador numa condição e tomar a abordagem mysql_real_escape_string, irá sofrer do problema descrito por polinomial nos comentários abaixo. Este caso é mais complicado porque os inteiros não seriam rodeados por aspas, pelo que poderá lidar com isso validando que a entrada do utilizador contém apenas algarismo.
  • Há provavelmente outros casos que desconheço. Você pode encontrar este é um recurso útil em alguns dos problemas mais sutis que você pode encontrar.
 1683
Author: Matt Sheppard, 2019-11-21 11:13:20
Todas as respostas aqui cobrem apenas uma parte do problema. De fato, existem Quatro diferentes partes de consulta que podemos adicionar ao SQL dinamicamente: -
  • um texto
  • um número
  • um identificador
  • uma palavra-chave de sintaxe

E as declarações preparadas abrangem apenas dois deles.

Mas às vezes temos que tornar nossa consulta ainda mais dinâmica, adicionando operadores ou identificadores também. Por isso, vamos precisar de uma protecção diferente. tecnico. De um modo geral, esta abordagem de protecção baseia-se na whitelisting .

Neste caso, todos os parâmetros dinâmicos devem ser codificados no seu programa e escolhidos a partir desse conjunto. Por exemplo, para fazer uma ordenação dinâmica:

$orders  = array("name", "price", "qty"); // Field names
$key = array_search($_GET['sort'], $orders)); // if we have such a name
$orderby = $orders[$key]; // If not, first one will be set automatically. 
$query = "SELECT * FROM `table` ORDER BY $orderby"; // Value is safe

Para facilitar o processo, escrevi uma função auxiliar da lista branca que faz todo o trabalho numa linha:

$orderby = white_list($_GET['orderby'], "name", ["name","price","qty"], "Invalid field name");
$query  = "SELECT * FROM `table` ORDER BY `$orderby`"; // sound and safe
Há outra maneira de garantir identificadores - escapando, mas prefiro manter a lista branca como mais robusta e explícita. abordagem. No entanto, enquanto você tiver um identificador Citado, você pode escapar do caráter de Citação para torná-lo seguro. Por exemplo, por padrão para o mysql você tem que dobrar o caráter de Citação para escapar dele . Para outros DBMS escapando regras seria diferente.

Ainda assim, há um problema com palavras-chave de sintaxe SQL (tais como AND, DESC e tal), mas a listagem branca parece ser a única abordagem neste caso.

Assim, uma recomendação geral pode ser redigida como

  • qualquer variável que representa um SQL dados literal, (ou, para colocá - lo simplesmente-uma string SQL, ou um número) deve ser adicionado através de uma declaração preparada. Sem Excepções.
  • qualquer outra parte da consulta, como uma palavra - chave SQL, uma tabela ou um nome de campo, ou um operador-deve ser filtrada através de uma lista branca.

Actualização

Apesar de existir um Acordo Geral sobre as melhores práticas em matéria de protecção da injecção de SQL, existem ainda muitas más práticas. e alguns deles muito profundamente enraizados nas mentes dos usuários de PHP. Por exemplo, nesta mesma página há (embora invisível para a maioria dos visitantes) mais de 80 respostas apagadas - todas removidas pela comunidade devido à má qualidade ou promovendo práticas más e desactualizadas. Pior ainda, algumas das más respostas não são apagadas, mas prosperam.

Por exemplo, lá(1) são(2) ainda(3) muitos(4) respostas(5), incluindo o segundo mais resposta upvoted sugerindo a fuga manual de cordas - uma abordagem desactualizada que se provou ser insegura.

Ou há uma resposta um pouco melhor que sugere apenas outro método de formatação de cordas e até mesmo ostenta como a panaceia final. Mas é claro que não é. Este método não é melhor do que a formatação regular de string, mas mantém todas as suas desvantagens: é aplicável apenas a strings e, como qualquer outra formatação manual, é essencialmente opcional, medida não obrigatória, propensa a qualquer tipo de erro humano.

Penso que tudo isto por causa de uma superstição muito antiga, apoiada por autoridades como OWASP ou o manual do PHP, que proclama a igualdade entre qualquer que seja a "fuga" e a protecção das injecções de SQL.

Independentemente do que o manual do PHP dizia há muito tempo., *_escape_string de modo algum torna os dados seguros e nunca foi previsto fazê-lo. Além de ser inútil para qualquer parte SQL que não seja string, manual escapar é errado, porque é manual como oposto ao automatizado.

E o OWASP torna-o ainda pior, insistindo em escapar à entrada do utilizador o que é um absurdo absoluto: não deve haver tais palavras no contexto da protecção da injecção. Cada variável é potencialmente perigosa - não importa a fonte! Ou, em outras palavras - cada variável tem que ser formatada corretamente para ser colocada em uma consulta - não importa a fonte novamente. É o destino que importa. No momento em que um desenvolvedor começa para separar as ovelhas das cabras (pensando se alguma variável em particular é "Segura" ou não) ele/ela dá o seu primeiro passo para o desastre. Sem mencionar que mesmo a formulação sugere fuga em massa no ponto de entrada, assemelhando - se ao recurso de citações muito mágicas-já desprezado, depreciado e removido.

Portanto, ao contrário de qualquer "fuga", as declarações preparadas são a medida que efectivamente protege da injecção de SQL (quando aplicável).

 1098
Author: Your Common Sense, 2020-06-20 09:12:55

Recomendaria a utilização de DOP (objectos de dados PHP) para executar consultas parametrizadas de SQL.

Isto não só protege contra a injecção de SQL, como também acelera as consultas.

E utilizando DOP em vez de mysql_, mysqli_, e as funções pgsql_, você faz sua aplicação um pouco mais abstraída da base de dados, na rara ocorrência que você tem que mudar de provedores de banco de dados.

 861
Author: Kibbee, 2019-07-15 12:48:22

Utilizar {[1] } e as consultas preparadas.

($conn is a PDO object)

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();
 630
Author: Imran, 2015-09-11 17:02:40
Como podem ver, as pessoas sugerem que usem declarações preparadas no máximo. Não é errado, mas quando sua consulta é executada apenas uma vez por processo, haveria uma pequena penalidade de desempenho. Estava a enfrentar este problema, mas acho que o resolvi de uma forma muito sofisticada, a forma como os hackers evitam usar citações. Usei isto em conjunto com declarações preparadas emuladas. Eu uso-o para prevenir todos os tipos de possível injecção de SQL ataque.

A minha abordagem:

  • Se você espera que a entrada seja inteira certifique-se que é a sério? inteiro. Em uma linguagem de tipo variável como PHP é este Muito importante. Você pode usar, por exemplo, esta solução muito simples, mas poderosa: sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • Se esperares mais alguma coisa do inteiro hex it . Se o enfeitiçares, escaparás perfeitamente a toda a informação. Em C / C++ há uma função chamada mysql_hex_string(), em PHP você pode usar bin2hex().

    Não se preocupe com o facto de que a corda fugitiva terá um tamanho de 2x do seu comprimento original, porque mesmo que use mysql_real_escape_string, o PHP tem de atribuir a mesma capacidade ((2*input_length)+1), que é a mesma.

  • Este método hex é muitas vezes usado quando você transfere dados binários, mas eu não vejo nenhuma razão para não usá-lo em todos os dados para evitar ataques de injeção SQL. Note que você tem que pré-enviar dados com 0x ou usar a função MySQL UNHEX Sim.

Então, por exemplo, a consulta:

SELECT password FROM users WHERE name = 'root';

Tornar-se-á:

SELECT password FROM users WHERE name = 0x726f6f74;

Ou

SELECT password FROM users WHERE name = UNHEX('726f6f74');
Hex é a fuga perfeita. Não há forma de injectar.

Diferença entre a função UNHEX e o prefixo 0x

Houve alguma discussão nos comentários, por isso, quero deixar isto bem claro. Estas duas abordagens são muito semelhantes, mas são um pouco diferentes em alguns aspectos:

O prefixo 0x só pode ser usado para colunas de dados como char, varchar, text, block, binary, etc.
Além disso, o seu uso é um pouco complicado se você estiver prestes a inserir uma string vazia. Terá que substituí-lo completamente por '', ou terá um erro.

UNHEX() Funciona em qualquer coluna ; não tem de se preocupar com o texto vazio.


Os métodos Hex são frequentemente usados como ataques

Note que este método hex é muitas vezes usado como um ataque de injeção SQL onde os inteiros são como cordas e escaparam apenas com mysql_real_escape_string. Então você pode evitar o uso de citações.

Por exemplo, se fizeres algo assim:

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

Um ataque Pode Injectar-te muito facilmente. Considere o seguinte código injectado devolvido do seu programa:

SELECT ... WHERE id = -1 UNION ALL SELECT table_name FROM information_schema.tables;

E agora apenas extrair a estrutura da tabela:

SELECT ... WHERE id = -1 UNION ALL SELECT column_name FROM information_schema.column WHERE table_name = __0x61727469636c65__;

E depois selecciona os dados que quiseres. Não é fixe?

Mas se o codificador de um local injetável o enviasse, nenhuma injecção seria possível porque a consulta ficaria assim:

SELECT ... WHERE id = UNHEX('2d312075...3635');
 564
Author: Zaffy, 2020-06-12 03:31:08

Aviso Depreciado: O código de amostra desta resposta (como o código de amostra da pergunta) usa a extensão de PHP MySQL, que foi depreciada em PHP 5.5.0 e removida inteiramente em PHP 7.0.

Aviso de segurança : esta resposta não está em conformidade com as melhores práticas de segurança. escapar é inadequado para prevenir a injecção de SQL , usar declarações preparadas em vez disso. Use a estratégia delineada abaixo por sua conta e risco. (Também, mysql_real_escape_string() foi removido em PHP 7.)

Importante

A melhor maneira de prevenir a injecção de SQL é utilizar declarações preparadas em vez de escapar, Como a resposta aceite demonstra.

Existem bibliotecas como Aura.Sql e EasyDB que permitem aos programadores usar as declarações preparadas mais facilmente. Para saber mais sobre as razões pelas quais as declarações preparadas são melhores em parar a injecção de SQL, consulte Este mysql_real_escape_string() bypass e fixaram recentemente vulnerabilidades de injeção Unicode SQL no WordPress .

Prevenção da injecção - mysql_real_escape_string()

O PHP tem uma função especial para prevenir estes ataques. Tudo que você precisa fazer é usar a boca cheia de uma função, mysql_real_escape_string.

mysql_real_escape_string pega uma string que vai ser usada em uma consulta MySQL e retorna a mesma string com todas as tentativas de injeção SQL escapadas com segurança. Basicamente, vai substituir aqueles aspas problemáticas(') um utilizador pode introduzir com um substituto MySQL-safe, uma citação escapada \'.

Nota: {[10] } deve estar ligado à base de dados para usar esta função!

/ / Connect to MySQL

$name_bad = "' OR 1'"; 

$name_bad = mysql_real_escape_string($name_bad);

$query_bad = "SELECT * FROM customers WHERE username = '$name_bad'";
echo "Escaped Bad Injection: <br />" . $query_bad . "<br />";


$name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; 

$name_evil = mysql_real_escape_string($name_evil);

$query_evil = "SELECT * FROM customers WHERE username = '$name_evil'";
echo "Escaped Evil Injection: <br />" . $query_evil;

Você pode encontrar mais detalhes em MySQL-SQL prevenção da injecção.

 504
Author: rahularyansharma, 2019-05-09 03:05:39

Podias fazer algo básico como isto:

$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
Isto não vai resolver todos os problemas, mas é um bom trampolim. Eu deixei de fora itens óbvios, tais como verificar a existência da variável, formato (números, letras, etc.).
 471
Author: Kiran Maniya, 2019-06-18 10:23:01

Seja o que for que acabes por usar, certifica-te que verificas que a tua entrada ainda não foi mutilada por {[[[0]} ou por qualquer outro lixo bem intencionado, e se necessário, passa-a por stripslashes ou seja lá o que for para a desinfectar.

 385
Author: Rob, 2017-12-25 14:40:48

Aviso Depreciado: O código de amostra desta resposta (como o código de amostra da pergunta) usa a extensão de PHP MySQL, que foi depreciada em PHP 5.5.0 e removida inteiramente em PHP 7.0.

Aviso de segurança : esta resposta não está em conformidade com as melhores práticas de segurança. escapar é inadequado para prevenir a injecção de SQL , usar declarações preparadas em vez disso. Use a estratégia delineada abaixo por sua conta e risco. (Também, mysql_real_escape_string() foi removido em PHP 7.)

Consulta parametrizada e validação de entrada é o caminho a seguir. Existem muitos cenários sob os quais a injecção de SQL pode ocorrer, apesar de mysql_real_escape_string() ter sido utilizado.

Estes exemplos são vulneráveis à injecção de SQL:

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

Ou

$order = isset($_GET['o']) ? $_GET['o'] : 'userid';
$order = mysql_real_escape_string($order);
RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

Em ambos os casos, não pode usar ' para proteger a encapsulação.

Origem: a injecção inesperada de SQL (ao escapar não é suficiente)

 367
Author: Cedric, 2019-05-09 02:59:38

Na minha opinião, a melhor maneira de evitar geralmente a injecção de SQL na sua aplicação PHP (ou em qualquer aplicação web, já agora) é pensar na arquitectura da sua aplicação. Se a única maneira de proteger contra a injecção de SQL é lembrar-se de usar um método ou função especial que faz a coisa certa cada vez que você fala com a base de dados, você está fazendo isso errado. Dessa forma, é apenas uma questão de tempo até que você se esqueça de formatar corretamente a sua consulta em algum momento em seu codigo.

A adopção do padrão MVC e de uma estrutura como CakePHP ou CodeIgniter é provavelmente o caminho certo a seguir: tarefas comuns como a criação de consultas seguras de bases de dados foram resolvidas e implementadas centralmente nesses quadros. Eles ajudam-no a organizar a sua aplicação web de uma forma sensata e fazem-no pensar mais sobre carregar e salvar objectos do que sobre a construção segura de consultas SQL.

 315
Author: Johannes Fahrenkrug, 2014-07-16 02:05:53

Eu favor procedimentos armazenados (o MySQL tem suporte de procedimentos armazenados desde 5.0 de um ponto de vista de segurança - as vantagens são-

  1. a maioria das bases de dados (incluindo o MySQL permite que o acesso do utilizador seja restrito à execução de procedimentos armazenados. O controle de acesso de segurança de qualidade é útil para evitar a escalada de ataques de privilégios. Isso evita que aplicações comprometidas sejam capazes de executar SQL diretamente contra o banco.
  2. eles abstêm a consulta SQL raw da aplicação para que menos Informações da estrutura da base de dados esteja disponível para a aplicação. Isso torna mais difícil para as pessoas entender a estrutura subjacente do banco de dados e projetar ataques adequados.
  3. eles aceitam apenas parâmetros, por isso as vantagens das consultas parametrizadas estão lá. Claro-IMO você ainda precisa sanitar a sua entrada-especialmente se você está usando SQL dinâmico dentro do armazenado procedimento.

As desvantagens são -

  1. Eles (procedimentos armazenados) são difíceis de manter e tendem a multiplicar-se muito rapidamente. Isso torna a gestão deles um problema.
  2. Eles não são muito adequados para consultas dinâmicas - se eles são construídos para aceitar o código dinâmico como parâmetros, então muitas das vantagens são negadas.
 304
Author: Nikhil, 2017-12-25 14:43:05

Existem muitas formas de prevenir injecções de SQL e outras invasões de SQL. Você pode facilmente encontrá-lo na Internet (Google Search). É claro que a DOP é uma das boas soluções. mas gostaria de lhe sugerir algumas boas ligações prevenção da injecção de SQL.

O que é a injecção de SQL e como evitar

Manual do PHP para injecção de SQL

Microsoft explanation of SQL injection and prevention in PHP

E outros como prevenção da injecção de SQL com MySQL e PHP.

Agora, porque é que precisa de evitar a sua consulta da injecção de SQL?

Gostaria de lhe dizer porque é que tentamos evitar a injecção de SQL com um pequeno exemplo abaixo:

Pesquisa para a correspondência de autenticação de login:

$query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";
Agora, se alguém (um hacker) colocar
$_POST['email']= [email protected]' OR '1=1

E senha qualquer coisa....

A consulta será processada em o sistema só pode atingir:

$query="select * from users where email='[email protected]' OR '1=1';

A outra parte será rejeitada. Então, o que vai acontecer? Um usuário não autorizado (hacker) será capaz de entrar como administrador sem ter sua senha. Agora, pode fazer qualquer coisa que o administrador/e-mail pessoa pode fazer. É muito perigoso se a injecção de SQL não for evitada.

 304
Author: Manish Shrivastava, 2020-02-22 17:39:17

Acho que se alguém quiser usar o PHP e o MySQL ou outro servidor de base de dados:

  1. pense em aprenderDOP (objectos de dados PHP) – é uma camada de acesso à base de dados que fornece um método uniforme de acesso a múltiplas bases de dados.
  2. pensa em aprenderMySQLi
  3. usar funções nativas do PHP como: strip_tags, mysql_real_escape_string ou se variável numérica, apenas (int)$foo. Leia mais sobre o tipo de variáveis em PHP Aqui. Se estiver a utilizar bibliotecas como a DOP ou a MySQLi, utilize sempre DOP: quote () e mysqli_real_escape_string () .

Exemplos de Bibliotecas:

---- DOP

----- não há espaços livres - prontos para a injecção de SQL! É mau.

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");

----- substituições sem nome

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);

----- substituições nomeadas

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)");

--- MySQLi

$request = $mysqliConnection->prepare('
       SELECT * FROM trainers
       WHERE name = ?
       AND email = ?
       AND last_login > ?');

    $query->bind_param('first_param', 'second_param', $mail, time() - 3600);
    $query->execute();

P. S:

A DOP ganha esta batalha com facilidade. Com o apoio de doze diferentes drivers de banco de dados e parâmetros nomeados, podemos ignorar o pequena perda de desempenho, e se acostumar com a sua API. De um Segurança ponto de vista, ambos são seguros desde que o desenvolvedor os use. a forma como devem ser usados.

Mas enquanto tanto a DOP como a MySQLi são bastante rápidos, a MySQLi actua insignificantemente mais rápido em benchmarks- ~ 2,5% para não preparados declarações, e ~6,5% para as preparadas.

E por favor, teste cada consulta à sua base de dados - é uma maneira melhor de prevenir a injeção.

 269
Author: RDK, 2014-07-16 02:27:44

Se possível, escolha os tipos dos seus parâmetros. Mas só funciona em tipos simples como int, bool e float.

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
 259
Author: devOp, 2018-05-01 16:51:42

Se quiser aproveitar-se dos motores de cache, como Redis ou Memcached, talvez DALMP possa ser uma escolha. Ele usa pure MySQLi . Assinale isto: Dalmp camada de abstracção da Base de dados para o MySQL usando o PHP.

Além disso, você pode 'preparar' seus argumentos antes de preparar sua consulta para que você possa construir consultas dinâmicas e no final tenha uma consulta de declarações totalmente preparada. Dalmp camada de extracção de bases de dados para o MySQL usando o PHP.

 233
Author: Peter Mortensen, 2017-12-25 14:45:42

Para aqueles que não sabem como utilizar DOP (vindo das funções mysql_), fiz um invólucro muito, muito simples da DOP que é um ficheiro único. Ele existe para mostrar como é fácil fazer todas as coisas comuns aplicações precisam ser feitas. Trabalha com PostgreSQL, MySQL e SQLite.

Basicamente, leia enquanto lê o manual para ver como colocar as funções DOP na vida real para tornar simples armazenar e recuperar valores no formato Você querer.

Quero uma única coluna.

$count = DB::column('SELECT COUNT(*) FROM `user`);

I want an array (key => value) results (i.e. for making a selectbox)

$pairs = DB::pairs('SELECT `id`, `username` FROM `user`);

Eu quero um resultado de Fila Única

$user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id));

Quero uma série de resultados.

$banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));
 224
Author: Xeoncross, 2017-12-25 14:47:00

Usando esta função PHP {[3] } pode obter uma boa prevenção de uma forma rápida.

Por exemplo:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'

mysql_escape_string - escapa de um texto para ser usado num mysql_ query

Para mais Prevenção, pode adicionar no final ...

wHERE 1=1   or  LIMIT 1
Finalmente tens ...
SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1
 223
Author: Nicolas Finelli, 2017-12-25 14:46:07

Algumas orientações para escapar de caracteres especiais nas declarações SQL.

Não utilize MySQL . Esta extensão está desactualizada. Utilizar MySQLi ou DOP em vez disso.

MySQLi

Para escapar manualmente de caracteres especiais num texto, poderá usar a funçãomysqli_ real_ escape_string . A função não funcionará corretamente a menos que o conjunto de caracteres correto seja definido com mysqli_set_charset .

Exemplo:

$mysqli = new mysqli('host', 'user', 'password', 'database');
$mysqli->set_charset('charset');

$string = $mysqli->real_escape_string($string);
$mysqli->query("INSERT INTO table (column) VALUES ('$string')");

Para escapar automaticamente dos valores com declarações preparadas, usar o mysqli_prepare , e o mysqli_stmt_bind_ param onde os tipos das variáveis de ligação correspondentes devem ser fornecidos para uma conversão adequada:

Exemplo:

$stmt = $mysqli->prepare("INSERT INTO table (column1, column2) VALUES (?,?)");

$stmt->bind_param("is", $integer, $string);

$stmt->execute();

Não importa se você usa declarações preparadas ou mysqli_real_escape_string, você sempre tem que saber o tipo de dados de entrada que você está trabalhando.

Por isso, se utilizar um preparado declaração, você deve especificar os tipos das variáveis para a função mysqli_stmt_bind_param.

E o uso de mysqli_real_escape_string é para, como o nome diz, escapar de caracteres especiais em uma string, de modo que não vai tornar os inteiros seguros. O objetivo desta função é evitar quebrar as cadeias de caracteres em declarações SQL, e os danos ao banco de dados que poderia causar. mysqli_real_escape_string é uma função útil quando usado corretamente, especialmente quando combinado com sprintf.

Exemplo:

$string = "x' OR name LIKE '%John%";
$integer = '5 OR id != 0';

$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5

$integer = '99999999999999999999';
$query = sprintf("SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647
 221
Author: Danijel, 2019-12-12 06:36:05

A alternativa simples a este problema poderia ser resolvida através da concessão de permissões adequadas na própria base de dados. Por exemplo: se estiver a usar uma base de dados MySQL, introduza a base de dados através do terminal ou da IU fornecida e basta seguir este comando:

 GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';

Isto irá restringir o utilizador a ficar confinado apenas com a consulta especificada. Remova a permissão de exclusão e assim os dados nunca seriam apagados da consulta disparada da página PHP. A segunda coisa a fazer é limpar os privilégios para que o MySQL actualize as permissões e actualizações.

FLUSH PRIVILEGES; 

Mais informações sobre flush .

Para ver os privilégios actuais para o utilizador, dispare a seguinte pesquisa.

select * from mysql.user where User='username';

Saiba mais sobreGRANT .

 184
Author: Apurv Nerlekar, 2018-11-19 13:25:25
Em relação a muitas respostas úteis, espero acrescentar algum valor a este tópico.

A injecção de SQL é um ataque que pode ser feito através de entradas do utilizador (entradas que foram preenchidas por um utilizador e depois utilizadas dentro de consultas). Os padrões de injeção SQL são sintaxe de consulta correta enquanto podemos chamá-lo de: consultas ruins por razões ruins, e assumimos que pode haver uma pessoa ruim que tenta obter informações secretas (contornando o controle de acesso) que afetam os três princípios de segurança (confidencialidade, integridade e disponibilidade).

Agora, nosso ponto é para evitar ameaças à segurança, tais como ataques de injeção de SQL, a pergunta (como evitar um ataque de injeção de SQL utilizando PHP), ser mais realista, filtragem de dados ou limpar os dados de entrada é o caso quando o usuário utilizar a entrada de dados dentro de tal consulta, usando PHP ou qualquer outra linguagem de programação não é o caso, ou conforme recomendado por mais pessoas a usar a tecnologia moderna, tais como a instrução preparada ou quaisquer outros instrumentos que actualmente a apoiar SQL injection prevention, considera que estas ferramentas já não estão disponíveis? Como você segura sua aplicação?

A minha abordagem contra a injecção de SQL é: limpar os dados de entrada do utilizador antes de Os enviar para a base de dados (antes de os usar dentro de qualquer consulta).

Filtragem de dados para (converter dados inseguros para dados seguros)

Considere que DOP e MySQLi não estão disponíveis. Como você pode garantir sua aplicação? Obrigas-me a usá-los? E que tal outras línguas para além do PHP? Prefiro fornecer ideias gerais, uma vez que podem ser utilizadas para uma fronteira mais vasta, e não apenas para uma língua específica.

  1. utilizador SQL( limiting user privilege): as operações SQL mais comuns são (SELECT, UPDATE, INSERT), então, por que dar o privilégio de actualização a um utilizador que não o necessita? Por exemplo, a autenticação e as páginas de pesquisa só estão a usar seleccionar, então, porque usar os utilizadores do DB nestas páginas com privilégios elevados?

Regra: não criar um utilizador de base de dados para todos os privilégios. Para todas as operações SQL, você pode criar seu esquema como (deluser, selectuser, updateuser) como usernames para fácil utilização.

Verprincípio do menor privilégio .

  1. Filtragem de dados: antes de construir qualquer entrada de consulta do usuário, ele deve ser validado e filtrado. Para programadores, é importante definir algumas propriedades para cada variável de entrada de usuário: tipo de dados, padrão de dados e comprimento de Dados . Um campo que é o número entre (x e y) deve ser validado exatamente usando a regra exata, e para um campo que é uma string (texto): padrão é o caso, por exemplo, um nome de usuário deve conter apenas alguns caracteres, digamos [a-zA-Z0-9_-.]. O comprimento varia entre (x e n) onde x e n (inteiros, x

  2. Use OUTRAS FERRAMENTAS: aqui, eu também concordarei com você que uma declaração preparada (consulta parametrizada) e procedimentos armazenados. As desvantagens aqui são estas maneiras requerem habilidades avançadas que não existem para a maioria dos usuários. A idéia básica aqui é distinguir entre a consulta SQL e os dados que são usados dentro. Ambas as abordagens podem ser usadas mesmo com dados inseguros, porque os dados de entrada do Usuário aqui não adicionam nada à consulta original, como (qualquer ou x=x).

Para mais informações, leia por favor a ficha de prevenção da injecção de OWASP SQL.

Agora, se você é um usuário avançado, comece a usar esta defesa como quiser, mas, para iniciantes, se eles não podem implementar rapidamente um procedimento armazenado e preparou a declaração, é melhor filtrar os dados de entrada o máximo que puderem.

Finalmente, vamos considerar que um utilizador envia este texto abaixo em vez de introduzir o seu nome de Utilizador:

[1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'

Esta entrada pode ser verificada precocemente sem qualquer declaração preparada e procedimentos armazenados, mas para estar no lado seguro, a sua utilização começa após a filtragem de dados pelo utilizador e validação.

O último ponto é detectar um comportamento inesperado que requer mais esforço e complexidade; não é recomendado para aplicações web normais.

O comportamento inesperado na entrada acima é selecionar, união, se, SUBSTRING, BENCHMARK, SHA, e root. Uma vez que estas palavras detectadas, você pode evitar a entrada.

Actualização 1:

Um usuário comentou que este post é inútil, OK! Aqui está o que OWASP.ORG fornecido:

Defesas primárias:

Opção #1: utilização de declarações preparadas (consultas parametrizadas)
Opção #2: utilização dos procedimentos armazenados
Opção #3: escapar a todos os dados fornecidos pelo Utilizador

Defesas adicionais:

Também Fazer Cumprir: Menos Privilégios
Realizar Também: Validação De Entrada Da Lista Branca
Como deve saber, alegar um artigo deve ser apoiado por um argumento válido, pelo menos por uma referência! Caso contrário, é considerado um ataque. e uma má reivindicação!

Actualização 2:

Do manual do PHP, PHP: declarações preparadas-Manual:

Fuga e injecção de SQL

As variáveis de ligação serão escapadas automaticamente pelo servidor. O o servidor insere os seus valores escapados nos locais apropriados no modelo de declaração antes da execução. Uma dica deve ser fornecida ao servidor para o tipo de variável encadernada, para criar uma variável apropriada conversao. Veja o função mysqli_stmt_bind_ param() para mais informacao.

A fuga automática dos valores dentro do servidor é por vezes considerado uma característica de segurança para prevenir a injecção de SQL. Mesmo o grau de segurança pode ser alcançado com declarações não preparadas se os valores de entrada são escapados corretamente.

Actualização 3:

Criei casos de teste para saber como a DOP e a MySQLi enviam a consulta ao servidor MySQL quando utilizam uma preparação declaração:

DOP:

$user = "''1''"; // Malicious keyword
$sql = 'SELECT * FROM awa_user WHERE userame =:username';
$sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':username' => $user));

Registo Da Pesquisa:

    189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\''
    189 Quit

MySQLi:

$stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) {
$stmt->bind_param("s", $user);
$user = "''1''";
$stmt->execute();

Registo Da Pesquisa:

    188 Prepare   SELECT * FROM awa_user WHERE username =?
    188 Execute   SELECT * FROM awa_user WHERE username ='\'\'1\'\''
    188 Quit
[130]é claro que uma declaração preparada também está escapando dos dados, nada mais.

Como também mencionado na declaração acima,

A fuga automática dos valores dentro do servidor é por vezes considerada uma característica de segurança para evitar a injecção de SQL. O mesmo grau de a segurança pode ser alcançada com declarações não preparadas, se os valores de entrada forem escapados correctamente

Portanto, isto prova que a validação de dados como intval() é uma boa ideia para valores inteiros antes de enviar qualquer consulta. Além disso, prevenir dados maliciosos do Usuário antes de enviar a consulta é uma abordagem correta e válida .

Por favor, veja esta pergunta para mais detalhes.: a DOP envia uma consulta em bruto ao MySQL enquanto o Mysqli envia uma consulta preparada, ambas produzem a mesma resultado

Referências:

  1. SQL Injection Cheat Sheet
  2. Injecção de SQL
  3. segurança da Informação
  4. Princípios De Segurança
  5. validação dos Dados
 177
Author: 18 revs, 8 users 53%user1646111, 2019-07-15 13:31:54

Aviso de segurança: esta resposta não está de acordo com as melhores práticas de segurança. escapar é inadequado para prevenir a injecção de SQL , usar declarações preparadas em vez disso. Use a estratégia delineada abaixo por sua conta e risco. (Also, mysql_real_escape_string() was removed in PHP 7.)

Aviso desactualizado: a extensão mysql está desactualizada neste momento. recomendamos utilizar a extensão DOP

Eu uso três maneiras diferentes de evitar que a minha aplicação web seja vulnerável à injecção de SQL.
  1. uso de mysql_real_escape_string(), que é uma função pré-definida em PHP , e este código adiciona barras invertidas aos seguintes caracteres: \x00, \n, \r, \, ', " e \x1a Passar os valores de entrada como parâmetros para minimizar a probabilidade de injecção de SQL.
  2. a forma mais avançada é usar DOP.
Espero que isto te ajude.

Considere o seguinte: pesquisa:

$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

O Mysql_real_escape_string () não irá proteger aqui. Se você usar aspas simples (' ') em torno de suas variáveis dentro de sua consulta é o que o protege contra isso. Aqui está uma solução abaixo para isto:

$iId = (int) mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

Esta pergunta tem boas respostas sobre isto.

Sugiro que utilizar DOP seja a melhor opção.

Editar:

mysql_real_escape_string() está desactualizado a partir de PHP 5.5.0. Usar o mysqli ou PROVEDORIA.

Uma alternativa ao mysql_real_escape_string () é

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

Exemplo:

$iId = $mysqli->real_escape_string("1 OR 1=1");
$mysqli->query("SELECT * FROM table WHERE id = $iId");
 176
Author: Soumalya Banerjee, 2019-05-09 03:00:18

Uma forma simples seria usar um framework PHP como CodeIgniter ou Laravel que tem funcionalidades internas como filtragem e registo activo para que não tenha de se preocupar com estas nuances.

 170
Author: Deepak Thomas, 2017-12-25 14:50:57

Atenção: a abordagem descrita nesta resposta aplica-se apenas a cenários muito específicos e não é segura, uma vez que os ataques de injecção SQL não dependem apenas de ser capaz de injectar X=Y.

Se os atacantes estão a tentar invadir o formulário através da variável PHP $_GET ou com o texto de pesquisa da URL, você seria capaz de os apanhar se não estiverem seguros.

RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php

Porque 1=1, 2=2, 1=2, 2=1, 1+1=2, etc... são as perguntas comuns a uma base de dados SQL de um invasor. Talvez também seja usado por muitas aplicações de hacking.

Mas você deve ter cuidado, que você não deve reescrever uma consulta segura a partir do seu site. O código acima está dando uma dica, para reescrever ou redirecionar (depende de você) que hackers dinâmica específica de seqüência de caracteres de consulta em uma página que irá armazenar o atacante endereço IP, ou até MESMO os SEUS COOKIES, histórico do navegador ou qualquer outra informação confidencial, para que você possa lidar com eles mais tarde, pelo banimento da sua conta ou contactar as autoridades.

 148
Author: 5ervant, 2019-07-15 13:40:48

Uma boa ideia é usar um mapeador relacional de objectos como Idiorm:

$user = ORM::for_table('user')
->where_equal('username', 'j4mie')
->find_one();

$user->first_name = 'Jamie';
$user->save();

$tweets = ORM::for_table('tweet')
    ->select('tweet.*')
    ->join('user', array(
        'user.id', '=', 'tweet.user_id'
    ))
    ->where_equal('user.username', 'j4mie')
    ->find_many();

foreach ($tweets as $tweet) {
    echo $tweet->text;
}

Não só o salva das injecções de SQL, mas também dos erros de sintaxe! Ele também suporta coleções de modelos com encadeamento de método para filtrar ou aplicar ações a múltiplos resultados de uma vez e múltiplas conexões.

 130
Author: Thomas Ahle, 2020-03-29 15:35:46

Existem tantas respostas para PHP e MySQL , mas aqui está o código para PHP e Oracle para prevenir a injecção de SQL, bem como o uso regular de controladores oci8:

$conn = oci_connect($username, $password, $connection_string);
$stmt = oci_parse($conn, 'UPDATE table SET field = :xx WHERE ID = 123');
oci_bind_by_name($stmt, ':xx', $fieldval);
oci_execute($stmt);
 128
Author: Chintan Gor, 2016-05-25 13:51:10

Aviso Depreciado: O código de amostra desta resposta (como o código de amostra da pergunta) usa a extensão de PHP MySQL, que foi depreciada em PHP 5.5.0 e removida inteiramente em PHP 7.0.

Aviso de segurança : esta resposta não está em conformidade com as melhores práticas de segurança. escapar é inadequado para prevenir a injecção de SQL , usar declarações preparadas em vez disso. Use a estratégia delineada abaixo por sua conta e risco. (Também, mysql_real_escape_string() foi removido em PHP 7.)

A utilização de DOP e MYSQLi é uma boa prática para prevenir injecções de SQL, mas se realmente quer trabalhar com funções e consultas MySQL, seria melhor usar

Mysql_real_escape_string

$unsafe_variable = mysql_real_escape_string($_POST['user_input']);

Existem mais habilidades para prevenir isso: como identificar-se a entrada é uma cadeia, número, char ou array, existem tantas funções incorporadas para detectar isso. Além disso, seria melhor usar estas funções para verificar a entrada dado.

Is_string

$unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : '');

Is_numérico

$unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : '');

E é muito melhor usar essas funções para verificar os dados de entrada com mysql_real_escape_string.

 124
Author: Rakesh Sharma, 2019-05-09 03:00:59
Escrevi esta pequena função há vários anos.
function sqlvprintf($query, $args)
{
    global $DB_LINK;
    $ctr = 0;
    ensureConnection(); // Connect to database if not connected already.
    $values = array();
    foreach ($args as $value)
    {
        if (is_string($value))
        {
            $value = "'" . mysqli_real_escape_string($DB_LINK, $value) . "'";
        }
        else if (is_null($value))
        {
            $value = 'NULL';
        }
        else if (!is_int($value) && !is_float($value))
        {
            die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\'s type is '. gettype($value). '.');
        }
        $values[] = $value;
        $ctr++;
    }
    $query = preg_replace_callback(
        '/{(\\d+)}/', 
        function($match) use ($values)
        {
            if (isset($values[$match[1]]))
            {
                return $values[$match[1]];
            }
            else
            {
                return $match[0];
            }
        },
        $query
    );
    return $query;
}

function runEscapedQuery($preparedQuery /*, ...*/)
{
    $params = array_slice(func_get_args(), 1);
    $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results.   
    return $results;
}

Isto permite a execução de declarações numa cadeia de caracteres C# - ish.Formato como:

runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);

Escapa considerando o tipo variável. Se você tentar parameterizar a tabela, os nomes das colunas, ela falharia, pois colocaria cada string entre aspas, o que é uma sintaxe inválida.

Actualização de segurança: as injecções permitidas pela versão anterior str_replace Adicionando tokens {#} nos dados do utilizador. Esta versão preg_replace_callback não causa problemas se a substituição contém estas fichas.

 87
Author: Calmarius, 2017-12-25 14:55:27