Protegendo Aplicações PHP de SQL Injection

Introdução
SQL Injection (SQLi) é uma das vulnerabilidades mais críticas em aplicações web, permitindo que atacantes executem comandos SQL maliciosos no banco de dados. Em PHP, práticas inseguras de manipulação de consultas SQL podem expor dados sensíveis, permitir acesso não autorizado ou até a exclusão de bancos inteiros, portanto quero deixar algumas dicas para prevenir SQL Injection em sistemas PHP.


1. Compreendendo o SQL Injection

Como ocorre:
Quando entradas do usuário são concatenadas diretamente em consultas SQL sem validação, um atacante pode inserir comandos SQL. Exemplo:

php
 
$id = $_GET['id']; // Entrada: 1; DROP TABLE users
$sql = "SELECT * FROM users WHERE id = $id"; // Consulta vulnerável!

Resultado:
A consulta final se torna:

SELECT * FROM users WHERE id = 1; DROP TABLE users;

2. Estratégias de Defesa

2.1. Escape de caracteres

Para proteger nossa aplicação precisamos escapar os caracteres que podem ser usados para compor código SQL, como é o caso de aspas e apóstrofos.
Para escapar esses caracteres, devemos usar a função mysqli_real_escape_string() ou o método escape_string() da classe mysqli.
Veja como fica o método gravar_tarefa() da classe Tarefas, no arquivo classes/Tarefas.php:
<?php
    class Tarefas
    {

        public function gravar_tarefa($tarefa){
            $nome = $this->mysqli->escape_string($tarefa[‘nome’]);
            $descricao = $this->mysqli->escape_string($tarefa[‘descricao’]);
            $prazo = $this->mysqli->escape_string($tarefa[‘prazo’]);
            $sqlGravar = “
                 INSERT INTO tarefas
                 (nome, descricao, prioridade, prazo, concluida)
                 VALUES(
                     ‘{$nome}’,
                     ‘{$descricao}’,
                     {$tarefa[‘prioridade’]},
                    ‘{$prazo}’,
                     {$tarefa[‘concluida’]}
                 )”;

    $this->mysqli->query($sqlGravar);
    }

Perceba que os dados que contêm textos foram colocados em variáveis que são os retornos do método $mysql->escape_string().
Escapar um texto é algo mais ou menos assim:
O texto era isso Assistir ‘Star Wars’ e virou isso Assistir \’Star Wars\’. Dessa forma, o MySQL trata o apóstrofo como um apóstrofo mesmo e não
como um delimitador de campos de texto.

Além dessa forma, também podemos fazer:

2.2. Prepared Statements (Declarações Preparadas)

A técnica mais eficaz! Separa a lógica SQL dos dados.

Usando PDO (PHP Data Objects):

php
 
// Conexão com o banco
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'user', 'password');

// Consulta com parâmetros nomeados
$sql = "SELECT * FROM users WHERE email = :email AND status = :status";
$stmt = $pdo->prepare($sql);

// Execução com valores seguros
$stmt->execute([
    'email' => $_POST['email'],
    'status' => 'active'
]);

// Obter resultados
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);

Usando MySQLi (MySQL Improved):

php
 
$mysqli = new mysqli("localhost", "user", "password", "test");

// Consulta com placeholders (?)
$sql = "SELECT name FROM products WHERE id = ?";
$stmt = $mysqli->prepare($sql);

// Vincula parâmetros (s = string, i = inteiro)
$id = $_GET['id'];
$stmt->bind_param("i", $id); // "i" indica que $id é um inteiro

// Executa
$stmt->execute();
$result = $stmt->get_result();

2.3. Validação e Filtragem de Entradas

Nunca confie em dados do usuário! Valide antes de usar.

php
 
// Validar e-mail
$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);
if (!$email) {
    die("E-mail inválido!");
}

// Filtrar entrada numérica
$id = filter_var($_GET['id'], FILTER_VALIDATE_INT);
if ($id === false) {
    die("ID deve ser um número!");
}

2.4. Escapagem de Strings (Como Último Recurso)

Use apenas se prepared statements não forem viáveis. Não recomendado para novos projetos!

php
 
$mysqli = new mysqli("localhost", "user", "password", "test");
$name = $mysqli->real_escape_string($_POST['name']);

$sql = "INSERT INTO users (name) VALUES ('$name')";
$mysqli->query($sql);

Limitação:

  • Não protege contra todos os tipos de SQL Injection.

  • Vulnerável a ataques de codificação (ex.: UTF-8 malicioso).


2.5. Stored Procedures (Procedimentos Armazenados)

Delegue a lógica SQL para o banco de dados.

php
 
$pdo = new PDO(...);
$stmt = $pdo->prepare("CALL GetUserByEmail(?)");
$stmt->execute([$_POST['email']]);

Cuidado:

  • Procedimentos ainda podem conter SQL dinâmico inseguro.

  • Use em conjunto com prepared statements.


2.6. Mapeamento Objeto-Relacional (ORM)

Bibliotecas como Eloquent (Laravel) ou Doctrine automatizam a segurança.

Exemplo com Eloquent:

php
 
User::where('email', $_POST['email'])->get();

O ORM gera automaticamente prepared statements.


3. Boas Práticas Adicionais

  • Princípio do Menor Privilégio:
    Configure o usuário do banco com permissões mínimas (ex.: apenas SELECT/INSERT).

  • Gerenciamento de Erros:
    Nunca exiba erros do SQL no frontend:

    php
     
    ini_set('display_errors', 0); // Desativa exibição
    error_reporting(0); // Em produção
  • Atualizações:
    Mantenha o PHP, bibliotecas e bancos de dados atualizados.

  • Web Application Firewall (WAF):
    Use soluções como ModSecurity para bloquear padrões de ataque conhecidos.


4. Exemplo de Ataque e Prevenção

Cenário de Ataque:

php
 
// Entrada maliciosa
$_POST['username'] = "' OR 1=1 -- ";
$_POST['password'] = "any";

// Consulta vulnerável
$sql = "SELECT * FROM users 
        WHERE username = '{$_POST['username']}' 
        AND password = '{$_POST['password']}'";

Resultado: SELECT * FROM users WHERE username = '' OR 1=1 -- ... (Retorna todos os usuários!).

Solução com Prepared Statement:

php
 
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$_POST['username'], $_POST['password']]);
// Ataque é neutralizado!

5. Ferramentas de Teste

  • SQLMap: Teste automatizado para SQL Injection.

  • PHPStan: Analisa código em busca de práticas inseguras.

  • SonarQube: Inspeção contínua de segurança.


Conclusão

Apesar de SQL Injection ser um risco para qualquer sistema que tenha entrada de dados, é evitável, adote estas práticas:
✅ Sempre use prepared statements (PDO/MySQLi).
✅ Valide e filtre todas as entradas do usuário.
✅ Atualize componentes regularmente.
✅ Monitore e teste sua aplicação.

Lembre-se: Segurança não é um passo único, mas um processo contínuo. Ao implementar essas técnicas, você estará protegendo não apenas dados, mas a confiança dos seus usuários.

Últimos Artigos

Deixe sua dúvida ou opinião

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Post anterior
Próximo post

Copyright © 2025 - Pablo Vinícius