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:
- 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. - Não exiba erro de SQL para o usuário. Em produção,
DEBUGligado transforma um erro em mapa do banco. - Aplique o bind na escrita também.
UPDATE,DELETEeINSERTconcatenados têm o mesmo problema, com dano maior. - 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.
- 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.