Blogue do SQLFix

Consultas parametrizadas vs SQL gerado

Um modelo que escreve uma consulta com um aspeto correto não resolve a injeção. A propriedade de segurança que importa vive na forma como a consulta encontra os dados, e essa é a tarefa das consultas parametrizadas com declarações preparadas. O texto gerado é um ponto de partida para revisão; não é um substituto para a fronteira que as declarações preparadas impõem entre o código e os dados.

O que uma declaração preparada realmente faz

Quando o driver recebe um pedido de preparação, 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 a aplicação 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 útil 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. Todas as linguagens principais fornecem uma API de ligaçã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, não está usando uma declaração preparada verdadeira; provavelmente está fazendo interpolação de string no lado do cliente, o que não fornece a segurança contra injeção.

Porque o SQL revisado ainda precisa de parâmetros

A revisão humana pode confirmar que a forma da consulta corresponde à regra de negócio pretendida. Pode detectar junções em falta, nomes de colunas incorrectos ou filtros ineficientes. No entanto, os valores que fluem para esses espaços reservados ainda vêm de fontes externas: formulários web, cargas de API, filas de mensagens ou ficheiros legados. Essas fontes não estão sob o controlo directo 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.

Embora o revisor humano tenha visto um comando INSERT sensato, a injeção ocorreu no passo de ligação de valores, que a revisão não abrangeu.

Desta forma, o trabalho do revisor permanece valioso, e a etapa de execução mantém a sua garantia de segurança.

Procedimentos armazenados não são segurança automática

Muitas equipas 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 stringa resultante torna-se um lote válido que elimina a tabela. O invólucro 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.

Depois da reescrita, execute o procedimento com um valor que contenha uma aspa e verifique que o erro é uma incompatibilidade de tipo de dados, e não um erro de sintaxe.

Manter 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 portal de política humano ou automatizado revisa essa forma para correcção, conformidade e desempenho. Só após a aprovação, o código da aplicação pega a forma, prepara-a com o controlador e vincula os parâmetros em tempo real.

Como o modelo nunca vê uma ligação ativa, não pode ser enganado para revelar detalhes do esquema através de mensagens de erro. Como a camada de execução nunca confia em strings brutos, mesmo um modelo comprometido não pode causar injeção.

Este processo mantém a saída do modelo no loop de revisão e garante que a etapa de execução utilize apenas declarações parametrizadas.

O que isto não é

Quero ser claro sobre os limites da abordagem descrita acima. Isto não garante que todos os possíveis bugs sejam detectados. Isto não promete 100 % de proteção contra todas as classes de falhas. Isto não afirma eliminar a necessidade de outras defesas, como validação de entrada, codificação de saída ou contas de base 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 ligaçã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 no mundo real

Imagine um produto SaaS que permite aos gestores exportar uma lista de faturas em atraso. O produto utiliza o SQLForm para elaborar a consulta.

Esta traça mostra como a etapa de geração permanece independente da etapa de execução, e como a fronteira do parâmetro bloqueia a injeção mesmo quando o utilizador tenta contrabandear SQL para um campo que o modelo nunca vê.

Armadilhas comuns e como as evitamos

Mesmo com um design sólido, as equipas por vezes caem em hábitos que enfraquecem a proteção. Abaixo estão erros frequentes que observei e as medidas 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 correcto de espaços reservados, e depois concatenando os valores directamente no SQL antes de o preparar. Isto derrota o objectivo porque os valores se tornam parte do texto da instrução.

Uma instrução preparada para SELECT * FROM orders WHERE status =? é mais tarde executada com um valor que na verdade contém uma nova instrução SELECT completa, confiando que o controlador trate o valor como dados. Embora o valor esteja devidamente citado, a lógica da aplicação pode estar errada porque o desenvolvedor assumiu que a instrução podia lidar com qualquer SQL.

Dashboards internos por vezes contornam o portão de revisão humana porque a equipa assume que os dados são de confiança. Mais tarde, uma conta de serviço é comprometida e começa a alimentar payloads maliciosos no dashboard.

Para depuração, as equipas por vezes registam os valores exactos dos parâmetros. Se esses registos forem expostos, podem vazar dados sensíveis, como palavras-passe ou informações de identificação pessoal.

Um ORM pode oferecer um método que parece aceitar parâmetros, mas por baixo dos panos constrói uma string e chama ExecuteNonQuery em uma string SQL bruta.

Ao vigiar estas armadilhas e aplicar as fixações correspondentes, a separação entre geração e execução permanece forte, e a fronteira parametrizada continua a fazer o trabalho para que foi projectada.

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 de 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 da ligaçã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 tem que ser aplicada.

Não. Mantenha a geração e a execução separadas: o modelo elabora um esboço, um portão aprova e o código de aplicação liga parâmetros em tempo de execução. O modelo nunca deve ser o caminho de execução.

Experimente 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 pedido correcto protege contra injeção SQL?

Não. A injeção depende de como os valores satisfazem o pedido, e não de se o pedido é logicamente correcto. Pedidos parametrizados mantêm os valores separados do texto de comando, independentemente do conteúdo dos valores.

Os procedimentos armazenados são seguros por defeito?

Não automaticamente. Um procedimento que concatena a entrada e a executa com EXECUTE IMMEDIATE ainda pode injectar. A segurança vem da ligação de parâmetros dentro do procedimento.

Um mapeador de objectos-relacionais (ORM) pode substituir pedidos parametrizados?

Um ORM é seguro apenas quando se utiliza a sua interface parametrizada. Construir uma cadeia e passá-la ao ORM reintroduz a injeção. A fronteira entre código e dados ainda tem que ser aplicada.

Um modelo deve alguma vez deter a ligação à base de dados?

Não. Mantenha a geração e a execução separadas: o modelo elabora, um portal de aprovação aprova, e o código de aplicação liga parâmetros em tempo de execução. O modelo nunca deve ser o caminho de execução.

Apenas para apoio à decisão. Não é um substituto para a revisão do DBA ou testes de carga de produção. Sem garantias de desempenho.

← Voltar ao blogue

Este resultado foi útil?Feedback