Como começar a parametrizar consultas
Mary Gathoni
24 de outubro de 2023
0 minutos de leituraQuando aplicações web dependem de dados fornecidos pelos usuários, existe o risco de ataques de injeção de SQL. Injeção de SQL é uma técnica usada para alterar uma instrução SQL por meio da manipulação da entrada do usuário. Mais especificamente, os invasores enviam código SQL malicioso ao banco de dados para contornar medidas de segurança e obter acesso não autorizado aos dados. As consequências podem ser graves, desde o vazamento de informações confidenciais até a perda total do sistema.
Há várias formas de evitar um ataque de injeção de SQL, e a parametrização de consultas SQL é uma das principais. Ela consiste em separar o código SQL dos valores fornecidos pelo usuário usando espaços reservados (parâmetros), para que a entrada seja tratada como dado, e não como código SQL. O segredo é usar instruções preparadas com consultas parametrizadas — uma combinação extremamente eficaz.
Para adicionar uma camada extra de segurança, podemos usar procedimentos armazenados parametrizados. Procedimentos armazenados são instruções SQL pré-compiladas e armazenadas no banco de dados. Podemos conceder permissões para executar o procedimento armazenado e, ao mesmo tempo, restringir o acesso direto à tabela subjacente. Quando formos usar um procedimento, passamos os parâmetros esperados pela nossa aplicação.
Neste artigo prático, vamos mostrar como usar a parametrização de consultas SQL e procedimentos armazenados para evitar ataques de injeção, além de outras medidas de segurança que ajudam a manter seu código protegido.
Como implementar a parametrização de consultas
Para implementar a parametrização de consultas, usamos espaços reservados nas consultas SQL para representar os dados fornecidos pelo usuário. Esses marcadores temporários são preenchidos pelo mecanismo SQL com os valores reais das variáveis. Ao executar a consulta, o mecanismo SQL associa as variáveis aos espaços reservados antes da execução, garantindo que elas sejam tratadas como dados literais.
Você pode parametrizar consultas com ou sem instruções preparadas. A diferença entre as duas abordagens é que as instruções preparadas são pré-compiladas e podem ser chamadas várias vezes com parâmetros diferentes.
Mais importante ainda, o uso de instruções preparadas pode melhorar a parametrização das consultas, pois o banco de dados garante que a instrução seja pré-compilada, reduzindo o tempo de execução. Além disso, como o plano de execução já foi criado, os parâmetros não influenciam esse plano — o que fortalece sua postura de segurança.
Se você precisar chamar uma consulta várias vezes, o banco de dados não terá que compilá-la novamente: basta passar os parâmetros desejados para a instrução preparada. Além disso, o uso de parâmetros vinculados reduz o uso de banda, pois apenas os parâmetros são enviados ao servidor, e não a consulta inteira.
As próximas seções mostram como implementar a parametrização de consultas em uma aplicação web.
Pré-requisitos
Para acompanhar esta demonstração, você precisa de:
Conhecimentos práticos de .NET
Conhecimentos práticos sobre como escrever consultas SQL.
Parametrização de consultas sem instruções preparadas
Para fins de demonstração, vamos usar uma aplicação de console .NET que recebe dados do usuário pela linha de comando e os utiliza como um dos valores em uma instrução de consulta SQL.
Vamos começar vendo como parametrizar a consulta sem usar uma instrução preparada.
Com o código abaixo, obtenha o nome de usuário com `Console.Readline()` e, em seguida, defina a consulta. Em vez de concatenar o nome de usuário à consulta, use um espaço reservado identificado pelo caractere `@`. Depois, usaremos o método `AddWithValue()` para associar o nome de usuário à consulta antes de executá-la:
Ao passar os valores dos parâmetros depois de definir a consulta, garantimos que o servidor SQL os interprete literalmente.
Parametrização de consultas com instruções preparadas
No mesmo cenário, vamos ver como atualizar a consulta de busca para usar uma instrução preparada.
A string da consulta continua igual e usando os espaços reservados `@id` e `@username`. Mas, ao contrário do exemplo anterior, depois de definir os parâmetros, chamamos o método `Prepare` no objeto `SqlCommand`.
Antes de chamar `Prepare()`, especifique o tipo de dado de cada parâmetro na instrução. Você também precisa definir a propriedade de tamanho com o valor máximo para evitar um erro.
Em seguida, use o método `ExecuteNonQuery` para executar a instrução `INSERT` com os parâmetros fornecidos e inserir os dados do usuário na tabela `Users`, como mostrado abaixo:
Com instruções preparadas, precisamos chamar o método `Prepare()` apenas uma vez. Depois, podemos alterar os valores dos parâmetros e executar a consulta várias vezes sem compilá-la novamente.
Como trabalhar com procedimentos armazenados
As instruções preparadas permitem reutilizar consultas na aplicação. No entanto, não podemos chamar uma instrução preparada a partir de várias aplicações nem implementar controles de acesso como fazemos com procedimentos armazenados.
Procedimentos armazenados são consultas pré-compiladas, guardadas no servidor de banco de dados e que podem ser chamadas repetidamente por várias aplicações. Quando implementados corretamente, oferecem a mesma proteção contra ataques de injeção de SQL que as instruções preparadas. Podemos controlar quem tem permissão para executar um procedimento armazenado e até restringir o acesso direto à tabela subjacente, reduzindo assim o impacto de uma eventual injeção de SQL.
Assim como as instruções preparadas, os procedimentos armazenados também permitem consultas parametrizadas e tratam a entrada do usuário como dado, e não como código SQL. Além disso, o banco de dados higieniza automaticamente os parâmetros passados ao procedimento. Esse processo impede que invasores enviem código malicioso ao banco de dados.
Devemos usar procedimentos armazenados com parâmetros de consulta, em vez de interpolar valores dinâmicos diretamente na consulta. Ao manter uma separação clara entre a entrada do usuário e a instrução SQL, impedimos que o mecanismo SQL a interprete como código.
Para demonstrar, vamos criar no banco de dados um procedimento armazenado que aceite os valores `Id` e `Name`:
Se você estiver usando o SQL Server Management Studio, execute o procedimento no editor de consultas e lembre-se de substituir db pelo nome do seu banco de dados.
Veja um exemplo de como usar o procedimento armazenado para inserir novos usuários na tabela.
Primeiro, use `Console.Readline()` para solicitar um nome de usuário pela linha de comando. Em seguida, estabeleça uma conexão com o banco de dados usando o comando `SQLConnection`. Depois, chame o procedimento armazenado `InsertUser` que você criou no exemplo acima.
Para associar os parâmetros a um procedimento armazenado, use o método `Parameters.AddWithValue`. Neste exemplo, definimos um valor fixo para o parâmetro `@id` e passamos o nome de usuário informado pelo usuário para o parâmetro `@name`.
Depois de chamar o procedimento armazenado e passar as variáveis, executamos a consulta com o método `ExecuteNonQuery`.
Medidas adicionais de segurança para consultas ao banco de dados
Além de usar a parametrização com instruções preparadas e procedimentos armazenados, existem outras medidas de segurança que ajudam a proteger as consultas SQL.
Higienize e valide as entradas
Como mencionamos, invasores podem explorar dados de entrada para executar código malicioso no banco de dados. Por isso, é muito importante higienizar e validar as entradas.
A higienização remove caracteres indesejados (por exemplo, `{}`, `'` e `/`) ou HTML inseguro dos dados fornecidos pelo usuário. Já a validação garante que esses dados estejam no formato esperado pelo banco de dados. Por exemplo, podemos verificar o tamanho da entrada e rejeitar as que forem longas demais. Também podemos exigir formatos específicos para endereços de e-mail e datas. Essa técnica impede que um invasor envie entradas especialmente criadas para incluir instruções SQL.
Higienizar e validar as entradas ajuda a controlar os dados usados nas consultas SQL. Porém, isso não é infalível: invasores podem usar técnicas como codificação dupla para contornar nossas proteções. Por exemplo, um invasor pode codificar uma aspa simples (`'`) como `%2527`, em vez de usar a forma habitual `%27`, para burlar filtros de entrada que decodificam apenas uma vez. Por isso, não use a higienização de entradas como defesa contra injeção de SQL. Em vez disso, parametrize suas consultas e use a higienização como uma camada adicional de segurança.
Codifique a saída
Ao exibir dados fornecidos pelo usuário em uma aplicação web, sempre os codifique primeiro. A codificação da saída impede que esses dados sejam interpretados como HTML, JavaScript ou SQL.
Use listas de permissões
Nem sempre é possível parametrizar consultas. A parametrização não é compatível com certas partes das consultas SQL, como nomes de tabelas ou colunas. Nesses casos, é melhor validar as entradas usando uma lista de permissões. Ela permite definir regras estritas que aceitam apenas determinados caracteres ou padrões, garantindo a rejeição de entradas maliciosas.
Uma lista de permissões é mais eficaz para evitar ataques de injeção de SQL do que uma lista de bloqueio. Ela define explicitamente quais tipos de entrada são permitidos, enquanto a lista de bloqueio deixa brechas que os invasores podem explorar facilmente com novas variações de entrada. Em resumo, uma lista de permissões dá a você mais controle sobre o que é aceito.
Aplique o princípio do menor privilégio
Para minimizar o impacto de um ataque de injeção de SQL bem-sucedido, sempre aplique o princípio do menor privilégio (PoLP). Esse princípio limita os direitos de acesso dos usuários ao mínimo necessário para realizar suas tarefas.
Por exemplo, se uma conta de usuário precisa apenas de acesso de leitura a uma tabela, seguindo o PoLP, concederíamos permissão somente de leitura para essa tabela. Se o usuário precisar acessar apenas parte dos dados de uma tabela, podemos criar uma visualização que inclua somente esses dados e conceder à conta acesso a ela, em vez de liberar o acesso à tabela inteira.
Se você usa procedimentos armazenados, limite as permissões de execução da sua aplicação aos procedimentos de que ela precisa. Assim, mesmo que ocorra uma injeção de SQL, você limitará o acesso do invasor e reduzirá o possível impacto.
Proteja seu código com o Snyk
Testes de injeção de SQL podem oferecer proteção adicional contra vulnerabilidades desse tipo. Ferramentas automatizadas de teste de segurança de aplicações estáticas (SAST), como o Snyk Code, ajudam você a identificar problemas de segurança logo no início.
O Snyk oferece um scanner de vulnerabilidades que analisa seu código diretamente na IDE. Você também pode encontrar uma extensão para Visual Studio Code no gerenciador de extensões. Siga as etapas para instalá-la e configurá-la.
Quando a configuração estiver concluída, você poderá começar a usar a extensão clicando no logotipo do Snyk na barra lateral. Será exibida uma barra de menus como a da imagem abaixo. Cada item tem uma seta suspensa que você pode clicar para começar. Vamos usar CODE SECURITY.

