FlawOpen

flawopen.com/SQL Injection/IDs numéricos

Por que a formatação de strings é perigosa até para IDs numéricos?

Página de referência
Resposta curta

O risco não depende do significado real do valor: a consulta é construída juntando texto. “Isso sempre é um número” é uma suposição sobre os dados, não uma garantia imposta pelo código.

Quando a suposição falha

Parâmetros HTTP sempre chegam como strings

Um parâmetro de URL ou campo de formulário é texto na transmissão, seja qual for o tipo esperado pela coluna do banco. Sem validação explícita, o framework não impede que alguém envie id=1 OR 1=1 em vez de um número antes de o código receber o valor.

Uma refatoração futura pode mudar discretamente a origem do valor

Um código hoje seguro porque o ID vem de uma fonte interna confiável pode ficar vulnerável quando outro chamador passa um valor do usuário à mesma função. A falha já estava latente, à espera dessa mudança.

A coerção de tipos pode ocultar o valor real

Em especial nas linguagens de tipagem dinâmica, um valor que parece numérico nos logs ainda pode ser uma string com caracteres adicionais. Sem validação explícita, o tipo esperado não é necessariamente o tipo recebido.

Parametrize a consulta mesmo após validar o tipo

Valide o ID conforme o formato esperado pela aplicação e, em seguida, passe-o pela API de parâmetros do driver do banco de dados. A validação protege o contrato de entrada; a parametrização impede que a entrada seja interpretada como sintaxe SQL. O comportamento do driver no protocolo pode variar, portanto não presuma que todos transmitem valores da mesma forma. Parâmetros do PostgreSQL não representam identificadores de tabela ou coluna; escolha estruturas dinâmicas da consulta em uma lista fixa permitida pela aplicação.

Vincule o valor numérico; valide seu significado

Valide o ID como um inteiro positivo seguro quando esse for o tipo exigido pela aplicação, mas ainda o vincule como valor da consulta. A validação protege o contrato de entrada da aplicação; a parametrização mantém a entrada fora da sintaxe SQL. Parâmetros do PostgreSQL não substituem identificadores de tabela ou coluna, então escolha qualquer estrutura dinâmica da consulta em uma lista fixa permitida pela aplicação.

INSEGURO — interpola o ID no SQL
const rawId = new URL(request.url).searchParams.get("id");
const result = await pool.query(
  `SELECT id FROM users WHERE id = ${rawId}`
);
SEGURO — valida o ID e vincula o valor
const rawId = new URL(request.url).searchParams.get("id");
if (rawId === null || !/^[0-9]+$/.test(rawId)) {
  return new Response("Invalid id", { status: 400 });
}
const id = Number(rawId);
if (!Number.isSafeInteger(id) || id < 1) {
  return new Response("Invalid id", { status: 400 });
}
const result = await pool.query(
  "SELECT id FROM users WHERE id = $1",
  Array.of(id)
);

Referências

Veja também SQL Injection (CWE-89): como funciona e como evitar em todas as linguagens — flawopen.com O que é uma consulta parametrizada? Prisma $queryRaw: é seguro contra SQL Injection? Interpolação em where() do Rails é segura? Sequelize query() é seguro contra SQL Injection? SQLAlchemy text() é seguro contra SQL Injection? A verificação de consultas do sqlx em compilação evita SQL injection?