Analisando Domínios de Typosquatting com DNSTwist
Visão Geral
O DNSTwist é um mecanismo de permutação de nomes de domínio que gera nomes de domínio visualmente semelhantes para detectar typosquatting, ataques de phishing homográficos e falsificação de marca. Ele cria milhares de permutações de domínio usando técnicas como substituição de caracteres, transposição, inserção, omissão e substituição por homóglifos, em seguida verifica registros DNS (A, AAAA, NS, MX), calcula a similaridade de páginas web usando hashing difuso (ssdeep) e hashing perceptual (pHash), e identifica domínios registrados potencialmente maliciosos.
Quando Usar
- Ao investigar incidentes de segurança que exigem análise de domínios de typosquatting com dnstwist
- Ao construir regras de detecção ou consultas de threat hunting para este domínio
- Quando analistas de SOC precisam de procedimentos estruturados para este tipo de análise
- Ao validar a cobertura de monitoramento de segurança para técnicas de ataque relacionadas
Pré-requisitos
- Python 3.9+ com
dnstwistinstalado (pip install dnstwist[full]) - Opcional: Banco de dados GeoIP para geolocalização de IP
- Opcional: Chave de API do Shodan para enriquecimento
- Acesso à rede para realizar consultas DNS
- Compreensão dos tipos de registro DNS e registro de domínio
Conceitos Chave
Técnicas de Permutação de Domínio
O DNSTwist gera permutações usando: adição (anexar caracteres), bitsquatting (erros de inversão de bits), homóglifo (caracteres Unicode visualmente semelhantes como rn vs m), hifenização (adicionar hífens), inserção (inserir caracteres), omissão (remover caracteres), repetição (repetir caracteres), substituição (substituir por teclas adjacentes do teclado), subdomínio (inserir pontos), transposição (trocar caracteres adjacentes), troca de vogais (trocar vogais) e baseada em dicionário (anexar palavras comuns).
Hashing Difuso e Similaridade Visual
O DNSTwist usa ssdeep (hash sensível à localidade) para comparar conteúdo HTML e pHash (hash perceptual) para comparar capturas de tela de páginas web. Isso ajuda a identificar sites de phishing clonados que imitam visualmente o site legítimo. Uma alta pontuação de similaridade indica uma página de phishing provável.
Fluxo de Trabalho de Detecção
O fluxo de trabalho típico é: gerar permutações de domínio -> resolver registros DNS -> verificar domínios registrados -> comparar similaridade de página web -> sinalizar domínios suspeitos -> alertar equipe de segurança -> solicitar remoção. Para um domínio corporativo típico, o dnstwist gera de 5.000 a 10.000 permutações.
Fluxo de Trabalho
Passo 1: Varredura Básica de Permutações de Domínio
import subprocess
import json
import csv
from datetime import datetime
def run_dnstwist_scan(domain, output_file=None):
"""Executa varredura dnstwist contra um domínio alvo."""
cmd = [
"dnstwist",
"--registered", # Mostrar apenas domínios registrados
"--format", "json", # Saída em JSON
"--nameservers", "8.8.8.8,1.1.1.1",
"--threads", "50",
"--mxcheck", # Verificar registros MX
"--ssdeep", # Comparação de hash difuso
"--geoip", # Consulta GeoIP
domain,
]
print(f"[*] Varrendo permutações para: {domain}")
result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
if result.returncode == 0:
results = json.loads(result.stdout)
registered = [r for r in results if r.get("dns_a") or r.get("dns_aaaa")]
print(f"[+] Encontrados {len(registered)} domínios falsos registrados")
if output_file:
with open(output_file, "w") as f:
json.dump(registered, f, indent=2)
print(f"[+] Resultados salvos em {output_file}")
return registered
else:
print(f"[-] Erro no dnstwist: {result.stderr}")
return []
results = run_dnstwist_scan("exemplo.com", "resultados_typosquat.json")
Passo 2: Analisar e Priorizar Resultados
def analyze_results(results, legitimate_ips=None):
"""Analisa resultados do dnstwist e prioriza ameaças."""
legitimate_ips = legitimate_ips or set()
high_risk = []
medium_risk = []
low_risk = []
for entry in results:
domain = entry.get("domain", "")
fuzzer = entry.get("fuzzer", "")
dns_a = entry.get("dns_a", [])
dns_mx = entry.get("dns_mx", [])
ssdeep_score = entry.get("ssdeep_score", 0)
risk_score = 0
risk_factors = []
# Alta similaridade com o site legítimo
if ssdeep_score and ssdeep_score > 50:
risk_score += 40
risk_factors.append(f"alta similaridade web ({ssdeep_score}%)")
# Possui registros MX (pode receber e-mail / phishing)
if dns_mx:
risk_score += 20
risk_factors.append("possui registros MX (capaz de receber e-mail)")
# Registrado recentemente (se dados whois disponíveis)
whois_created = entry.get("whois_created", "")
if whois_created:
try:
created = datetime.fromisoformat(whois_created.replace("Z", "+00:00"))
age_days = (datetime.now(created.tzinfo) - created).days
if age_days < 30:
risk_score += 30
risk_factors.append(f"registrado recentemente ({age_days} dias)")
elif age_days < 90:
risk_score += 15
risk_factors.append(f"registrado há {age_days} dias")
except (ValueError, TypeError):
pass
# Ataques homográficos são de maior risco
if fuzzer == "homoglyph":
risk_score += 25
risk_factors.append("homóglifo (visualmente idêntico)")
elif fuzzer in ("addition", "replacement", "transposition"):
risk_score += 10
risk_factors.append(f"tipo de permutação: {fuzzer}")
# Não está apontando para a infraestrutura legítima
if dns_a and not set(dns_a).intersection(legitimate_ips):
risk_score += 10
risk_factors.append("IP diferente do legítimo")
entry["risk_score"] = risk_score
entry["risk_factors"] = risk_factors
if risk_score >= 50:
high_risk.append(entry)
elif risk_score >= 25:
medium_risk.append(entry)
else:
low_risk.append(entry)
high_risk.sort(key=lambda x: x["risk_score"], reverse=True)
medium_risk.sort(key=lambda x: x["risk_score"], reverse=True)
print(f"\n=== Análise de Typosquatting ===")
print(f"Alto Risco: {len(high_risk)}")
print(f"Médio Risco: {len(medium_risk)}")
print(f"Baixo Risco: {len(low_risk)}")
if high_risk:
print(f"\n--- Domínios de Alto Risco ---")
for entry in high_risk[:10]:
print(f" {entry['domain']} (pontuação: {entry['risk_score']})")
for factor in entry['risk_factors']:
print(f" - {factor}")
return {"high": high_risk, "medium": medium_risk, "low": low_risk}
analysis = analyze_results(results, legitimate_ips={"93.184.216.34"})
Passo 3: Pipeline de Monitoramento Contínuo
import time
import hashlib
class TyposquatMonitor:
def __init__(self, domains, known_domains_file="known_typosquats.json"):
self.domains = domains
self.known_file = known_domains_file
self.known_domains = self._load_known()
def _load_known(self):
try:
with open(self.known_file, "r") as f:
return json.load(f)
except FileNotFoundError:
return {}
def _save_known(self):
with open(self.known_file, "w") as f:
json.dump(self.known_domains, f, indent=2)
def scan_all_domains(self):
"""Varre todos os domínios monitorados em busca de novos typosquats."""
new_findings = []
for domain in self.domains:
results = run_dnstwist_scan(domain)
for entry in results:
domain_key = entry.get("domain", "")
if domain_key not in self.known_domains:
entry["first_seen"] = datetime.now().isoformat()
entry["monitored_domain"] = domain
self.known_domains[domain_key] = entry
new_findings.append(entry)
print(f" [NOVO] {domain_key} ({entry.get('fuzzer', '')})")
self._save_known()
print(f"\n[+] Novos domínios de typosquatting encontrados: {len(new_findings)}")
return new_findings
def generate_alert(self, findings):
"""Gera alerta para novos domínios de typosquatting de alto risco."""
analysis = analyze_results(findings)
alerts = []
for entry in analysis["high"]:
alerts.append({
"severity": "HIGH",
"domain": entry["domain"],
"target": entry.get("monitored_domain", ""),
"risk_score": entry["risk_score"],
"risk_factors": entry["risk_factors"],
"dns_a": entry.get("dns_a", []),
"dns_mx": entry.get("dns_mx", []),
"timestamp": datetime.now().isoformat(),
})
return alerts
monitor = TyposquatMonitor(["minhaempresa.com", "minhaempresa.org"])
new_findings = monitor.scan_all_domains()
alerts = monitor.generate_alert(new_findings)
Passo 4: Exportar para Lista de Bloqueio e Remoção
def export_blocklist(analysis, output_file="blocklist.txt"):
"""Exporta domínios de alto risco como lista de bloqueio para firewall/proxy."""
domains = []
for entry in analysis["high"] + analysis["medium"]:
domain = entry.get("domain", "")
if domain:
domains.append(domain)
with open(output_file, "w") as f:
f.write(f"# Lista de bloqueio de typosquatting gerada em {datetime.now().isoformat()}\n")
for d in sorted(set(domains)):
f.write(f"{d}\n")
print(f"[+] Lista de bloqueio salva: {len(domains)} domínios -> {output_file}")
return domains
def generate_takedown_report(high_risk_domains):
"""Gera relatório de solicitação de remoção."""
report = f"""# Solicitação de Remoção de Domínio
Gerada em: {datetime.now().isoformat()}
## Resumo
{len(high_risk_domains)} domínios identificados como potencial typosquatting/phishing.
## Domínios que Requerem Remoção
"""
for entry in high_risk_domains:
report += f"""
### {entry['domain']}
- **Tipo de Permutação**: {entry.get('fuzzer', 'desconhecido')}
- **Endereço IP**: {', '.join(entry.get('dns_a', ['N/A']))}
- **Registros MX**: {', '.join(entry.get('dns_mx', ['N/A']))}
- **Pontuação de Risco**: {entry.get('risk_score', 0)}
- **Fatores de Risco**: {'; '.join(entry.get('risk_factors', []))}
- **Similaridade Web**: {entry.get('ssdeep_score', 'N/A')}%
"""
with open("relatorio_remocao.md", "w") as f:
f.write(report)
print("[+] Relatório de remoção gerado: relatorio_remocao.md")
export_blocklist(analysis)
generate_takedown_report(analysis["high"])
Critérios de Validação
- O DNSTwist gera permutações de domínio para o domínio alvo
- A resolução DNS identifica domínios falsos registrados
- A pontuação de similaridade web detecta páginas de phishing clonadas
- A pontuação de risco prioriza domínios por nível de ameaça
- O monitoramento contínuo detecta typosquats recém-registrados
- Listas de bloqueio e relatórios de remoção são gerados corretamente