Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Como posso evitar a injeção de SQL em PHP?


O correto maneira de evitar ataques de injeção de SQL, não importa qual banco de dados você use, é separar os dados do SQL , para que os dados permaneçam dados e nunca sejam interpretados como comandos pelo analisador SQL. É possível criar uma instrução SQL com partes de dados formatadas corretamente, mas se você não completamente entender os detalhes, você deve sempre usar declarações preparadas e consultas parametrizadas. Essas são instruções SQL que são enviadas e analisadas pelo servidor de banco de dados separadamente de quaisquer parâmetros. Dessa forma, é impossível para um invasor injetar SQL malicioso.

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

  1. Usando PDO (para qualquer driver de banco 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 você estiver se conectando a um banco de dados diferente do MySQL, há uma segunda opção específica do driver que você pode consultar (por exemplo, pg_prepare() e pg_execute() para PostgreSQL). O DOP é a opção universal.

Configurando corretamente a conexão


Observe que ao usar o PDO para acessar um banco de dados MySQL real instruções preparadas não são usadas por padrão . Para corrigir isso, você deve desabilitar a emulação de instruções preparadas. Um exemplo de criação de uma conexão usando PDO é:
$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 irá parar com um Fatal Error quando algo dá errado. E dá ao desenvolvedor a chance de catch quaisquer erros que sejam throw n como PDOException s.

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

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

Explicação


A instrução SQL que você passa para prepare é analisado e compilado pelo servidor de banco de dados. Ao especificar parâmetros (um ? ou um parâmetro nomeado como :name no exemplo acima) você informa ao mecanismo de banco de dados onde deseja filtrar. Então, quando você chama execute , a instrução preparada é combinada com os valores de parâmetro especificados.

O importante aqui é que os valores dos parâmetros sejam 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 ao criar SQL para enviar ao banco de dados. Assim, ao enviar 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 de banco de dados possa fazer alguma otimização para que os parâmetros também possam acabar como números, é claro). No exemplo acima, se o $name variável contém 'Sarah'; DELETE FROM employees o resultado seria simplesmente uma busca pela string "'Sarah'; DELETE FROM employees" , e você não terá uma tabela vazia .

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

Ah, e já que você perguntou sobre como fazer isso para um insert, aqui está um exemplo (usando PDO):
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute([ '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, o melhor a fazer é usar um filtro de lista branca 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';
}