O que o marcador não cobre
Marcadores substituem valores. Nome de tabela, nome de coluna, direção de ordenação e palavras-chave não podem ser parametrizados. Para esses casos, a aplicação escolhe entre opções fixas no código:
COLUNAS_ORDENAVEIS = {"nome": "nome", "data": "criado_em"}
coluna = COLUNAS_ORDENAVEIS.get(parametro_ordem, "nome")
query = f"SELECT id, nome FROM clientes ORDER BY {coluna}"
O usuário escolhe uma chave. O texto que entra no SQL sai do dicionário, escrito pelo desenvolvedor.
Outros dois cuidados:
- Stored procedure só protege se não montar SQL dinâmico por concatenação dentro dela.
- Cláusula
IN com lista variável pede um marcador por item, gerado pelo código. Juntar os valores com vírgula em uma string reabre a falha.
Onde o ORM ainda deixa passar
ORMs geram consultas parametrizadas por padrão. Enquanto o código usa a API de objetos, a proteção vem junto. A falha volta nos pontos em que o framework aceita SQL escrito à mão.
Raw query. Todo ORM tem uma saída para SQL direto: raw() e extra() no Django, text() no SQLAlchemy, createNativeQuery() no Hibernate, FromSqlRaw() no Entity Framework, sequelize.query() no Sequelize. Essas funções aceitam parâmetros, mas também aceitam uma string já montada.
# Vulnerável: interpolação dentro de raw()
Cliente.objects.raw(f"SELECT * FROM clientes WHERE nome = '{nome}'")
# Correto: valor passado como parâmetro
Cliente.objects.raw("SELECT * FROM clientes WHERE nome = %s", [nome])
Concatenação dinâmica na linguagem do ORM. HQL e JPQL também passam por um analisador. Montar "FROM Pedido WHERE status = '" + status + "'" cria a mesma falha uma camada acima.
Fragmentos em query builders. Métodos como whereRaw(), orderByRaw() e literal() inserem texto direto no SQL gerado. Eles costumam aparecer em filtros opcionais e em ordenação configurável.
Em uma reunião de risco, “usamos ORM” descreve o padrão do projeto e diz pouco sobre as exceções. A informação que falta é quantas raw queries existem no repositório e quem revisa cada uma.