Files
Anthropic-Cybersecurity-Skills/skills/hunting-for-dns-based-persistence/scripts/agent.py
T
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
2026-03-19 13:26:49 +01:00

163 lines
6.6 KiB
Python

#!/usr/bin/env python3
"""DNS Persistence Hunting Agent - detects DNS hijacking, dangling CNAMEs, and unauthorized zone changes."""
import json
import argparse
import logging
import requests
import subprocess
from datetime import datetime
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
DANGLING_CNAME_SERVICES = [
"s3.amazonaws.com", "cloudfront.net", "azurewebsites.net", "herokuapp.com",
"github.io", "pantheonsite.io", "readme.io", "surge.sh",
"unbouncepages.com", "wordpress.com", "shopify.com",
]
def query_securitytrails(domain, api_key):
"""Query SecurityTrails API for passive DNS history."""
headers = {"APIKEY": api_key, "Content-Type": "application/json"}
url = f"https://api.securitytrails.com/v1/history/{domain}/dns/a"
resp = requests.get(url, headers=headers, timeout=30)
resp.raise_for_status()
return resp.json()
def get_subdomains(domain, api_key):
"""Enumerate subdomains via SecurityTrails API."""
headers = {"APIKEY": api_key}
url = f"https://api.securitytrails.com/v1/domain/{domain}/subdomains"
resp = requests.get(url, headers=headers, timeout=30)
resp.raise_for_status()
data = resp.json()
return [f"{sub}.{domain}" for sub in data.get("subdomains", [])]
def resolve_record(hostname, record_type="A"):
"""Resolve DNS record using dig command."""
cmd = ["dig", "+short", record_type, hostname]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
return [line.strip() for line in result.stdout.strip().split("\n") if line.strip()]
def detect_dangling_cnames(subdomains):
"""Detect CNAME records pointing to decommissioned services (subdomain takeover risk)."""
findings = []
for subdomain in subdomains:
cname_records = resolve_record(subdomain, "CNAME")
for cname in cname_records:
for service in DANGLING_CNAME_SERVICES:
if cname.rstrip(".").endswith(service):
a_records = resolve_record(cname, "A")
if not a_records:
findings.append({
"subdomain": subdomain, "cname": cname.rstrip("."),
"service": service, "resolves": False,
"severity": "critical",
"issue": "Dangling CNAME - subdomain takeover possible",
"mitre": "T1584.001",
})
return findings
def detect_wildcard_abuse(domain):
"""Check for wildcard DNS records that resolve all subdomains."""
findings = []
random_sub = f"randomtestxyz123.{domain}"
results = resolve_record(random_sub, "A")
if results:
findings.append({
"domain": domain, "wildcard_target": results,
"severity": "high",
"issue": "Wildcard DNS record detected - all subdomains resolve",
"detail": f"Random subdomain {random_sub} resolves to {results}",
})
return findings
def check_ns_delegation(domain):
"""Verify NS records haven't been modified to point to unauthorized servers."""
findings = []
ns_records = resolve_record(domain, "NS")
known_providers = ["awsdns", "azure-dns", "cloudflare", "google", "route53", "ns1."]
for ns in ns_records:
ns_lower = ns.lower().rstrip(".")
is_known = any(provider in ns_lower for provider in known_providers)
if not is_known:
findings.append({
"domain": domain, "nameserver": ns_lower,
"severity": "high",
"issue": "NS record points to unrecognized nameserver",
"recommendation": "Verify this is an authorized nameserver for the domain",
})
return findings
def analyze_dns_history(domain, api_key):
"""Analyze historical DNS changes for signs of hijacking."""
findings = []
try:
history = query_securitytrails(domain, api_key)
records = history.get("records", [])
if len(records) >= 2:
for i in range(1, len(records)):
prev_ips = set(v.get("ip", "") for v in records[i - 1].get("values", []))
curr_ips = set(v.get("ip", "") for v in records[i].get("values", []))
changed = curr_ips - prev_ips
if changed:
findings.append({
"domain": domain,
"first_seen": records[i].get("first_seen", ""),
"new_ips": list(changed),
"severity": "medium",
"issue": f"DNS A record changed: added {changed}",
})
except requests.RequestException as e:
logger.warning("SecurityTrails query failed: %s", e)
return findings
def generate_report(domain, dangling, wildcard, ns_findings, history_findings):
all_findings = dangling + wildcard + ns_findings + history_findings
critical = sum(1 for f in all_findings if f.get("severity") == "critical")
return {
"timestamp": datetime.utcnow().isoformat(),
"target_domain": domain,
"dangling_cnames": dangling,
"wildcard_abuse": wildcard,
"ns_delegation_issues": ns_findings,
"historical_anomalies": history_findings,
"total_findings": len(all_findings),
"critical_findings": critical,
"risk_level": "critical" if critical > 0 else "high" if all_findings else "low",
}
def main():
parser = argparse.ArgumentParser(description="DNS Persistence Hunting Agent")
parser.add_argument("--domain", required=True, help="Target domain to hunt")
parser.add_argument("--api-key", required=True, help="SecurityTrails API key")
parser.add_argument("--output", default="dns_persistence_report.json")
args = parser.parse_args()
subdomains = get_subdomains(args.domain, args.api_key)
logger.info("Discovered %d subdomains for %s", len(subdomains), args.domain)
dangling = detect_dangling_cnames(subdomains[:200])
wildcard = detect_wildcard_abuse(args.domain)
ns_findings = check_ns_delegation(args.domain)
history_findings = analyze_dns_history(args.domain, args.api_key)
report = generate_report(args.domain, dangling, wildcard, ns_findings, history_findings)
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
logger.info("DNS hunt: %s - %d findings (%d critical), %d subdomains checked",
args.domain, report["total_findings"], report["critical_findings"], len(subdomains))
print(json.dumps(report, indent=2, default=str))
if __name__ == "__main__":
main()