O que é injeção de SQL e como posso evitá-la em C#?
A injeção de SQL é uma técnica de injeção de código que permite que invasores enviem código SQL malicioso para o seu servidor de banco de dados por meio da entrada do usuário. Em seu vídeo " O que é SQL Injection e como posso evitá-la em C#? ", Tim Corey demonstra exatamente como as vulnerabilidades de SQL Injection aparecem em código real, mostra vários exemplos de ataques de SQL Injection bem-sucedidos (incluindo ataques baseados em UNION e ataques destrutivos) e apresenta técnicas práticas de prevenção de SQL Injection que você pode aplicar em C#. Este artigo segue o passo a passo do Tim para que você possa ver exatamente os problemas e soluções que ele mostra.
Aplicativo de demonstração e por que isso é importante.
Tim começa com um pequeno aplicativo de demonstração WPF vinculado a um InjectableDB local (tabelas People e Secrets). A caixa de pesquisa do aplicativo, semelhante a um formulário da web, recebe a entrada do usuário (um sobrenome) e constrói uma consulta SQL para retornar o ID, o nome e o sobrenome. Funciona — digite Corey e você encontrará Tim Corey — mas Tim enfatiza o ponto principal: "Só porque um aplicativo funciona não significa que ele seja seguro." Um aplicativo web em funcionamento ainda pode ter vulnerabilidades de injeção de SQL quando a entrada fornecida pelo usuário é inserida diretamente em instruções SQL por meio de concatenação de strings ou SQL dinâmico.
Código inseguro — concatenação de strings e SQL dinâmico
Tim demonstra o padrão inseguro exato que muitos desenvolvedores utilizam:
var sql = $"SELECT * FROM People WHERE LastName = '{searchText}'";
var results = connection.Query<Person>(sql);
var sql = $"SELECT * FROM People WHERE LastName = '{searchText}'";
var results = connection.Query<Person>(sql);
Esta consulta original utiliza concatenação de strings para criar uma instrução SQL. Tim alerta: se você encontrar código que injeta entradas do usuário diretamente em consultas SQL, pare — isso é uma vulnerabilidade de injeção de SQL. Os atacantes podem criar entradas maliciosas que alteram a estrutura dos seus comandos SQL ou até mesmo executam instruções SQL maliciosas adicionais.
Como um atacante explora isso — UNION e DROP
Para demonstrar como funciona um ataque de injeção de SQL, Tim reproduz consultas no SQL Server e, em seguida, cria injeções usando UNION ALL e comentários SQL (--) para ocultar caracteres finais. Exemplos de payloads maliciosos que Tim demonstra:
-
Injeção de SQL baseada em UNION para ler outras tabelas:
UNION ALL SELECT ID, Username AS FirstName, Password AS LastName FROM Secrets;Isso mistura os resultados de Secrets com o conjunto de resultados original do SELECT, expondo dados confidenciais como nomes de usuário e senhas.
-
Injeção destrutiva para excluir tabelas:
DROP TABLE DemoTable;Isso executa uma segunda instrução SQL (DROP TABLE) finalizando a primeira instrução com um ponto e vírgula e, em seguida, adicionando o comando destrutivo. Tim mostra que a tabela desapareceu — o banco de dados foi modificado por um comando SQL malicioso.
A questão levantada por Tim é a seguinte: os atacantes não precisam saber os nomes das tabelas ou colunas com antecedência — eles podem enumerar os nomes das tabelas ou colunas nos servidores de banco de dados, ou simplesmente tentar técnicas baseadas em análise de dados ou em tempo real para descobrir o comportamento.
Correção 1 — Consultas parametrizadas
A primeira e principal defesa de Tim é parar de construir strings SQL com dados do usuário. Substitua o SQL dinâmico por consultas parametrizadas:
string sql = "SELECT * FROM People WHERE LastName = @LastName";
var results = connection.Query<Person>(sql, new { LastName = searchText });
string sql = "SELECT * FROM People WHERE LastName = @LastName";
var results = connection.Query<Person>(sql, new { LastName = searchText });
Tim explica que a parametrização (uso no estilo de instruções preparadas) significa que o banco de dados trata a entrada fornecida pelo usuário estritamente como dados — o SQL malicioso se torna apenas um valor de string e não pode alterar a estrutura do SQL. Isso impede muitos ataques comuns de injeção de SQL, incluindo payloads baseados em union e anexados; Comandos DROP TABLE.
Ele também recomenda combinar a parametrização com a validação mínima de entrada: higienizar ou bloquear caracteres improváveis em um sobrenome (por exemplo, ponto e vírgula ou marcadores de comentário --) enquanto permite caracteres legítimos como apóstrofos (O'Reilly). Consultas parametrizadas e limpeza de entrada oferecem proteção substancial contra ataques de injeção de SQL.
Correção 2 — Procedimentos armazenados
Em seguida, Tim mostra dois procedimentos armazenados: um procedimento armazenado inseguro que concatena SQL dentro do procedimento e depois o executa, e um procedimento armazenado seguro que usa parâmetros diretamente.
-
O procedimento armazenado inseguro constrói uma string @sql a partir do parâmetro e a executa — ainda vulnerável a injeção.
- O procedimento armazenado seguro executa SELECT ... WHERE LastName = @LastName e executa com o parâmetro — seguro.
Tim esclarece: procedimentos armazenados não são uma solução automática se você ainda construir SQL dinâmico dentro deles. Mas, quando usadas corretamente (sem SQL dinâmico), as stored procedures ajudam a centralizar as instruções SQL, facilitando a parametrização e a auditoria de consultas. Os procedimentos armazenados também podem ajudar a simplificar a prevenção de injeção de SQL em seu aplicativo.
Não confie em nenhum dado — nem mesmo em dados de banco de dados.
Um ponto importante, e muitas vezes ignorado, que Tim levanta: você não deve confiar cegamente nos dados recuperados do seu próprio banco de dados SQL. Os atacantes às vezes inserem cargas maliciosas em colunas (uma "bomba-relógio") que serão posteriormente concatenadas em SQL dinâmico por outro processo. Tim insiste: use sempre parâmetros e higienize os dados em cada etapa — seja por meio de um formulário da web, um upload de arquivo ou seu próprio banco de dados — para que entradas maliciosas não se tornem posteriormente uma via de injeção.
Dica extra — princípio do menor privilégio e limitação de privilégios em bancos de dados.
Além das correções de código, Tim recomenda uma configuração defensiva: limite os privilégios de banco de dados para as contas do seu aplicativo. Na demonstração, a conexão utiliza uma conta de administrador por meio de segurança integrada — o que é perigoso. Em vez disso, utilize o princípio do menor privilégio:
-
Crie uma conta de banco de dados para o aplicativo com apenas os direitos necessários.
-
Se você usar procedimentos armazenados, conceda a essa conta apenas a permissão EXECUTE em procedimentos armazenados específicos e nada mais.
- Não conceda privilégios de administrador amplos às contas de aplicativos, que permitam executar comandos como DROP TABLE, listar todas as tabelas ou ler outros bancos de dados.
Isso reduz o impacto de um ataque de injeção de SQL bem-sucedido — mesmo que a injeção seja possível, o invasor não poderá fazer mais do que a conta permite.
Tim também observa que o Entity Framework complica isso: o EF geralmente requer permissões elevadas (migrações, alterações de esquema). Se você usa o EF em produção, planeje suas permissões e implantação com cuidado.
Resumo — parar, parametrizar, higienizar, limitar
Tim conclui seu vídeo com uma lista de verificação clara para evitar injeção de SQL em aplicações C#:
-
Pare de criar instruções SQL com concatenação de strings ou SQL dinâmico que inclua entrada do usuário.
-
Utilize consultas parametrizadas/padrões de instruções preparadas para que os dados do usuário sejam sempre tratados como dados.
-
Limpe a entrada quando apropriado (bloqueie ponto e vírgula, comentários SQL e caracteres inesperados).
-
Prefira procedimentos armazenados seguros (sem SQL dinâmico em seu interior) para centralizar a lógica de consulta.
-
Aplique o princípio do menor privilégio às contas de banco de dados — limite o que o usuário do banco de dados do aplicativo pode fazer.
- Analise o código (especialmente os locais que constroem SQL dinamicamente) e teste em busca de falhas de injeção de SQL.
Último aviso de Tim: o manuseio inadequado de entradas de usuário, SQL dinâmico e privilégios excessivos de banco de dados podem levar a violações graves — vazamento de dados confidenciais, tabelas destruídas ou exfiltração de dados não detectada e de longa duração. Considere a prevenção de injeção de SQL como um requisito fundamental de segurança, não como um mero aprimoramento opcional.
