test - Como posso evitar a injeção de SQL no PHP?




sql php id (19)

Aviso de segurança : essa resposta não está de acordo com as práticas recomendadas de segurança. Escapar é inadequado para impedir a injeção de SQL , use instruções preparadas . Use a estratégia descrita abaixo por sua conta e risco. (Além disso, mysql_real_escape_string() foi removido no PHP 7.)

Você poderia fazer algo básico como este:

$safe_variable = mysql_real_escape_string($_POST["user-input"]);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

Isso não resolverá todos os problemas, mas é um bom passo. Deixei itens óbvios, como verificar a existência da variável, formato (números, letras, etc.).

Se a entrada do usuário for inserida sem modificação em uma consulta SQL, o aplicativo ficará vulnerável à injeção de SQL , como no exemplo a seguir:

$unsafe_variable = $_POST['user_input']; 

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

Isso porque o usuário pode inserir algo como value'); DROP TABLE table;-- value'); DROP TABLE table;-- , e a consulta torna-se:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

O que pode ser feito para evitar que isso aconteça?


Aviso: O código de amostra desta resposta (como o código de amostra da pergunta) usa a extensão mysql do PHP, que foi preterida no PHP 5.5.0 e removida inteiramente no PHP 7.0.0.

Se você estiver usando uma versão recente do PHP, a opção mysql_real_escape_string descrita abaixo não estará mais disponível (embora mysqli::escape_string seja um equivalente moderno). Atualmente, a opção mysql_real_escape_string só faria sentido para o código legado em uma versão antiga do PHP.

Você tem duas opções: escapar dos caracteres especiais em sua unsafe_variable ou usar uma consulta parametrizada. Ambos protegeriam você da injeção de SQL. A consulta parametrizada é considerada a melhor prática, mas exigirá a mudança para uma nova extensão do mysql no PHP antes que você possa usá-la.

Nós vamos cobrir a menor corda de impacto 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

Veja também os detalhes da função mysql_real_escape_string .

Para usar a consulta parametrizada, você precisa usar o MySQLi em vez das funções do 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 .

Além disso, como outros sugeriram, talvez seja mais fácil aumentar uma camada de abstração com algo como PDO .

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

  • Se você quiser alterar a estrutura do SQL com base na entrada do usuário, as consultas parametrizadas não vão ajudar, e o escape necessário não é coberto por mysql_real_escape_string . Nesse tipo de caso, seria melhor passar a entrada do usuário por meio de uma lista de permissões para garantir que apenas valores "seguros" sejam permitidos.
  • Se você usar números inteiros a partir da entrada do usuário em uma condição e usar a abordagem mysql_real_escape_string , você sofrerá com o problema descrito por Polynomial nos comentários abaixo. Este caso é mais complicado porque números inteiros não seriam cercados por aspas, então você poderia lidar com a validação de que a entrada do usuário contém apenas dígitos.
  • Há provavelmente outros casos dos quais não conheço. Você pode achar que this é um recurso útil em alguns dos problemas mais sutis que você pode encontrar.

Como você pode ver, as pessoas sugerem que você use instruções preparadas no máximo. Não está errado, mas quando sua consulta é executada apenas uma vez por processo, haverá uma pequena penalidade de desempenho.

Eu estava enfrentando esse problema, mas acho que resolvi de maneira muito sofisticada - a maneira como os hackers usam para evitar o uso de cotações. Eu usei isso em conjunto com instruções preparadas emuladas. Eu uso isso para evitar todos os tipos de possíveis ataques de injeção de SQL.

Minha abordagem:

  • Se você espera que a entrada seja inteira, certifique-se de que ela seja realmente inteira. Em uma linguagem de tipo variável como o PHP, isso é 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 você espera algo mais do inteiro hexa ele . Se você fizer isso, você irá escapar perfeitamente de todas as entradas. Em C / C ++ existe uma função chamada mysql_hex_string() , no PHP você pode usar bin2hex() .

    Não se preocupe com o fato de que a string de escape terá um tamanho 2x de seu comprimento original, porque mesmo se você usar mysql_real_escape_string , o PHP terá que alocar a mesma capacidade ((2*input_length)+1) , que é o mesmo.

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

Então, por exemplo, a consulta:

SELECT password FROM users WHERE name = 'root'

Se tornará:

SELECT password FROM users WHERE name = 0x726f6f74

ou

SELECT password FROM users WHERE name = UNHEX('726f6f74')

Hex é o escape perfeito. Não há maneira de injetar.

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

