Blog do SQLFix
Consultas parametrizadas vs SQL gerado
Um modelo que escreve uma consulta com aparência correta não resolveu a injeção. A propriedade de segurança que importa reside em como a consulta atende aos dados, e esse é o trabalho das consultas parametrizadas com instruções preparadas. O texto gerado é um ponto de partida para revisão; não é um substituto para a fronteira que as instruções preparadas impõem entre o código e os dados.
O que uma declaração preparada realmente faz
Quando o driver recebe uma solicitação de preparo, ele envia o texto SQL para o servidor. O servidor analisa o texto, constrói um plano de execução e armazena esse plano. Mais tarde, quando o aplicativo chama a execução, o driver envia apenas os valores dos parâmetros. O servidor aplica o plano armazenado a esses valores sem reanalisar o texto SQL. Como os valores nunca se tornam parte da string da declaração, uma carga como ' OR 1=1-- é tratada como um valor literal, não como parte do comando.
Eu vi isso funcionar no MySQL 5.0, PostgreSQL 8.0, SQL Server 2005 e Oracle 9i. Cada linguagem mainstream fornece uma API de vinculação: o PreparedStatement do JDBC, o cursor.execute com espaços reservados do Python, o database/sql.Query do Go e o pool.execute do Node. O modelo pode produzir a forma SELECT * FROM orders WHERE customer_id = ? AND status = ? ; o código circundante fornece os dois valores em tempo de execução.
Se você vê a aspa dentro do texto da declaração, você não está usando uma declaração preparada verdadeira; você provavelmente está fazendo interpolação de string do lado do cliente, o que não fornece segurança contra injeção.
Por que o SQL revisado ainda precisa de parâmetros
A revisão humana pode confirmar que a forma da consulta corresponde à regra de negócios pretendida. Ela pode detectar joins faltantes, nomes de colunas errados ou filtros ineficientes. No entanto, os valores que fluem para esses espaços reservados ainda vêm de fontes externas: formulários da web, payloads de API, filas de mensagens ou arquivos legados. Essas fontes não estão sob o controle direto do desenvolvedor no momento da execução.
A Folha de Dicas de Prevenção de Injeção de SQL da OWASP afirma claramente: "Opção de Defesa 1: Declarações Preparadas (Consultas Parametrizadas)". A folha de dicas não lista "aprovação humana" como substituto. A aprovação aborda a correção lógica; a parametrização aborda a fronteira de confiança entre o código e os dados. Abandonar qualquer uma das duas deixa a outra desprotegida.
Mesmo que o revisor humano tenha visto uma declaração INSERT sensata, a injeção ocorreu na etapa de vinculação de valores, que a revisão não cobriu.
Dessa forma, o trabalho do revisor permanece valioso, e a etapa de execução retém sua garantia de segurança.
Procedimentos armazenados não são segurança automática
Muitas equipes assumem que mover a lógica para um procedimento armazenado elimina o risco de injeção. Essa suposição falha quando o próprio procedimento constrói SQL dinâmico.
Considere um procedimento escrito em T‑SQL:
CREATE PROCEDURE GetOrders @Customer NVARCHAR(50) AS BEGIN DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM Orders WHERE Customer = ''' + @Customer + ''''; EXEC sp_executesql @sql; END Se @Customer contém '); DROP TABLE Orders;-- , a string resultante se torna um lote válido que exclui a tabela. O wrapper do procedimento não protegeu os dados; a concatenação dentro do procedimento foi a causa.
O mesmo padrão aparece em PL/SQL com EXECUTE IMMEDIATE, em PostgreSQL com EXECUTE format e em MySQL com PREPARE/EXECUTE construído a partir de uma variável.
Após a reescrita, execute o procedimento com um valor que contenha uma aspa e verifique se o erro é uma incompatibilidade de tipo de dados, e não um erro de sintaxe.
Mantenha a geração e a execução como camadas separadas
A arquitetura mais segura trata o modelo de IA como um mecanismo de sugestão, e não como um componente de acesso a dados. O modelo produz uma forma de consulta candidata. Um gate de política humano ou automatizado revisa essa forma para correção, conformidade e desempenho. Somente após a aprovação, o código de aplicação pega a forma, prepara-a com o driver e vincula os parâmetros em tempo real.
Porque o modelo nunca vê uma conexão ativa, ele não pode ser enganado para revelar detalhes do esquema por meio de mensagens de erro. Porque a camada de execução nunca confia em strings brutos, mesmo um modelo comprometido não pode causar injeção.
Esse processo mantém a saída do modelo no loop de revisão e garante que a etapa de execução use apenas instruções parametrizadas.
O que isso NÃO é
Quero deixar claro os limites da abordagem descrita acima. Ela não garante que todos os possíveis bugs sejam detectados. Ela não promete 100 % de proteção contra todas as classes de falhas. Ela não afirma eliminar a necessidade de outras defesas, como validação de entrada, codificação de saída ou contas de banco de dados com privilégios mínimos.
O que o método faz é:
Se você confiar apenas na saída do modelo e saltar a revisão ou o passo de vinculação de parâmetros, você perde a proteção. Se você permitir que a camada de execução concatene valores após a preparação, você reintroduz o risco. A técnica é um componente de uma estratégia de defesa em profundidade, e não uma solução mágica isolada.
Exemplo de fluxo de trabalho do mundo real
Imagine um produto SaaS que permite que gerentes exportem uma lista de faturas vencidas. O produto usa SQLForm para criar a consulta.
Este rastreamento mostra como a etapa de geração permanece independente da etapa de execução e como o limite do parâmetro bloqueia a injeção mesmo quando o usuário tenta contrabandear SQL para um campo que o modelo nunca vê.
Armadilhas comuns e como as evitamos
Mesmo com um design sólido, as equipes às vezes caem em hábitos que enfraquecem a proteção. Abaixo estão os erros frequentes que observei e as etapas concretas que tomamos para manter o sistema seguro.
Alguns desenvolvedores constroem SELECT * FROM tags WHERE tag IN (?, ?, ?) criando primeiro uma string com o número correto de espaços reservados, então concatenando os valores diretamente ao SQL antes de prepará-lo. Isso derrota o propósito porque os valores se tornam parte do texto da instrução.
Uma instrução preparada para SELECT * FROM orders WHERE status = ? é executada posteriormente com um valor que na verdade contém uma nova instrução SELECT completa, confiando que o driver trate o valor como dados. Embora o valor seja citado com segurança, a lógica da aplicação pode estar errada porque o desenvolvedor supôs que a instrução pudesse lidar com qualquer SQL.
Painéis internos às vezes ignoram o portão de revisão humana porque a equipe assume que os dados são confiáveis. Mais tarde, uma conta de serviço é comprometida e começa a alimentar payloads mal-intencionados no painel.
Para depuração, as equipes às vezes registram os valores exatos dos parâmetros. Se esses logs forem expostos, eles podem vazar dados sensíveis, como senhas ou informações de identificação pessoal.
Um ORM pode oferecer um método que parece aceitar parâmetros, mas por baixo dos panos ele constrói uma string e chama ExecuteNonQuery em uma string SQL bruta.
Ao observar essas armadilhas e aplicar as correções correspondentes, a separação entre geração e execução permanece forte, e o limite parametrizado continua a fazer o trabalho para o qual foi projetado.
Referências
https://cheatsheetseries.owasp.org/cheatsheets/LLM_Prompt_Injection_Prevention_Cheat_Sheet.html
https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
https://cwe.mitre.org/data/definitions/798.html
https://cwe.mitre.org/data/definitions/89
Perguntas frequentes
Não. A injeção depende de como os valores atendem à consulta, e não se a consulta é logicamente correta. As consultas parametrizadas mantêm os valores separados do texto de comando, independentemente do conteúdo dos valores.
Não automaticamente. Um procedimento que concatena a entrada e a executa com EXECUTE IMMEDIATE ainda pode injetar. A segurança vem do vinculação de parâmetros dentro do procedimento.
Um ORM é seguro apenas quando você usa sua interface parametrizada. Construir uma string e passá-la para o ORM reintroduz a injeção. A fronteira entre código e dados ainda precisa ser aplicada.
Não. Mantenha a geração e a execução separadas: o modelo elabora um rascunho, um gateway aprova e o código de aplicação vincula parâmetros em tempo de execução. O modelo nunca deve ser o caminho de execução.
Tente o produto: SQLForm
2026-09-16 · apenas fontes primárias · nenhum tráfego métrico fabricado
Artigos relacionados
Língua natural para SQL: verificações de segurança que importam
Explore a matriz completa de tópicos de micro-SaaS de IA →
Um consulta correta protege contra injeção de SQL?
Não. A injeção depende de como os valores atendem à consulta, não se a consulta é logicamente correta. Consultas parametrizadas mantêm os valores separados do texto de comando independentemente do conteúdo dos valores.
Os procedimentos armazenados são seguros por padrão?
Não automaticamente. Um procedimento que concatena a entrada e a executa com EXECUTE IMMEDIATE ainda pode injetar. A segurança vem do vinculo de parâmetros dentro do procedimento.
Um mapeador de objeto-relacional (ORM) pode substituir consultas parametrizadas?
Um ORM é seguro apenas quando você usa sua interface parametrizada. Construir uma string e passá-la para o ORM reintroduz a injeção. A fronteira entre código e dados ainda tem que ser aplicada.
Um modelo deve segurar a conexão de banco de dados?
Não. Mantenha a geração e a execução separadas: o modelo elabora, um portão aprova e o código de aplicação vincula parâmetros em tempo de execução. O modelo nunca deve ser o caminho de execução.
Apenas para suporte à decisão. Não é um substituto para a revisão do DBA ou testes de carga de produção. Não há garantias de desempenho.