Some checks are pending
CI — CoM Config Validation / Validate JSON Configs (push) Waiting to run
CI — CoM Config Validation / Validate YAML Configs (push) Waiting to run
CI — CoM Config Validation / Lint Shell Scripts (push) Waiting to run
CI — CoM Config Validation / Secret Detection (push) Waiting to run
CI — CoM Config Validation / Lint Markdown (push) Waiting to run
CI — CoM Config Validation / Validate CODEOWNERS (push) Waiting to run
Public, sanitized mirror of an AI orchestration command center: agents, skills, MCP servers, slash-command workflows. All infrastructure identifiers, hostnames, mesh IPs/subnets, repo paths, maintainer identity, and hardware fleet specifics scrubbed to <placeholders>; session debug logs and host-specific memory removed. No live credentials. Verified clean by automated leak sweep. See SANITIZATION.md. churchofmalware.org . authorized research only
239 lines
9.0 KiB
Python
239 lines
9.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
DNS Reconnaissance Tool
|
|
Comprehensive DNS record analysis and misconfiguration detection.
|
|
|
|
Repository: https://github.com/Masriyan/Claude-Code-CyberSecurity-Skill
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import logging
|
|
import sys
|
|
import time
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
try:
|
|
import dns.resolver
|
|
import dns.query
|
|
import dns.zone
|
|
import dns.reversename
|
|
except ImportError:
|
|
print("[!] 'dnspython' module required: pip install dnspython")
|
|
sys.exit(1)
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
datefmt="%Y-%m-%d %H:%M:%S",
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class DNSRecon:
|
|
"""Comprehensive DNS reconnaissance engine."""
|
|
|
|
RECORD_TYPES = ["A", "AAAA", "MX", "NS", "TXT", "SOA", "SRV", "CNAME", "CAA", "PTR"]
|
|
|
|
def __init__(self, domain: str, nameserver: Optional[str] = None, timeout: int = 5):
|
|
self.domain = domain.lower().strip()
|
|
self.resolver = dns.resolver.Resolver()
|
|
self.resolver.timeout = timeout
|
|
self.resolver.lifetime = timeout
|
|
if nameserver:
|
|
self.resolver.nameservers = [nameserver]
|
|
self.results: Dict[str, Any] = {"domain": self.domain, "records": {}}
|
|
|
|
def enumerate_records(self) -> Dict[str, List[str]]:
|
|
"""Enumerate all DNS record types."""
|
|
logger.info("[DNS] Enumerating records for %s", self.domain)
|
|
records = {}
|
|
for rtype in self.RECORD_TYPES:
|
|
try:
|
|
answers = self.resolver.resolve(self.domain, rtype)
|
|
records[rtype] = [str(r) for r in answers]
|
|
logger.info("[DNS] %s records: %d found", rtype, len(records[rtype]))
|
|
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers):
|
|
records[rtype] = []
|
|
except Exception as e:
|
|
logger.debug("[DNS] %s lookup error: %s", rtype, str(e))
|
|
records[rtype] = []
|
|
self.results["records"] = records
|
|
return records
|
|
|
|
def check_zone_transfer(self) -> Dict[str, Any]:
|
|
"""Attempt AXFR zone transfer on all nameservers."""
|
|
logger.info("[AXFR] Testing zone transfer for %s", self.domain)
|
|
axfr_results = {"vulnerable": False, "nameservers": {}}
|
|
try:
|
|
ns_answers = self.resolver.resolve(self.domain, "NS")
|
|
for ns in ns_answers:
|
|
ns_host = str(ns).rstrip(".")
|
|
try:
|
|
zone = dns.zone.from_xfr(
|
|
dns.query.xfr(ns_host, self.domain, timeout=5)
|
|
)
|
|
zone_records = []
|
|
for name, node in zone.nodes.items():
|
|
zone_records.append(str(name))
|
|
axfr_results["vulnerable"] = True
|
|
axfr_results["nameservers"][ns_host] = {
|
|
"status": "VULNERABLE",
|
|
"records_count": len(zone_records),
|
|
"records": zone_records[:50],
|
|
}
|
|
logger.warning("[AXFR] Zone transfer SUCCESSFUL on %s!", ns_host)
|
|
except Exception:
|
|
axfr_results["nameservers"][ns_host] = {"status": "REFUSED"}
|
|
logger.info("[AXFR] Zone transfer refused on %s", ns_host)
|
|
except Exception as e:
|
|
logger.error("[AXFR] Error: %s", str(e))
|
|
|
|
self.results["zone_transfer"] = axfr_results
|
|
return axfr_results
|
|
|
|
def analyze_email_security(self) -> Dict[str, Any]:
|
|
"""Analyze SPF, DKIM, and DMARC records."""
|
|
logger.info("[Email] Analyzing email security for %s", self.domain)
|
|
email_security = {"spf": None, "dmarc": None, "dkim_selector_test": None}
|
|
|
|
# SPF
|
|
try:
|
|
txt_records = self.resolver.resolve(self.domain, "TXT")
|
|
for record in txt_records:
|
|
text = str(record).strip('"')
|
|
if text.startswith("v=spf1"):
|
|
email_security["spf"] = {
|
|
"record": text,
|
|
"mechanisms": self._parse_spf(text),
|
|
}
|
|
break
|
|
except Exception:
|
|
pass
|
|
|
|
# DMARC
|
|
try:
|
|
dmarc_domain = f"_dmarc.{self.domain}"
|
|
dmarc_records = self.resolver.resolve(dmarc_domain, "TXT")
|
|
for record in dmarc_records:
|
|
text = str(record).strip('"')
|
|
if text.startswith("v=DMARC1"):
|
|
email_security["dmarc"] = {
|
|
"record": text,
|
|
"policy": self._parse_dmarc(text),
|
|
}
|
|
break
|
|
except Exception:
|
|
pass
|
|
|
|
# DKIM (common selectors)
|
|
selectors = ["default", "google", "selector1", "selector2", "mail", "dkim"]
|
|
for selector in selectors:
|
|
try:
|
|
dkim_domain = f"{selector}._domainkey.{self.domain}"
|
|
dkim_records = self.resolver.resolve(dkim_domain, "TXT")
|
|
email_security["dkim_selector_test"] = {
|
|
"selector": selector,
|
|
"found": True,
|
|
"record": str(list(dkim_records)[0]).strip('"')[:200],
|
|
}
|
|
break
|
|
except Exception:
|
|
continue
|
|
|
|
# Security assessment
|
|
issues = []
|
|
if not email_security["spf"]:
|
|
issues.append("No SPF record found — domain vulnerable to email spoofing")
|
|
if not email_security["dmarc"]:
|
|
issues.append("No DMARC record found — no email authentication policy")
|
|
elif email_security["dmarc"]["policy"].get("p") == "none":
|
|
issues.append("DMARC policy set to 'none' — no enforcement")
|
|
|
|
email_security["issues"] = issues
|
|
self.results["email_security"] = email_security
|
|
return email_security
|
|
|
|
def _parse_spf(self, record: str) -> Dict[str, Any]:
|
|
"""Parse SPF record into components."""
|
|
parts = record.split()
|
|
return {
|
|
"version": parts[0] if parts else "",
|
|
"mechanisms": [p for p in parts[1:] if not p.startswith("~") and not p.startswith("-")],
|
|
"all_policy": parts[-1] if parts and parts[-1] in ["+all", "-all", "~all", "?all"] else "unknown",
|
|
}
|
|
|
|
def _parse_dmarc(self, record: str) -> Dict[str, str]:
|
|
"""Parse DMARC record into components."""
|
|
policy = {}
|
|
for part in record.split(";"):
|
|
part = part.strip()
|
|
if "=" in part:
|
|
key, value = part.split("=", 1)
|
|
policy[key.strip()] = value.strip()
|
|
return policy
|
|
|
|
def reverse_dns(self, ip_addresses: List[str]) -> Dict[str, str]:
|
|
"""Perform reverse DNS lookups."""
|
|
logger.info("[rDNS] Performing reverse lookups on %d IPs", len(ip_addresses))
|
|
rdns_results = {}
|
|
for ip in ip_addresses:
|
|
try:
|
|
rev_name = dns.reversename.from_address(ip)
|
|
answers = self.resolver.resolve(rev_name, "PTR")
|
|
rdns_results[ip] = str(list(answers)[0]).rstrip(".")
|
|
except Exception:
|
|
rdns_results[ip] = "No PTR record"
|
|
self.results["reverse_dns"] = rdns_results
|
|
return rdns_results
|
|
|
|
def run(self, check_axfr: bool = True) -> Dict[str, Any]:
|
|
"""Execute full DNS reconnaissance."""
|
|
logger.info("=" * 60)
|
|
logger.info("DNS Reconnaissance: %s", self.domain)
|
|
logger.info("=" * 60)
|
|
|
|
self.enumerate_records()
|
|
if check_axfr:
|
|
self.check_zone_transfer()
|
|
self.analyze_email_security()
|
|
|
|
# Reverse DNS on discovered A records
|
|
a_records = self.results["records"].get("A", [])
|
|
if a_records:
|
|
self.reverse_dns(a_records)
|
|
|
|
self.results["timestamp"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
return self.results
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="DNS Reconnaissance Tool",
|
|
epilog="https://github.com/Masriyan/Claude-Code-CyberSecurity-Skill",
|
|
)
|
|
parser.add_argument("--domain", "-d", required=True, help="Target domain")
|
|
parser.add_argument("--output", "-o", help="Output file (JSON)")
|
|
parser.add_argument("--nameserver", "-n", help="Custom nameserver")
|
|
parser.add_argument("--timeout", type=int, default=5, help="DNS timeout (default: 5)")
|
|
parser.add_argument("--check-zone-transfer", action="store_true", default=True, help="Test zone transfers")
|
|
parser.add_argument("--verbose", "-v", action="store_true")
|
|
args = parser.parse_args()
|
|
|
|
if args.verbose:
|
|
logging.getLogger().setLevel(logging.DEBUG)
|
|
|
|
recon = DNSRecon(domain=args.domain, nameserver=args.nameserver, timeout=args.timeout)
|
|
results = recon.run(check_axfr=args.check_zone_transfer)
|
|
|
|
if args.output:
|
|
with open(args.output, "w") as f:
|
|
json.dump(results, f, indent=2)
|
|
logger.info("Results saved to %s", args.output)
|
|
else:
|
|
print(json.dumps(results, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|