Houve alguma discussão nos comentários, então eu finalmente quero deixar claro. Essas 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, texto, bloco, binário, etc.
Além disso, seu uso é um pouco complicado se você estiver prestes a inserir uma string vazia. Você terá que substituí-lo completamente por '' , ou você receberá um erro.

UNHEX () funciona em qualquer coluna; você não precisa se preocupar com a string vazia.

Métodos hexadecimais são frequentemente usados ​​como ataques

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

Por exemplo, se você fizer algo assim:

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

um ataque pode injetar você com muita facilidade . Considere o seguinte código injetado retornado do seu script:

SELECT ... WHERE id = -1 union todos selecionam table_name de information_schema.tables

e agora apenas extrair a estrutura da tabela:

SELECT ... WHERE id = -1 union all seleciona column_name de information_schema.column em que table_name = 0x61727469636c65

Em seguida, basta selecionar os dados desejados. Não é legal?

Mas se o codificador de um site injetável iria hexadecima-lo, nenhuma injeção seria possível porque a consulta ficaria assim: SELECT ... WHERE id = UNHEX('2d312075...3635')


Consulta parametrizada e validação de entrada é o caminho a percorrer. Existem muitos cenários sob os quais a injeção SQL pode ocorrer, mesmo que mysql_real_escape_string() tenha sido usado.

Esses exemplos são vulneráveis ​​à injeçã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, você não pode usar ' para proteger o encapsulamento.

Source : A Injeção Inesperada de SQL (Quando Escapar Não É Suficiente)


Na minha opinião, a melhor maneira de impedir a injeção de SQL em seu aplicativo PHP (ou em qualquer aplicativo da Web) é pensar na arquitetura do seu aplicativo. Se a única maneira de se proteger contra a injeção de SQL é lembrar-se de usar um método ou função especial que faz o Right Thing toda vez que você fala com o banco de dados, você está fazendo errado. Dessa forma, é apenas uma questão de tempo até que você esqueça de formatar corretamente sua consulta em algum momento do seu código.

Adotar o padrão MVC e uma estrutura como o CakePHP ou o CodeIgniter é provavelmente o caminho certo a seguir: Tarefas comuns como criar consultas de banco de dados seguras foram resolvidas e implementadas centralmente em tais estruturas. Eles ajudam você a organizar seu aplicativo da Web de uma maneira sensata e fazer com que você pense mais em carregar e salvar objetos do que em construir com segurança consultas SQL únicas.


O que quer que você faça, certifique-se de verificar se sua entrada ainda não foi destroçada por magic_quotes ou algum outro lixo bem-intencionado e, se necessário, execute-o em stripslashes ou qualquer stripslashes coisa para desinfetá-lo.


Use PDO e consultas preparadas.

( $conn é um objeto PDO )

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();

Use instruções preparadas e consultas parametrizadas. Estas são instruções SQL que são enviadas e analisadas pelo servidor de banco de dados separadamente de qualquer parâmetro. Desta forma, é impossível para um invasor injetar SQL malicioso.

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

  1. Usando o PDO (para qualquer driver de banco de dados suportado):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute(array('name' => $name));
    
    foreach ($stmt as $row) {
        // do something with $row
    }
    
  2. Usando o 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 você está se conectando a um banco de dados diferente do MySQL, existe uma segunda opção específica do driver que você pode consultar (por exemplo, pg_prepare() e pg_execute() para o PostgreSQL). ODP é a opção universal.

Corrigindo corretamente a conexão

Observe que, ao usar o PDO para acessar um banco de dados MySQL, instruções reais preparadas não são usadas por padrão . Para corrigir isso, você deve desativar a emulação de instruções preparadas. Um exemplo de criação de uma conexão usando o PDO é:

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

$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 der errado. E dá ao desenvolvedor a chance de catch qualquer erro (s) que são PDOException como PDOException s.

O que é obrigatório , no entanto, é a primeira linha setAttribute() , que diz ao PDO para desabilitar instruções preparadas emuladas e usar instruções preparadas reais . Isso garante que a instrução e os valores não sejam analisados ​​pelo PHP antes de enviá-lo para o servidor MySQL (dando a um possível invasor a chance de injetar SQL malicioso).

Embora você possa definir o charset nas opções do construtor, é importante observar que as versões 'mais antigas' do PHP (<5.3.6) ignoraram silenciosamente o parâmetro charset no DSN.

Explicação

