SQL Injection (ou Injeção SQL) é o nome dado a uma falha no código de uma aplicação qualquer que possibilita, por meio de uma entrada qualquer, a manipulação de uma consulta SQL.
Se a entrada do usuário for inserida sem modificação em uma consulta SQL, o aplicativo se tornará vulnerável à injeção de SQL , como no exemplo a seguir:
$variavel_insegura = $_POST['user_input'];
mysql_query("INSERT INTO `table` (`column`) VALUES ('$variavel_insegura')");
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;--')
O que pode ser feito para impedir que isso aconteça?
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 quaisquer parâmetros. Dessa 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) {
// Faça algo com $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()) {
// Faça algo com $row
}
Se você estiver se conectando a um banco de dados que não seja o MySQL, existe uma segunda opção específica do driver à qual você pode se referir (por exemplo, pg_prepare()
e pg_execute()
no PostgreSQL). O DOP é a opção universal.
Configurando corretamente a conexão
Observe que ao usar PDO
para acessar um banco de dados MySQL, as 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', '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 é recomendável adicioná-lo . Dessa forma, o script não irá parar com um Fatal Error
quando algo der errado. E isso dá ao desenvolvedor a chance de catch
qualquer erro que seja throw
como PDOException
.
O que é obrigatório , no entanto, é a primeira setAttribute()
linha, que informa ao PDO para desativar as instruções preparadas emuladas e usar as declarações reais preparadas. Isso garante que a instrução e os valores não sejam analisados pelo PHP antes de enviá-la ao servidor MySQL (dando a um possível invasor nenhuma chance de injetar SQL malicioso).
Embora você possa definir as charset
opções do construtor, é importante observar que as versões ‘mais antigas’ do PHP (antes da 5.3.6) ignoravam silenciosamente o parâmetro charset no DSN.
Explicação
A instrução SQL para a qual 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 do banco de dados onde deseja filtrar. Então, quando você chama execute
, a instrução preparada é combinada com os valores dos parâmetros especificados.
O importante aqui é que os valores dos parâmetros sejam combinados com a instrução compilada, não uma string SQL. A injeção de SQL funciona enganando o script para incluir sequências maliciosas quando ele cria o SQL para enviar ao banco de dados. Portanto, 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, portanto os parâmetros também podem acabar como números, é claro). No exemplo acima, se a $name
variável contiver 'Sarah'; DELETE FROM employees
o resultado, seria simplesmente uma pesquisa para a sequência "'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 o DOP):
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');
$preparedStatement->execute(array('column' => $unsafeValue));
Instruções preparadas podem ser usadas para consultas dinâmicas?
Embora você ainda possa usar instruções preparadas para os parâmetros da consulta, a estrutura da consulta dinâmica em si não pode ser parametrizada e certos recursos da 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.
// Lista de permissões de valor
// $dir só pode ser 'DESC', caso contrário, será 'ASC'
if (empty($dir) || $dir !== 'DESC') {
$dir = 'ASC';
}
Eu recomendo usar o PDO (PHP Data Objects) para executar consultas SQL parametrizadas.
Isso não apenas protege contra a injeção de SQL, mas também acelera as consultas.