Introdução: por que a injeção SQL ainda é relevante em 2025?
Imagine: você criou um site legal em PHP, adicionou um formulário de contato, busca por produtos, e uma semana depois descobre que um invasor baixou todo o banco de dados de clientes. Parece uma história de terror dos anos 2010? Infelizmente, de acordo com a OWASP, a injeção SQL (SQL injection) ainda está entre as 3 vulnerabilidades mais perigosas de aplicações web. E sim, em 2025, os desenvolvedores ainda cometem esse erro.
A pergunta "How can I prevent SQL injection in PHP?" no Stack Overflow acumulou milhares de visualizações e centenas de respostas. Mas o problema não é a falta de informação — o problema é que muitos ainda usam métodos ultrapassados como funções mysql_* (que foram removidas no PHP 7.0) ou confiam ingenuamente no mysqli_real_escape_string() como uma bala de prata.
Neste artigo, vamos abordar as melhores práticas de proteção contra injeções SQL em PHP em 2025. Você aprenderá por que as consultas preparadas (prepared statements) são o padrão da indústria, como trabalhar corretamente com PDO e mysqli, e quais armadilhas ainda esperam os desenvolvedores inexperientes. Vamos lá!
O que é injeção SQL em termos simples?
Injeção SQL é um ataque no qual um invasor "injeta" código SQL malicioso em uma consulta através da entrada do usuário (formulários, URL, cookies). Em vez de apenas enviar um nome de usuário, o hacker pode enviar: ' OR '1'='1 — e obter acesso a todos os dados.
Aqui está um exemplo clássico de código vulnerável:
// NUNCA faça isso!$username = $_POST['username'];$password = $_POST['password'];$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";$result = mysqli_query($conn, $query);Se o invasor inserir no campo username: admin' --, a consulta se tornará:
SELECT * FROM users WHERE username = 'admin' --' AND password = '...'Tudo após -- é um comentário. A autenticação é contornada. O banco de dados é nosso. Olá, vazamento de dados!
Método nº 1 (e o mais importante): Consultas Preparadas (Prepared Statements)
Em 2025, esta é a única maneira recomendada de se proteger contra injeções SQL. As consultas preparadas separam a lógica SQL dos dados. Você primeiro diz ao banco de dados: "Aqui está o modelo da consulta, aqui estão os lugares para as variáveis", e depois envia os dados separadamente. O próprio banco de dados escapa tudo o que for necessário.
Opção A: PDO (PHP Data Objects) — o padrão moderno
PDO suporta 12 SGBDs diferentes (MySQL, PostgreSQL, SQLite e outros). Se você está escrevendo um novo projeto em 2025 — use PDO.
<?php// Conexão ao BD via PDO$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';$user = 'root';$password = '';
try { $pdo = new PDO($dsn, $user, $password); // Importante: lançar exceções em caso de erros $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);} catch (PDOException $e) { die('Erro de conexão: ' . $e->getMessage());}
// Consulta preparada com placeholders nomeados$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email AND status = :status');$stmt->execute([ ':email' => $_POST['email'], ':status' => 'active']);$user = $stmt->fetch(PDO::FETCH_ASSOC);
// Alternativa com placeholders posicionais (?)$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ? AND status = ?');$stmt->execute([$_POST['email'], 'active']);$user = $stmt->fetch();?>Por que isso é seguro? Porque os dados nunca são inseridos diretamente na string SQL. Mesmo que o usuário envie ' OR '1'='1, o PDO passará isso como um literal de string, e o banco de dados o interpretará exatamente como uma string, e não como parte do comando SQL.