O que acontece é que a instrução SQL que você passa a prepare é analisada e compilada pelo servidor de banco de dados. Especificando parâmetros (um ou um parâmetro nomeado como :name no exemplo acima), você diz ao mecanismo de banco de dados onde deseja filtrar. Então, quando você chamar execute , a instrução preparada será combinada com os valores de parâmetros especificados.

O importante aqui é que os valores dos parâmetros são combinados com a instrução compilada, não com uma string SQL. A injeção de SQL funciona enganando o script para incluir strings maliciosas quando ele cria o SQL para enviar ao banco de dados. Então, enviando o SQL real separadamente dos parâmetros, você limita o risco de acabar com algo que não pretendia. Quaisquer parâmetros que você enviar ao usar uma instrução preparada serão tratados apenas como strings (embora o mecanismo do banco de dados possa fazer alguma otimizaçã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 'Sarah'; DELETE FROM employees o resultado seria simplesmente uma busca pela string "'Sarah'; DELETE FROM employees" , e você não terminará com uma tabela vazia .

Outro benefício do uso de instruções preparadas é que, se você executar a mesma instrução várias vezes na mesma sessão, ela será analisada e compilada apenas uma vez, fornecendo alguns ganhos de velocidade.

Ah, e desde que você perguntou sobre como fazer isso para uma inserção, aqui está um exemplo (usando PDO):

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

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

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

Embora você ainda possa usar instruções preparadas para os parâmetros de consulta, a estrutura da consulta dinâmica em si não pode ser parametrizada e determinados recursos de consulta não podem ser parametrizados.

Para esses cenários específicos, a melhor coisa a fazer é usar um filtro de lista de permissões 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';
}

Eu acho que se alguém quiser usar PHP e MySQL ou algum outro servidor dataBase:

  1. Pense em aprender PDO (PHP Data Objects) - é uma camada de acesso ao banco de dados que fornece um método uniforme de acesso a vários bancos de dados.
  2. Pense em aprender MySQLi
  3. Use 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 here . Se você estiver usando bibliotecas como o PDO ou o MySQLi, use sempre PDO::quote() e mysqli_real_escape_string() .

Exemplos de bibliotecas:

---- DOP

----- Sem espaços reservados - prontos para injeção de SQL! É mau

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

----- espaços reservados sem nome

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

----- espaços reservados nomeados

$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();

PS :

PDO vence esta batalha com facilidade. Com suporte para doze diferentes drivers de banco de dados e parâmetros nomeados, podemos ignorar a pequena perda de desempenho e nos acostumar com sua API. Do ponto de vista de segurança, ambos são seguros, desde que o desenvolvedor os use da maneira que eles devem ser usados

Mas enquanto o PDO e o MySQLi são bastante rápidos, o MySQLi é insignificantemente mais rápido em benchmarks - ~ 2.5% para declarações não preparadas, e ~ 6.5% para declarações preparadas.

E, por favor, teste cada consulta ao seu banco de dados - é uma maneira melhor de evitar a injeção.


Eu 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;
}

Isso permite executar instruções em um C # de cadeia de um-liner. Formatar como:

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

Escapa considerando o tipo de variável. Se você tentar parametrizar os nomes de tabela, coluna, isso falhará, pois colocará todas as strings entre aspas, o que é uma sintaxe inválida.

ATUALIZAÇÃO DE SEGURANÇA: A str_replaceversão anterior permitia injeções adicionando {#} tokens aos dados do usuário. Esta preg_replace_callbackversão não causa problemas se a substituição contiver esses tokens.


** Aviso: a abordagem descrita nesta resposta aplica-se apenas a cenários muito específicos e não é segura, pois os ataques de injeção de SQL não dependem apenas da capacidade de injeção X=Y. **

Se os invasores estiverem tentando invadir o formulário por meio da $_GETvariável do PHP ou com a string de consulta da URL, você poderá capturá-los se eles 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 um banco de dados SQL de um atacante. Talvez também seja usado por muitos aplicativos de hackers.

Mas você deve ter cuidado para não reescrever uma consulta segura do seu site. O código acima está dando a você uma dica para reescrever ou redirecionar (depende de você) que a string de consulta dinâmica específica de invasão em uma página armazene o endereço IP do invasor , ou até mesmo seus cookies, histórico, navegador ou qualquer outro informações, para que você possa lidar com elas posteriormente, banindo a conta ou as autoridades de contato.


A alternativa simples para esse problema poderia ser resolvida concedendo permissões apropriadas no próprio banco de dados. Por exemplo: se você estiver usando um banco de dados mysql, entre no banco de dados por meio do terminal ou da interface do usuário fornecida e apenas siga este comando:

 GRANT SELECT, INSERT, DELETE ON database TO [email protected]'localhost' IDENTIFIED BY 'password';

Isso restringirá o usuário a ficar confinado apenas com a consulta especificada. Remova a permissão de exclusão e para que os dados nunca sejam excluídos da consulta disparada da página do php. A segunda coisa a fazer é liberar os privilégios para que o mysql atualize as permissões e atualizações.

FLUSH PRIVILEGES; 

mais informações sobre flush .

Para ver os privilégios atuais do usuário, acesse a seguinte consulta.

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

Saiba mais sobre o GRANT .


Existem muitas respostas para PHP e MySQL , mas aqui está o código para PHP e Oracle para prevenir a injeção de SQL, bem como o uso regular de drivers 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);

