Que fait Analyse des domaines typosquatting avec DNSTwist ?
Présentation
DNSTwist est un moteur de permutation de noms de domaine qui génère des noms de domaine visuellement similaires pour détecter le typosquatting, les attaques de phishing homographique et l'usurpation d'identité de marque. Il crée des milliers de permutations en utilisant des techniques comme la substitution de caractères, la transposition, l'insertion, l'omission et le remplacement par des homoglyphes, puis vérifie les enregistrements DNS (A, AAAA, NS, MX), calcule la similarité des pages web via le hachage flou (ssdeep) et le hachage perceptuel (pHash), et identifie les domaines enregistrés potentiellement malveillants.
Quand l'utiliser
- Lors d'enquêtes sur des incidents de sécurité nécessitant l'analyse de domaines typosquatting avec dnstwist
- Lors de la création de règles de détection ou de requêtes de chasse aux menaces pour ce domaine
- Lorsque les analystes SOC ont besoin de procédures structurées pour ce type d'analyse
- Lors de la validation de la couverture de surveillance de sécurité pour les techniques d'attaque associées
Prérequis
- Python 3.9+ avec
dnstwistinstallé (pip install dnstwist[full]) - Optionnel : base de données GeoIP pour la géolocalisation IP
- Optionnel : clé API Shodan pour l'enrichissement
- Accès réseau pour effectuer des requêtes DNS
- Compréhension des types d'enregistrements DNS et de l'enregistrement des domaines
Concepts clés
Techniques de permutation de domaines
DNSTwist génère des permutations en utilisant : l'ajout (ajout de caractères), le bitsquatting (erreurs de basculement de bits), les homoglyphes (caractères Unicode visuellement similaires comme rn vs m), la césure (ajout de tirets), l'insertion (insertion de caractères), l'omission (suppression de caractères), la répétition (répétition de caractères), le remplacement (remplacement par des touches adjacentes du clavier), le sous-domaine (insertion de points), la transposition (échange de caractères adjacents), l'échange de voyelles (échange de voyelles) et une approche basée sur un dictionnaire (ajout de mots courants).
Hachage flou et similarité visuelle
DNSTwist utilise ssdeep (hachage sensible à la localité) pour comparer le contenu HTML et pHash (hachage perceptuel) pour comparer les captures d'écran des pages web. Cela permet d'identifier les sites de phishing clonés qui imitent visuellement le site légitime. Un score de similarité élevé indique une page de phishing probable.
Flux de détection
Le flux de travail typique est : générer des permutations de domaines -> résoudre les enregistrements DNS -> vérifier les domaines enregistrés -> comparer la similarité des pages web -> signaler les domaines suspects -> alerter l'équipe de sécurité -> demander le retrait. Pour un domaine d'entreprise typique, dnstwist génère 5 000 à 10 000 permutations.
Flux de travail
Étape 1 : Analyse de base des permutations de domaines
import subprocess
import json
import csv
from datetime import datetime
def run_dnstwist_scan(domain, output_file=None):
"""Exécute une analyse dnstwist sur un domaine cible."""
cmd = [
"dnstwist",
"--registered", # Afficher uniquement les domaines enregistrés
"--format", "json", # Sortie au format JSON
"--nameservers", "8.8.8.8,1.1.1.1",
"--threads", "50",
"--mxcheck", # Vérifier les enregistrements MX
"--ssdeep", # Comparaison par hachage flou
"--geoip", # Recherche GeoIP
domain,
]
print(f"[*] Analyse des permutations pour : {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"[+] {len(registered)} domaines similaires enregistrés trouvés")
if output_file:
with open(output_file, "w") as f:
json.dump(registered, f, indent=2)
print(f"[+] Résultats enregistrés dans {output_file}")
return registered
else:
print(f"[-] Erreur dnstwist : {result.stderr}")
return []
results = run_dnstwist_scan("example.com", "typosquat_results.json")
Étape 2 : Analyser et prioriser les résultats
def analyze_results(results, legitimate_ips=None):
"""Analyse les résultats dnstwist et priorise les menaces."""
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 = []
# Forte similarité avec le site légitime
if ssdeep_score and ssdeep_score > 50:
risk_score += 40
risk_factors.append(f"similarité web élevée ({ssdeep_score}%)")
# Possède des enregistrements MX (peut recevoir des e-mails / phishing)
if dns_mx:
risk_score += 20
risk_factors.append("possède des enregistrements MX (capable d'envoyer des e-mails)")
# Enregistré récemment (si les données whois sont disponibles)
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"enregistré récemment ({age_days} jours)")
elif age_days < 90:
risk_score += 15
risk_factors.append(f"enregistré il y a {age_days} jours")
except (ValueError, TypeError):
pass
# Les attaques par homoglyphes sont les plus risquées
if fuzzer == "homoglyph":
risk_score += 25
risk_factors.append("homoglyphe (visuellement identique)")
elif fuzzer in ("addition", "replacement", "transposition"):
risk_score += 10
risk_factors.append(f"type de permutation : {fuzzer}")
# Ne pointe pas vers l'infrastructure légitime
if dns_a and not set(dns_a).intersection(legitimate_ips):
risk_score += 10
risk_factors.append("IP différente de celle du site légitime")
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=== Analyse du typosquatting ===")
print(f"Risque élevé : {len(high_risk)}")
print(f"Risque moyen : {len(medium_risk)}")
print(f"Risque faible : {len(low_risk)}")
if high_risk:
print(f"\n--- Domaines à risque élevé ---")
for entry in high_risk[:10]:
print(f" {entry['domain']} (score : {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"})
Étape 3 : Pipeline de surveillance continue
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):
"""Analyse tous les domaines surveillés pour détecter de nouveaux 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" [NOUVEAU] {domain_key} ({entry.get('fuzzer', '')})")
self._save_known()
print(f"\n[+] Nouveaux domaines typosquatting trouvés : {len(new_findings)}")
return new_findings
def generate_alert(self, findings):
"""Génère une alerte pour les nouveaux domaines typosquatting à haut risque."""
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(["mycompany.com", "mycompany.org"])
new_findings = monitor.scan_all_domains()
alerts = monitor.generate_alert(new_findings)
Étape 4 : Export pour liste de blocage et demande de retrait
def export_blocklist(analysis, output_file="blocklist.txt"):
"""Exporte les domaines à haut risque comme liste de blocage pour le pare-feu/le 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"# Liste de blocage typosquatting générée le {datetime.now().isoformat()}\n")
for d in sorted(set(domains)):
f.write(f"{d}\n")
print(f"[+] Liste de blocage enregistrée : {len(domains)} domaines -> {output_file}")
return domains
def generate_takedown_report(high_risk_domains):
"""Génère un rapport de demande de retrait."""
report = f"""# Demande de retrait de domaine
Généré le : {datetime.now().isoformat()}
## Résumé
{len(high_risk_domains)} domaines identifiés comme potentiels typosquatting/phishing.
## Domaines nécessitant un retrait
"""
for entry in high_risk_domains:
report += f"""
### {entry['domain']}
- **Type de permutation** : {entry.get('fuzzer', 'inconnu')}
- **Adresse IP** : {', '.join(entry.get('dns_a', ['N/A']))}
- **Enregistrements MX** : {', '.join(entry.get('dns_mx', ['N/A']))}
- **Score de risque** : {entry.get('risk_score', 0)}
- **Facteurs de risque** : {'; '.join(entry.get('risk_factors', []))}
- **Similarité web** : {entry.get('ssdeep_score', 'N/A')}%
"""
with open("takedown_report.md", "w") as f:
f.write(report)
print("[+] Rapport de demande de retrait généré : takedown_report.md")
export_blocklist(analysis)
generate_takedown_report(analysis["high"])
Critères de validation
- DNSTwist génère des permutations de domaines pour le domaine cible
- La résolution DNS identifie les domaines similaires enregistrés
- Le score de similarité web détecte les pages de phishing clonées
- Le score de risque priorise les domaines par niveau de menace
- La surveillance continue détecte les typosquats nouvellement enregistrés
- La liste de blocage et les rapports de demande de retrait sont générés correctement