Introdução

SQL Injection é uma vulnerabilidade que permite alterar consultas SQL por meio de entradas manipuladas. Neste tutorial, você aprenderá a identificar, testar e explorar essa falha em laboratório, além de proteger aplicações PHP com PDO.

Como identificar uma possível vulnerabilidade

Comece identificando os pontos em que a aplicação recebe dados que depois são utilizados em consultas ao banco:

  • Parâmetros de URL, como ?id=10.
  • Campos de formulários e filtros de pesquisa.
  • Cookies e cabeçalhos HTTP.
  • Dados enviados por APIs.
  • Parâmetros de ordenação e paginação.

Considere esta URL:

https://exemplo.local/produto.php?id=10

Uma implementação vulnerável poderia ser:

<?php
// PHP 8.3 - Exemplo vulnerável

$id = $_GET['id'] ?? '';

$sql = "SELECT * FROM produtos WHERE id = $id";

$resultado = $pdo->query($sql);

O problema é a inclusão direta de uma entrada externa na instrução SQL. Se o valor recebido alterar a estrutura da consulta, a aplicação poderá executar uma operação diferente da planejada.

Como testar uma possível SQL Injection

Faça testes somente em aplicações próprias, ambientes de homologação ou laboratórios autorizados. Compare a resposta da aplicação a entradas normais e inesperadas. Em um laboratório, por exemplo, compare:

?id=10
?id=10'
?id=10%27

Uma mensagem de erro SQL pode indicar um problema, mas não comprova sozinha que existe uma vulnerabilidade explorável. Observe diferenças consistentes no conteúdo, no código de resposta e no comportamento da aplicação.

Testes baseados em condições booleanas

Em um laboratório controlado, compare condições verdadeiras e falsas:

10 AND 1=1
10 AND 1=2

Se a resposta mudar de maneira consistente com essas condições, existe um indício de que a entrada influencia a consulta. Essa técnica é conhecida como SQL Injection baseada em booleanos.

Como os ataques SQL Injection funcionam

A técnica utilizada depende do banco de dados, da consulta vulnerável e da forma como a aplicação apresenta os resultados. Os exemplos abaixo são para laboratórios isolados.

Injeção baseada em condições verdadeiras

Considere uma consulta que incorpora diretamente um valor recebido:

SELECT id, nome
FROM usuarios
WHERE email = 'valor_recebido';

Em um laboratório, uma entrada clássica para demonstrar a alteração da lógica é:

' OR '1'='1'-- -

A intenção é inserir uma condição sempre verdadeira e comentar o restante da instrução, conforme a sintaxe do banco. Em uma aplicação vulnerável, isso pode alterar a lógica de autenticação ou pesquisa. Nunca teste esse tipo de entrada em sistemas sem autorização.

SQL Injection com UNION

A cláusula UNION combina resultados de consultas SELECT. Para funcionar, as consultas precisam retornar a mesma quantidade de colunas e tipos compatíveis.

Em um laboratório, um teste de compatibilidade pode usar:

' UNION SELECT 'abc','def'-- -

O objetivo didático é entender a estrutura da consulta e como a aplicação trata o resultado, sem acessar dados de terceiros.

Blind SQL Injection

Na Blind SQL Injection, a aplicação não exibe diretamente o resultado da consulta. O teste observa diferenças indiretas, como conteúdo da resposta, comportamento, erros condicionais ou tempo de resposta. A análise dessas diferenças pode revelar que uma entrada está influenciando a consulta.

Como reproduzir testes em laboratório

Uma forma segura de aprender é utilizar os laboratórios da PortSwigger Web Security Academy, que oferecem exercícios sobre identificação, condições booleanas, UNION e Blind SQL Injection.

Testando com Burp Suite

Em um laboratório autorizado:

  1. Abra o laboratório fornecido pela plataforma.
  2. Configure o navegador para utilizar o proxy do Burp Suite.
  3. Intercepte uma requisição que contenha um parâmetro de entrada.
  4. Envie a requisição para o Repeater.
  5. Modifique o parâmetro de teste.
  6. Compare as respostas obtidas.

Não utilize essa técnica contra sites ou serviços sem autorização expressa.

Testando com SQLmap em ambiente local

O SQLmap automatiza a identificação e validação de possíveis vulnerabilidades. Consulte o site oficial do SQLmap. Em uma aplicação de laboratório local, um teste inicial pode ser executado assim:

# Linux: Terminal
sqlmap -u "http://127.0.0.1/item.php?id=1" \
  -p id \
  --batch \
  --risk=1 \
  --level=1
  • -u: informa a URL de teste.
  • -p: limita o teste ao parâmetro indicado.
  • --batch: utiliza respostas padrão às perguntas.
  • --risk=1: seleciona o nível de risco mais baixo.
  • --level=1: utiliza o nível inicial de testes.

O endereço 127.0.0.1 representa a própria máquina. Utilize apenas um laboratório que você controla ou está autorizado a testar. Não execute opções de extração de dados, alteração de registros ou operações destrutivas em ambientes reais.

Como prevenir SQL Injection em PHP

A defesa principal é separar a estrutura SQL dos valores recebidos. Em PHP, utilize PDO com consultas preparadas e parâmetros vinculados.

Exemplo vulnerável

<?php
// PHP 8.3 - Não utilize este padrão

$email = $_POST['email'] ?? '';

$sql = "SELECT id, nome
        FROM usuarios
        WHERE email = '$email'";

$resultado = $pdo->query($sql);

A entrada é concatenada diretamente à consulta e pode interferir na estrutura SQL.

Implementação segura com PDO