Há muitas maneiras de impedir injeções de SQL e outros hacks de SQL. Você pode encontrá-lo facilmente na Internet (Pesquisa do Google). Claro que o PDO é uma das boas soluções. Mas eu gostaria de sugerir a você alguns bons links de prevenção do SQL Injection.

O que é injeção de SQL e como evitar

Manual PHP para injeção SQL

Explicação da Microsoft sobre injeção de SQL e prevenção em PHP

e alguns outros como Prevenir injeção SQL com MySQL e PHP

Agora, por que você precisa evitar sua consulta de injeção de SQL?

Gostaria de informar: Por que tentamos impedir a injeção de SQL com um breve exemplo abaixo:

Consulta para 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á analisada no sistema apenas até:

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

A outra parte será descartada. Então, o que vai acontecer? Um usuário não autorizado (hacker) poderá fazer login como administrador sem ter sua senha. Agora, ele pode fazer qualquer coisa que a pessoa de admin / email possa fazer. Veja, é muito perigoso se a injeção SQL não for impedida.


Quanto a muitas respostas úteis, espero adicionar alguns valores a este tópico. Injeção de SQL é um ataque que pode ser feito através de entradas do usuário (Entradas preenchidas pelo usuário e usadas dentro de consultas), Os padrões de injeção SQL são a sintaxe correta da consulta enquanto podemos chamá-la: consultas ruins por razões ruins, assumimos que pode ser uma pessoa ruim que tenta obter informações secretas (ignorando o controle de acesso) que afetam os três princípios de segurança (Confidencialidade, Integridade, Disponibilidade).

Agora, nosso objetivo é evitar ameaças de segurança, como ataques de injeção SQL, a pergunta (como impedir o ataque de injeção SQL usando PHP), ser mais realista, filtragem de dados ou dados de entrada de limpeza é o caso ao usar dados de entrada do usuário dentro de tais consulta, usando PHP ou qualquer outra linguagem de programação não é o caso, ou como recomendado por mais pessoas para usar a tecnologia moderna, como declaração preparada ou quaisquer outras ferramentas que atualmente suportam prevenção de injeção de SQL, considere que essas ferramentas não estão mais disponíveis? Como você protege seu aplicativo?

Minha abordagem contra a injeção de SQL é: limpar os dados de entrada do usuário antes de enviá-los ao banco de dados (antes de usá-los dentro de qualquer consulta).

Filtragem de dados para (Convertendo dados não seguros em dados seguros) Considere que PDO e MySQLi não estão disponíveis, como você pode proteger sua aplicação? Você me força a usá-los? E quanto a outras linguagens além do PHP? Eu prefiro fornecer idéias gerais, pois elas podem ser usadas para bordas mais amplas, não apenas para linguagem específica.

  1. Usuário SQL (limitando o privilégio de usuário): as operações SQL mais comuns são (SELECT, UPDATE, INSERT), então, por que dar privilégio UPDATE para um usuário que não precisa dele? Por exemplo , login e páginas de pesquisa estão usando apenas SELECT, então, por que usar usuários de banco de dados nessas páginas com altos privilégios? REGRA: não crie um usuário de banco de dados para todos os privilégios, para todas as operações SQL, você pode criar seu esquema como (deluser, selectuser, updateuser) como nomes de usuário para fácil uso.

