Back

Prevenção de SQL Injection: Um Guia Completo para Devs Modernos

SQL Injection (SQLi) continua sendo uma das vulnerabilidades mais antigas e perigosas em aplicações web. Apesar de ser um problema conhecido há décadas, ele marca presença constante no Top 10 da OWASP. Neste mergulho profundo, vamos explorar como o SQLi funciona "debaixo do capô", por que ele ainda acontece e, o mais importante, como blindar suas aplicações contra ele.

A Anatomia de um Ataque

Basicamente, o SQL Injection acontece quando um input de usuário não confiável é concatenado diretamente em uma string de query do banco de dados. Isso permite que um atacante manipule a estrutura da query, acessando, modificando ou até deletando dados que não deveria.

Imagine um sistema de login legado usando uma query crua (raw query):

SELECT * FROM users WHERE username = '$username' AND password = '$password';

Se um usuário digitar admin' -- no campo de usuário, a query resultante vira:

SELECT * FROM users WHERE username = 'admin' --' AND password = '...';

A sequência -- comenta o resto da query, anulando a verificação de senha e logando o atacante como administrador. Esse é um ataque clássico de "tautologia", mas os vetores modernos de SQLi podem ser muito mais sofisticados, incluindo blind injection, ataques baseados em tempo e exfiltração de dados out-of-band.

Por que "Sanitização" Não é Suficiente

Um erro comum é achar que "sanitizar" o input (escapar caracteres especiais como aspas) resolve tudo. Ajuda, claro, mas é uma defesa frágil. Diferentes bancos de dados lidam com escape de formas diferentes, e casos extremos (como exploits com caracteres multibyte) muitas vezes conseguem driblar filtros simples.

Em vez de tentar limpar a sujeira, o padrão da indústria é separar os dados do código completamente.

O Padrão Ouro: Queries Parametrizadas

Queries parametrizadas (ou Prepared Statements) são a defesa mais "bala de prata" que temos contra SQLi. Elas forçam o banco de dados a tratar o input do usuário estritamente como dado, nunca como código executável.

Quando você usa um prepared statement, o banco primeiro compila a estrutura da query SQL, deixando placeholders (espaços reservados) para os dados. O input do usuário é então "amarrado" (bound) a esses placeholders. Mesmo que o input contenha comandos SQL perigosos, o banco vai tratá-los como simples strings de texto.

Exemplo em Node.js (pg)

// ❌ VULNERÁVEL const query = `SELECT * FROM products WHERE id = ${req.params.id}`; client.query(query); // ✅ SEGURO const query = 'SELECT * FROM products WHERE id = $1'; client.query(query, [req.params.id]);

Exemplo em Python (Psycopg2)

# ❌ VULNERÁVEL cur.execute("SELECT * FROM users WHERE name = '" + username + "'") # ✅ SEGURO cur.execute("SELECT * FROM users WHERE name = %s", (username,))

Usando ORMs para Segurança

ORMs modernos como Prisma, TypeORM ou Sequelize já usam queries parametrizadas por padrão. Usar um ORM reduz drasticamente o risco de injeção acidental porque você raramente escreve SQL na mão.

Mas atenção: cuidado com as "saídas de emergência" (escape hatches) que os ORMs oferecem para rodar queries cruas (Raw Queries). Sempre use as features de binding de parâmetros do ORM quando precisar descer para o nível do SQL puro.

Debugando e Formatando SQL

Quando você está lidando com queries complexas ou debugando código legado, ler strings de SQL cruas pode ser um pesadelo. Um SQL mal formatado esconde vulnerabilidades e erros de lógica.

Dica Pro: Se você está batendo cabeça com logs de SQL bagunçados ou precisa montar queries complexas com segurança, dá uma olhada no nosso Formatador SQL. Ele ajuda a visualizar a estrutura da sua query com clareza, facilitando identificar onde você deve usar parâmetros em vez de valores diretos.

Estratégias de Defesa Avançadas

Além das queries parametrizadas, considere estas camadas extras de defesa:

1. Princípio do Menor Privilégio

Garanta que o usuário de banco de dados da sua aplicação tenha apenas as permissões mínimas necessárias. Uma aplicação web raramente precisa de DROP TABLE ou GRANT. Se uma vulnerabilidade for encontrada, permissões limitadas contêm o estrago.

2. Validação de Input

Valide o input contra uma lista branca (Allowlist) rigorosa. Se um parâmetro espera um número inteiro, garanta que ele seja um inteiro antes mesmo de chegar perto do banco de dados.

3. Web Application Firewall (WAF)

Um WAF pode ajudar a detectar e bloquear padrões comuns de SQL injection no tráfego de entrada, servindo como uma rede de segurança para sua aplicação.

Conclusão

SQL Injection é um problema resolvido, mas exige disciplina. Ao adotar queries parametrizadas como regra inegociável, usar ORMs com sabedoria e manter seu código SQL limpo e legível, você constrói aplicações seguras "by design".

Não confie em "limpar" input ruim. Arquiteture seu sistema para que um input ruim nunca tenha a chance de virar código malicioso.

securitydatabasesqlweb-developmentbackend

Explore ferramentas relacionadas

Experimente estas ferramentas gratuitas do Pockit