<?php
// PHP 8.3 - Consulta preparada com PDO

$email = $_POST['email'] ?? '';

$sql = "
    SELECT id, nome
    FROM usuarios
    WHERE email = :email
";

$stmt = $pdo->prepare($sql);
$stmt->execute([
    'email' => $email
]);

$usuario = $stmt->fetch(PDO::FETCH_ASSOC);

if ($usuario) {
    echo htmlspecialchars(
        $usuario['nome'],
        ENT_QUOTES,
        'UTF-8'
    );
} else {
    echo 'Usuário não encontrado.';
}

O valor recebido é tratado como dado, e não como parte da instrução SQL. A função htmlspecialchars() protege a saída HTML contra interpretação indevida de caracteres; ela não substitui a proteção contra SQL Injection.

Configurando o PDO

Na criação da conexão, utilize o modo de exceções e desative a emulação de consultas preparadas quando o driver oferecer suporte adequado:

<?php
// PHP 8.3

$pdo = new PDO(
    'mysql:host=localhost;dbname=aplicacao;charset=utf8mb4',
    'usuario_app',
    'senha_segura',
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false
    ]
);

Parâmetros preparados representam valores, como identificadores, nomes e datas. Eles não substituem nomes de tabelas, colunas ou palavras-chave SQL.

Protegendo consultas com ordenação dinâmica

Parâmetros PDO não representam identificadores SQL. Por isso, não concatene diretamente uma coluna ou direção de ordenação recebida do usuário.

Este padrão é inseguro:

<?php
$coluna = $_GET['coluna'] ?? 'nome';
$sql = "SELECT id, nome FROM produtos ORDER BY $coluna";

Utilize uma lista permitida de opções:

<?php
// PHP 8.3

$colunasPermitidas = [
    'nome' => 'nome',
    'preco' => 'preco',
    'data' => 'data_cadastro'
];

$colunaSolicitada = $_GET['coluna'] ?? 'nome';
$coluna = $colunasPermitidas[$colunaSolicitada] ?? 'nome';

$direcaoSolicitada = strtoupper($_GET['direcao'] ?? 'ASC');

$direcao = in_array(
    $direcaoSolicitada,
    ['ASC', 'DESC'],
    true
) ? $direcaoSolicitada : 'ASC';

$sql = "
    SELECT id, nome, preco
    FROM produtos
    ORDER BY $coluna $direcao
";

$stmt = $pdo->query($sql);
$produtos = $stmt->fetchAll();

Os valores inseridos na estrutura SQL são escolhidos exclusivamente pela aplicação.

Outras medidas de proteção

Princípio do menor privilégio

O usuário do banco utilizado pela aplicação deve possuir somente as permissões necessárias. Uma aplicação que apenas consulta produtos não precisa de permissões administrativas para apagar bancos ou criar usuários.

Validação de entradas

Valide os dados conforme o tipo esperado. Exemplo de identificador inteiro:

<?php

$id = filter_input(
    INPUT_GET,
    'id',
    FILTER_VALIDATE_INT
);

if ($id === false || $id === null || $id < 1) {
    http_response_code(400);
    exit('Identificador inválido.');
}

A validação reduz entradas inesperadas, mas não substitui consultas preparadas.

Tratamento de erros

Não apresente erros internos do banco ao visitante. Registre detalhes técnicos em local protegido e mostre uma mensagem genérica:

<?php

try {
    $stmt = $pdo->prepare(
        'SELECT id, nome FROM usuarios WHERE id = :id'
    );

    $stmt->execute(['id' => $id]);
    $usuario = $stmt->fetch();

} catch (PDOException $e) {
    error_log($e->getMessage());

    http_response_code(500);
    echo 'Não foi possível processar a solicitação.';
}

Proteja os registros de erros contra acesso público e evite armazenar senhas ou dados sensíveis desnecessariamente.

Como descobrir SQL Injection no próprio projeto

Durante uma auditoria de código, procure por:

  • Concatenação de variáveis em consultas SQL.
  • Uso de query() com strings construídas a partir de entradas externas.
  • Procedures que concatenam entradas em SQL dinâmico.
  • Filtros e ordenações montados diretamente com parâmetros HTTP.
  • Consultas HQL ou outras linguagens construídas por concatenação.

Ferramentas úteis:

FerramentaFinalidade
Burp SuiteInterceptação e análise de requisições
OWASP ZAPTestes dinâmicos de segurança
SQLmapIdentificação automatizada em testes autorizados
PHPStanAnálise estática de código PHP
SemgrepBusca de padrões inseguros no código

Combine análise estática, testes dinâmicos e revisão manual. Nenhuma ferramenta isolada garante que uma aplicação esteja livre de SQL Injection.

Checklist de segurança

  • Todas as entradas externas são tratadas como dados.
  • Consultas dinâmicas utilizam parâmetros vinculados.
  • Nomes de tabelas e colunas são escolhidos por listas permitidas.
  • Procedures não concatenam entradas em SQL dinâmico.
  • O usuário do banco possui privilégios mínimos.
  • Erros internos não são apresentados ao visitante.
  • Os testes foram realizados em ambiente autorizado.
  • O código foi revisado e as vulnerabilidades corrigidas.

Conclusão

SQL Injection pode comprometer dados e operações quando entradas externas são incorporadas diretamente às consultas. A principal defesa é utilizar consultas preparadas, complementadas por validação, controle de privilégios e auditorias periódicas. Aprender a explorar essa vulnerabilidade em laboratórios controlados ajuda a reconhecer e corrigir erros antes de colocar um sistema em produção.

Referências