← Voltar ao blog

SQL Injection no Firebird: por que concatenar strings ainda derruba sistemas

Um parâmetro na URL e uma concatenação de string bastam para vazar dados no Firebird. Veja o ataque na prática e como o bind elimina a injeção.


Sistemas legados em PHP com Firebird compartilham um padrão perigoso: montar SQL juntando strings. Funciona até o dia em que um parâmetro vem da URL e o banco devolve dados que não deveria.

O cenário real

Um endpoint público recebe um identificador na query string e busca o token correspondente:

$id = trim($_GET['identificador']);
$token = db_GetOne("SELECT token FROM WEBHOOKS_DETS WHERE identificador = '$id'");
echo $token;

A intenção é simples: buscar uma linha. O problema é que $id é texto livre. trim() remove espaços, não neutraliza SQL.

Com identificador=' OR 1=1 --, a consulta vira:

SELECT token FROM WEBHOOKS_DETS WHERE identificador = '' OR 1=1 --'

O OR 1=1 casa com a primeira linha da tabela e o -- comenta o resto. O atacante recebe um token que não era dele. Com UNION SELECT, dá para ler outras tabelas do mesmo banco.

Por que validar o formato não é suficiente sozinho

Validar o identificador ajuda, mas é frágil: basta aparecer outro parâmetro, outro endpoint ou outro formato aceito. A causa raiz é a concatenação. A correção precisa acontecer no ponto onde o SQL é montado.

A solução: bind de parâmetros

O driver ibase do Firebird suporta parâmetros posicionais com ?. O SQL e os valores viajam separados; o banco trata o valor como dado, nunca como comando.

$token = db_GetOne(
    'SELECT token FROM WEBHOOKS_DETS WHERE identificador = ?',
    [$id]
);

No Firebird, o driver envia a query e o array de valores em chamadas distintas (ibase_query($conn, $sql, ...$params)). Por isso ' OR 1=1 -- deixa de ser sintaxe e passa a ser apenas uma string procurada na coluna, que não existe.

Antes e depois

Entrada Antes Depois
identificador válido token correto token correto
zzz' OR '1'='1 token de outra linha vazio
x' erro de SQL do Firebird vazio, sem erro
parâmetro ausente warning no PHP vazio, resposta 200

Note a última linha: além da injeção, o bind com validação de tipo evita avisos que revelam detalhes internos.

Defesa em profundidade

Bind resolve a injeção, mas algumas camadas extras custam pouco:

  1. Valide o formato esperado quando ele é conhecido. Um identificador numérico pode ser checado com preg_match('/^\d{5,25}$/', $id). Se não bater, retorne vazio sem consultar o banco.
  2. Não exiba erro de SQL para o usuário. Em produção, DEBUG ligado transforma um erro em mapa do banco.
  3. Aplique o bind na escrita também. UPDATE, DELETE e INSERT concatenados têm o mesmo problema, com dano maior.
  4. Use o menor privilégio possível no usuário do banco. Um endpoint de leitura não precisa de permissão para alterar tabelas.
  5. Revise os pontos sem login. Webhooks, callbacks e endpoints de campanha são a porta de entrada preferida porque não passam por sessão.

O que fazer hoje

Procure no código os padrões "SELECT ... '$var'", "WHERE ... $var" e "... = $var". Cada ocorrência com dado externo é candidata a injeção. Troque por ? com array de parâmetros e teste com aspas, -- e UNION.

O Sqlify gera SQL Firebird a partir de português e usa parâmetros posicionais nos exemplos, para você começar com o pé direito. Descreva a consulta em português e receba a query pronta, com a explicação de cada parte.