Se você usar a entrada do usuário e inserir sem modificar uma consulta SQL, o aplicativo se tornará vulnerável a injeção e SQL, como no exemplo a seguir:
$unsafe_variable = $_POST['user_input'];
mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");
Isso ocorre porque o usuário pode inserir algo como value'); DROP TABLE table;--
, e a consulta se torna:
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
Como Evitar que isso Aconteça?
A maneira correta de evitar ataques de injeção de SQL, não importa qual banco de dados você use, é separar os dados do SQL , de modo 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 entender completamente os detalhes, você deve sempre usar instruções preparadas e consultas parametrizadas. Essas são instruções SQL 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ê tem basicamente duas opções para conseguir isso:
- Usando PDO (para qualquer driver de banco de dados compatível):
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute([ 'name' => $name ]); foreach ($stmt as $row) { // Do something with $row }
- 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 PDO é a opção universal.
Configurando corretamente a conexão
Observe que, ao usar 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 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 . Dessa forma, o script não irá parar Fatal Error
quando algo der errado. E dá ao desenvolvedor a chance de catch
qualquer erro(s) que são throw
como PDOException
.
O que é obrigatório , no entanto, é a primeira setAttribute()
linha, que diz ao PDO para desabilitar as instruções preparadas emuladas e usar as instruções preparadas reais . Isso garante que a instrução e os valores não sejam analisados pelo PHP antes de enviá-los ao servidor MySQL (não dando a um possível invasor nenhuma chance de injetar SQL malicioso).
Embora você possa definir o charset
nas opções do construtor, é importante notar que as versões ‘mais antigas’ do PHP (antes de 5.3.6) ignoraram silenciosamente o parâmetro charset no DSN.
Explicação
A instrução SQL que você passa prepare
é analisada e compilada 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 que você especificar.
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, fazendo-o incluir strings maliciosas ao criar SQL para enviar ao banco de dados. Portanto, ao enviar o SQL real separadamente dos parâmetros, você limita o risco de terminar 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, então os parâmetros podem acabar como números também, é claro). No exemplo acima, se a $name
variável contiver '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 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 como fazer isso para uma inserção, 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 própria consulta dinâmica não pode ser parametrizada e certos 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 desbloqueio que restrinja os valores possíveis.
// Lista de permissões de valor
// $dir só pode ser 'DESC', caso contrário, será 'ASC'
if (empty($dir) || $dir !== 'DESC') {
$dir = 'ASC';
}