Se executarmos a análise de segurança de código em uma aplicação .NET que não usa consultas parametrizadas, o Snyk alertará que o projeto tem uma vulnerabilidade de injeção de SQL.
Veja o método a seguir, que usa formatação de strings para concatenar a entrada do usuário à consulta:
Se executarmos a análise de segurança de código no código acima, o Snyk encontrará uma vulnerabilidade, como mostra a captura de tela abaixo.

O relatório de análise indica que uma entrada não higienizada, recebida como argumento pela linha de comando, é usada na consulta SQL, o que pode causar uma vulnerabilidade de injeção de SQL. O Snyk identifica a linha e o arquivo com a vulnerabilidade. Ele também mostra exemplos de possíveis correções para ajudar a corrigir vulnerabilidades mais rapidamente.
Conclusão
A parametrização de consultas é fundamental para evitar ataques de injeção de SQL. Ela garante que a entrada do usuário seja tratada como dado, e não como código executável, reduzindo efetivamente o risco de injeção de SQL maliciosa.
Já discutimos as vantagens de implementar a parametrização de consultas, com ou sem instruções preparadas — e observamos que a primeira opção oferece mais segurança, pois separa a lógica SQL dos dados inseridos pelo usuário, eliminando a necessidade de fazer escape ou sanitização manual. As instruções preparadas também melhoram o desempenho, já que as consultas são pré-compiladas.
Uma alternativa às instruções preparadas são os procedimentos armazenados, que acrescentam uma camada extra de segurança quando usados com parâmetros de consulta, pois permitem controlar quem tem acesso.
Para reforçar ainda mais a segurança da sua aplicação, podemos incorporar o scanner de vulnerabilidades da Snyk ao fluxo de desenvolvimento. Ao analisar regularmente nossa base de código, podemos identificar vulnerabilidades que podem levar a ataques de injeção de SQL. Essa prática nos permite lidar com possíveis riscos de segurança durante o desenvolvimento, reduzindo as chances de expor nossa aplicação a invasores.
Comece a jogar Capture the Flag
Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.



