| name | analyzing-typosquatting-domains-with-dnstwist |
| description | Detect typosquatting, homograph phishing, and brand impersonation domains using dnstwist to generate domain permutations and identify registered lookalike domains targeting your organization. |
| domain | cybersecurity |
| subdomain | threat-intelligence |
| tags | ["dnstwist","typosquatting","phishing","domain-monitoring","brand-protection","homograph","dns","threat-intelligence"] |
| version | 1.0 |
| author | mahipal |
| license | Apache-2.0 |
Analyzing Typosquatting Domains with DNSTwist
Overview
DNSTwist is a domain name permutation engine that generates similar-looking domain names to detect typosquatting, homograph phishing attacks, and brand impersonation. It creates thousands of domain permutations using techniques like character substitution, transposition, insertion, omission, and homoglyph replacement, then checks DNS records (A, AAAA, NS, MX), calculates web page similarity using fuzzy hashing (ssdeep) and perceptual hashing (pHash), and identifies potentially malicious registered domains.
Prerequisites
- Python 3.9+ with
dnstwist installed (pip install dnstwist[full])
- Optional: GeoIP database for IP geolocation
- Optional: Shodan API key for enrichment
- Network access to perform DNS queries
- Understanding of DNS record types and domain registration
Key Concepts
Domain Permutation Techniques
DNSTwist generates permutations using: addition (appending characters), bitsquatting (bit-flip errors), homoglyph (visually similar Unicode characters like rn vs m), hyphenation (adding hyphens), insertion (inserting characters), omission (removing characters), repetition (repeating characters), replacement (replacing with adjacent keyboard keys), subdomain (inserting dots), transposition (swapping adjacent characters), vowel-swap (swapping vowels), and dictionary-based (appending common words).
Fuzzy Hashing and Visual Similarity
DNSTwist uses ssdeep (locality-sensitive hash) to compare HTML content and pHash (perceptual hash) to compare screenshots of web pages. This helps identify cloned phishing sites that visually mimic the legitimate site. A high similarity score indicates a likely phishing page.
Detection Workflow
The typical workflow is: generate domain permutations -> resolve DNS records -> check for registered domains -> compare web page similarity -> flag suspicious domains -> alert security team -> request takedown. For a typical corporate domain, dnstwist generates 5,000-10,000 permutations.
Practical Steps
Step 1: Basic Domain Permutation Scan
import subprocess
import json
import csv
from datetime import datetime
def run_dnstwist_scan(domain, output_file=None):
"""Run dnstwist scan against a target domain."""
cmd = [
"dnstwist",
"--registered",
"--format", "json",
"--nameservers", "8.8.8.8,1.1.1.1",
"--threads", "50",
"--mxcheck",
"--ssdeep",
"--geoip",
domain,
]
print(f"[*] Scanning permutations for: {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"[+] Found {len(registered)} registered lookalike domains")
if output_file:
with open(output_file, ) f:
json.dump(registered, f, indent=)
()
registered
:
()
[]
results = run_dnstwist_scan(, )
Step 2: Analyze and Prioritize Results
def analyze_results(results, legitimate_ips=None):
"""Analyze dnstwist results and prioritize threats."""
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 = []
if ssdeep_score and ssdeep_score > 50:
risk_score += 40
risk_factors.append(f"high web similarity ({ssdeep_score}%)")
if dns_mx:
risk_score += 20
risk_factors.append("has MX records (email capable)")
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()
age_days < :
risk_score +=
risk_factors.append()
(ValueError, TypeError):
fuzzer == :
risk_score +=
risk_factors.append()
fuzzer (, , ):
risk_score +=
risk_factors.append()
dns_a (dns_a).intersection(legitimate_ips):
risk_score +=
risk_factors.append()
entry[] = risk_score
entry[] = risk_factors
risk_score >= :
high_risk.append(entry)
risk_score >= :
medium_risk.append(entry)
:
low_risk.append(entry)
high_risk.sort(key= x: x[], reverse=)
medium_risk.sort(key= x: x[], reverse=)
()
()
()
()
high_risk:
()
entry high_risk[:]:
()
factor entry[]:
()
{: high_risk, : medium_risk, : low_risk}
analysis = analyze_results(results, legitimate_ips={})
Step 3: Continuous Monitoring Pipeline
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):
"""Scan all monitored domains for new 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 .known_domains:
entry[] = datetime.now().isoformat()
entry[] = domain
.known_domains[domain_key] = entry
new_findings.append(entry)
()
._save_known()
()
new_findings
():
analysis = analyze_results(findings)
alerts = []
entry analysis[]:
alerts.append({
: ,
: entry[],
: entry.get(, ),
: entry[],
: entry[],
: entry.get(, []),
: entry.get(, []),
: datetime.now().isoformat(),
})
alerts
monitor = TyposquatMonitor([, ])
new_findings = monitor.scan_all_domains()
alerts = monitor.generate_alert(new_findings)
Step 4: Export for Blocklist and Takedown
def export_blocklist(analysis, output_file="blocklist.txt"):
"""Export high-risk domains as blocklist for 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"# Typosquatting blocklist generated {datetime.now().isoformat()}\n")
for d in sorted(set(domains)):
f.write(f"{d}\n")
print(f"[+] Blocklist saved: {len(domains)} domains -> {output_file}")
return domains
def generate_takedown_report(high_risk_domains):
"""Generate takedown request report."""
report = f"""# Domain Takedown Request
Generated: {datetime.now().isoformat()}
## Summary
{len(high_risk_domains)} domains identified as potential typosquatting/phishing.
## Domains Requiring Takedown
"""
for entry in high_risk_domains:
report += f"""
### {entry['domain']}
- **Permutation Type**: {entry.get('fuzzer', 'unknown')}
- **IP Address**:
- **MX Records**:
- **Risk Score**:
- **Risk Factors**:
- **Web Similarity**: %
"""
(, ) f:
f.write(report)
()
export_blocklist(analysis)
generate_takedown_report(analysis[])
Validation Criteria
- DNSTwist generates domain permutations for target domain
- DNS resolution identifies registered lookalike domains
- Web similarity scoring detects cloned phishing pages
- Risk scoring prioritizes domains by threat level
- Continuous monitoring detects newly registered typosquats
- Blocklist and takedown reports generated correctly
References