Pular para o conteúdo
Categoria: Pentest & Hacking Ético12 min de leitura

Explorando SQL Injection na prática (ético)

Por Schematize Blog ·

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'='1

    A 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ável

          Se 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 colunas

          Com o número de colunas conhecido, anexe sua própria consulta:

          ?id=10 UNION SELECT usuario, senha FROM usuarios

          Há 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)) > 109

          Na 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 --batch

            A 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.

                Referências

                  Leituras relacionadas

                  Nenhum comentário ainda

                  Seja o primeiro a comentar.

                  Deixe seu comentário

                  Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

                  Entrar com Canverly