veja Princípio do menor privilégio

  1. Filtragem de dados: antes de criar qualquer consulta, a entrada do usuário deve ser validada e filtrada; para programadores, é importante definir algumas propriedades para cada variável de entrada do usuário: tipo de dados, padrão de dados e comprimento de dados . um campo que é um número entre (x e y) deve ser exatamente validado usando uma regra exata, para um campo que é uma string (texto): padrão é o caso, por exemplo, 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 <= n). Regra: criar filtros exatos e regras de validação são a melhor prática para mim.

  2. Use outras ferramentas: Aqui, eu também vou concordar com você que instrução preparada (consulta parametrizada) e procedimentos armazenados, as desvantagens aqui é que requer habilidades avançadas que não existem para a maioria dos usuários, a idéia básica aqui é distinguir entre consulta SQL e os dados que estão sendo usados ​​internamente, 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 (any ou x = x). Para mais informações, leia a folha de dicas sobre prevenção de injeção OWASP SQL .

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

Finalmente, vamos considerar que o usuário envie este texto abaixo em vez de digitar seu nome de usuário:

[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 antecipadamente sem qualquer declaração preparada e procedimentos armazenados, mas para estar no lado seguro, usá-los inicia após a filtragem e validação de dados do usuário.

O último ponto é detectar comportamento inesperado que requer mais esforço e complexidade; não é recomendado para aplicativos da web normais. Comportamento inesperado na entrada do usuário acima é SELECT, UNION, SE, SUBSTRING, BENCHMARK, SHA, raiz, uma vez que essas palavras detectadas, você pode evitar a entrada.

UPDATE1:

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

Defesas primárias:

Opção nº 1: Uso de instruções preparadas (consultas parametrizadas)
Opção nº 2: uso de procedimentos armazenados
Opção nº 3: escape de todas as entradas fornecidas pelo usuário

Defesas adicionais:

também Reforçar: privilégios mínimos
também são executados: Validação de entrada de lista branca

Como você deve saber, reivindicar em um artigo deve ser apoiado por um argumento válido, pelo menos uma referência! Caso contrário, é considerado um ataque e uma reclamação ruim!

Update2:

Do manual do PHP, PHP: Prepared Statements - Manual :

Escapando e injeção de SQL

Variáveis ​​associadas serão automaticamente escapadas pelo servidor. O servidor insere seus valores de escape nos locais apropriados no modelo de instrução antes da execução. Uma dica deve ser fornecida ao servidor para o tipo de variável ligada, para criar uma conversão apropriada. Veja a função mysqli_stmt_bind_param () para mais informações.

O escape automático de valores dentro do servidor é, às vezes, considerado um recurso de segurança para impedir a injeção de SQL. O mesmo grau de segurança pode ser obtido com instruções não preparadas, se os valores de entrada tiverem escapado corretamente.

Update3:

Eu criei casos de teste para saber como o PDO e o MySQLi enviam a consulta para o servidor MySQL ao usar uma instrução preparada:

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

Registro de consulta:

    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();

Registro de consulta:

    188 Prepare   SELECT * FROM awa_user WHERE username =?
    188 Execute   SELECT * FROM awa_user WHERE username ='\'\'1\'\''
    188 Quit

É claro que uma declaração preparada também está escapando dos dados, nada mais.

Como também mencionado na declaração acima The automatic escaping of values within the server is sometimes considered a security feature to prevent SQL injection. The same degree of security can be achieved with non-prepared statements, if input values are escaped correctly, portanto, isso prova que a validação de dados, como intval()é uma boa idéia para valores inteiros antes de enviar qualquer consulta, além de impedir que os dados do usuário mal-intencionado antes de enviar a consulta seja abordagem correta e válida .

Por favor, veja esta questão para mais detalhes: O PDO envia uma consulta bruta para o MySQL enquanto o Mysqli envia uma consulta preparada, ambos produzem o mesmo resultado

Referências:

  1. Cheat Sheet de Injeção SQL
  2. Injeção SQL
  3. Segurança da informação
  4. Princípios de segurança
  5. Data de validade

Se possível, elenco os tipos de seus parâmetros. Mas está apenas trabalhando 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 . "')");

Uma boa idéia é usar um 'mapeador objeto-relacional' como o 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;
}

Ele não apenas salva você de injeções de SQL, mas também de erros de sintaxe! Também suporta coleções de modelos com encadeamento de métodos para filtrar ou aplicar ações a múltiplos resultados de uma vez e múltiplas conexões.


Uma maneira simples seria usar um framework PHP como o CodeIgniter ou o Laravel que possui recursos como filtragem e active-record, para que você não precise se preocupar com essas nuances.


Usando esta função PHP mysql_escape_string()você pode obter uma boa prevenção de maneira rápida.

Por exemplo:

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

mysql_escape_string - Escapa uma string para uso em um mysql_query

Para mais prevenção, você pode adicionar no final ...

wHERE 1=1   or  LIMIT 1

Finalmente você recebe:

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




sql-injection