O que é SQL Injection e como preveni-lo?
Uma análise prática de ataques de SQL injection, exemplos reais e as correções concretas que realmente funcionam em código de produção.
SQL injection existe desde o final dos anos 1990 e continua sendo uma das formas mais comuns de comprometer aplicações web. O bug é simples: um atacante envia uma entrada que é interpretada como código SQL em vez de dados simples, e o banco de dados faz algo que nunca deveria fazer.
Como o ataque realmente funciona
Imagine um formulário de login que constrói uma query assim em PHP:
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
Se a aplicação não sanitizar a entrada, um atacante digita admin' -- no campo de nome de usuário. A query se torna:
SELECT * FROM users WHERE username = 'admin' --' AND password = ''
O -- comenta o resto da linha, então a verificação de senha nunca acontece. Esse é um bypass de autenticação clássico. Atacantes também usam UNION SELECT para extrair dados de outras tabelas, ou empilham queries com ponto e vírgula para executar algo destrutivo como DROP TABLE users; se o driver permitir múltiplas declarações.
Existe também SQL injection cega, onde a aplicação não mostra resultados de query diretamente. Atacantes inferem informações através de timing (SLEEP(5) em MySQL) ou respostas booleanas (a página se comporta diferente para uma condição verdadeira vs. falsa). Ferramentas como sqlmap automatizam esse tipo de extração assim que um parâmetro vulnerável é encontrado.
Por que concatenação de strings é o problema raiz
Toda variante desse ataque volta a uma coisa: misturar código e dados na mesma string. O banco de dados não consegue diferenciar entre um valor legítimo e sintaxe injetada porque chegam no mesmo canal. Escapar aspas ajuda em alguns casos, mas é frágil — diferentes encodings, second-order injection (dados armazenados uma vez, depois reutilizados de forma insegura depois), e particularidades específicas do driver criam caminhos de bypass. Escapar é um remendo, não uma correção.
Queries paramétrizadas são a correção real
A correção é separar código SQL de dados do usuário no nível do driver, usando queries paramétrizadas (também chamadas de prepared statements). O banco de dados recebe a estrutura da query primeiro, depois vincula valores depois, então entrada do usuário nunca consegue mudar o significado da query.
Em Python com psycopg2:
cur.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))
Em Node.js com mysql2:
connection.execute('SELECT * FROM users WHERE username = ? AND password = ?', [username, password]);
Em Java com JDBC:
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE username = ? AND password = ?");
stmt.setString(1, username);
stmt.setString(2, password);
Note o padrão: placeholders (%s, ?) ocupam o lugar dos dados, e os valores reais são passados separadamente. Sem concatenação de strings, sem escaping manual necessário. Isso funciona para a grande maioria das queries que você escreverá em uma aplicação normal.
E quanto a nomes de tabela ou coluna dinâmicos?
Parametrização lida com valores mas não com identificadores — você não consegue vincular um nome de tabela como parâmetro. Se sua aplicação precisa selecionar uma tabela dinamicamente (raro, e frequentemente um cheiro de design), faça whitelist dos valores permitidos contra uma lista codificada em vez de confiar na entrada do usuário diretamente:
allowed_tables = {'orders', 'invoices', 'customers'}
if table_name not in allowed_tables:
raise ValueError("Invalid table")
Nunca construa nomes de identificadores através de formatação de string de entrada do usuário, mesmo com escaping.
Defesas em camadas além da query em si
Queries paramétrizadas são o controle primário, mas algumas outras coisas importam:
- Privilégio mínimo na conta do banco de dados. O usuário DB da aplicação não deveria ter
DROP,ALTER, ou acesso a schemas não relacionados. Se uma injeção escorregar, privilégios limitados limitam o dano. - ORMs ajudam por padrão. O ORM do Django, SQLAlchemy e Hibernate parametrizam queries automaticamente quando você usa seus métodos padrão de construção de query. O risco reaparece quando desenvolvedores caem em SQL bruto ou usam chamadas
.extra()/text()com interpolação de strings — então audite esses locais especificamente. - Validação de entrada é uma camada secundária, não uma substituição. Verificar que um campo de email parece um email é boa prática, mas não para injeção por si só — atacantes encontram payloads criativos que ainda passam validação solta.
- Um WAF pode detectar padrões de ataque conhecidos, mas é uma camada de detecção, não uma correção para o código subjacente.
Testando seu próprio código para esse bug
Execute suas queries através de uma ferramenta de análise estática (Bandit para Python, Semgrep com rulesets de SQL injection) como parte do CI. Para testes manuais, tente injetar uma aspa simples (') em todo campo de entrada e veja por vazamentos de mensagens de erro SQL na resposta — isso frequentemente é o primeiro sinal de que uma query não é parametrizada.
Se você quer ir mais a fundo nisso, a track Web Security do Korra Studio cobre injeção junto com XSS e auth bypass, e os segmentos de Databases passam por padrões de design de query que evitam essa classe de bug completamente.
Escrito com assistência de IA, revisado e publicado por Michal Pilch (CISSP), Korra Studio.
Esta é uma anotação da base de conhecimento da Korra Studio — a plataforma associa cada tema com mentoria 1-para-1.
Começar gratuitamentearrow_forward