finalize
This commit is contained in:
parent
5d0e095a81
commit
88017edbeb
575
dnsrecon.py
575
dnsrecon.py
@ -66,9 +66,9 @@ class DNSReconTool:
|
|||||||
|
|
||||||
def get_dns_records(self, domain: str, record_type: str,
|
def get_dns_records(self, domain: str, record_type: str,
|
||||||
server: Optional[str] = None) -> Dict[str, Any]:
|
server: Optional[str] = None) -> Dict[str, Any]:
|
||||||
"""Fetch DNS records with comprehensive error handling."""
|
"""Fetch DNS records with comprehensive error handling and proper parsing."""
|
||||||
server_flag = f"@{server}" if server else ""
|
server_flag = f"@{server}" if server else ""
|
||||||
cmd = f"dig {domain} {record_type} {server_flag} +noall +answer +nottlid"
|
cmd = f"dig {domain} {record_type} {server_flag} +noall +answer"
|
||||||
|
|
||||||
output = self.run_command(cmd)
|
output = self.run_command(cmd)
|
||||||
|
|
||||||
@ -78,24 +78,35 @@ class DNSReconTool:
|
|||||||
for line in output.split('\n'):
|
for line in output.split('\n'):
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if line and not line.startswith(';') and not line.startswith('>>'):
|
if line and not line.startswith(';') and not line.startswith('>>'):
|
||||||
# Split on whitespace, but preserve quoted strings
|
# Split on any whitespace (handles both tabs and spaces)
|
||||||
parts = line.split(None, 4)
|
parts = line.split()
|
||||||
if len(parts) >= 5: # name, ttl, class, type, data
|
|
||||||
records.append({
|
if len(parts) >= 4:
|
||||||
'name': parts[0].rstrip('.'), # Remove trailing dot
|
name = parts[0].rstrip('.')
|
||||||
'ttl': parts[1],
|
|
||||||
'class': parts[2],
|
# Check if second field is numeric (TTL)
|
||||||
'type': parts[3],
|
if len(parts) >= 5 and parts[1].isdigit():
|
||||||
'data': parts[4]
|
# Format: name TTL class type data
|
||||||
})
|
ttl = parts[1]
|
||||||
elif len(parts) == 4: # Sometimes no data field
|
dns_class = parts[2]
|
||||||
records.append({
|
dns_type = parts[3]
|
||||||
'name': parts[0].rstrip('.'),
|
data = ' '.join(parts[4:])
|
||||||
'ttl': parts[1],
|
else:
|
||||||
'class': parts[2],
|
# Format: name class type data (no TTL shown)
|
||||||
'type': parts[3],
|
ttl = ''
|
||||||
'data': ''
|
dns_class = parts[1]
|
||||||
})
|
dns_type = parts[2]
|
||||||
|
data = ' '.join(parts[3:]) if len(parts) > 3 else ''
|
||||||
|
|
||||||
|
# Validate that we have the expected record type
|
||||||
|
if dns_type.upper() == record_type.upper():
|
||||||
|
records.append({
|
||||||
|
'name': name,
|
||||||
|
'ttl': ttl,
|
||||||
|
'class': dns_class,
|
||||||
|
'type': dns_type,
|
||||||
|
'data': data
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'query': f"{domain} {record_type}",
|
'query': f"{domain} {record_type}",
|
||||||
@ -124,15 +135,23 @@ class DNSReconTool:
|
|||||||
dns_results = {}
|
dns_results = {}
|
||||||
|
|
||||||
for record_type in record_types:
|
for record_type in record_types:
|
||||||
|
print(f" Querying {record_type} records...")
|
||||||
dns_results[record_type] = {}
|
dns_results[record_type] = {}
|
||||||
for server in dns_servers:
|
for server in dns_servers:
|
||||||
server_name = server or 'system'
|
server_name = server or 'system'
|
||||||
dns_results[record_type][server_name] = self.get_dns_records(
|
result = self.get_dns_records(domain, record_type, server)
|
||||||
domain, record_type, server
|
dns_results[record_type][server_name] = result
|
||||||
)
|
|
||||||
|
# Debug output for troubleshooting
|
||||||
|
if result['records']:
|
||||||
|
print(f" {server_name}: Found {len(result['records'])} {record_type} records")
|
||||||
|
elif result['raw_output'].startswith('Error:'):
|
||||||
|
print(f" {server_name}: {result['raw_output']}")
|
||||||
|
|
||||||
time.sleep(0.1) # Rate limiting
|
time.sleep(0.1) # Rate limiting
|
||||||
|
|
||||||
# Try DNSSEC validation
|
# Try DNSSEC validation
|
||||||
|
print(" Querying DNSSEC information...")
|
||||||
dnssec_cmd = f"dig {domain} +dnssec +noall +answer"
|
dnssec_cmd = f"dig {domain} +dnssec +noall +answer"
|
||||||
dns_results['DNSSEC'] = {
|
dns_results['DNSSEC'] = {
|
||||||
'system': {
|
'system': {
|
||||||
@ -146,7 +165,7 @@ class DNSReconTool:
|
|||||||
return dns_results
|
return dns_results
|
||||||
|
|
||||||
def get_whois_data(self, domain: str) -> Dict[str, Any]:
|
def get_whois_data(self, domain: str) -> Dict[str, Any]:
|
||||||
"""Fetch and parse WHOIS data."""
|
"""Fetch and parse WHOIS data with improved parsing."""
|
||||||
print("📋 Fetching WHOIS data...")
|
print("📋 Fetching WHOIS data...")
|
||||||
|
|
||||||
raw_whois = self.run_command(f"whois {domain}")
|
raw_whois = self.run_command(f"whois {domain}")
|
||||||
@ -161,12 +180,26 @@ class DNSReconTool:
|
|||||||
lines = raw_whois.split('\n')
|
lines = raw_whois.split('\n')
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if ':' in line and not line.startswith('%') and not line.startswith('#'):
|
if ':' in line and not line.startswith('%') and not line.startswith('#') and not line.startswith('>>>'):
|
||||||
key, value = line.split(':', 1)
|
# Handle different WHOIS formats
|
||||||
key = key.strip().lower().replace(' ', '_')
|
if line.count(':') == 1:
|
||||||
|
key, value = line.split(':', 1)
|
||||||
|
else:
|
||||||
|
# Multiple colons - take first as key, rest as value
|
||||||
|
parts = line.split(':', 2)
|
||||||
|
key, value = parts[0], ':'.join(parts[1:])
|
||||||
|
|
||||||
|
key = key.strip().lower().replace(' ', '_').replace('-', '_')
|
||||||
value = value.strip()
|
value = value.strip()
|
||||||
if value:
|
if value and key:
|
||||||
whois_data['parsed'][key] = value
|
# Handle multiple values for same key (like name servers)
|
||||||
|
if key in whois_data['parsed']:
|
||||||
|
# Convert to list if not already
|
||||||
|
if not isinstance(whois_data['parsed'][key], list):
|
||||||
|
whois_data['parsed'][key] = [whois_data['parsed'][key]]
|
||||||
|
whois_data['parsed'][key].append(value)
|
||||||
|
else:
|
||||||
|
whois_data['parsed'][key] = value
|
||||||
|
|
||||||
return whois_data
|
return whois_data
|
||||||
|
|
||||||
@ -280,33 +313,52 @@ class DNSReconTool:
|
|||||||
def _write_dns_server_comparison(self, f, dns_data: Dict):
|
def _write_dns_server_comparison(self, f, dns_data: Dict):
|
||||||
"""Compare responses from different DNS servers."""
|
"""Compare responses from different DNS servers."""
|
||||||
servers = ['system', '1.1.1.1', '8.8.8.8', '9.9.9.9']
|
servers = ['system', '1.1.1.1', '8.8.8.8', '9.9.9.9']
|
||||||
record_types = ['A', 'AAAA', 'MX', 'NS']
|
record_types = ['A', 'AAAA', 'MX', 'NS', 'TXT']
|
||||||
|
|
||||||
discrepancies_found = False
|
discrepancies_found = False
|
||||||
|
|
||||||
for record_type in record_types:
|
for record_type in record_types:
|
||||||
if record_type in dns_data:
|
if record_type in dns_data:
|
||||||
|
f.write(f"\n{record_type} Records:\n")
|
||||||
server_results = {}
|
server_results = {}
|
||||||
|
errors = {}
|
||||||
|
|
||||||
for server in servers:
|
for server in servers:
|
||||||
if server in dns_data[record_type]:
|
if server in dns_data[record_type]:
|
||||||
records = dns_data[record_type][server].get('records', [])
|
server_data = dns_data[record_type][server]
|
||||||
server_results[server] = set(r.get('data', '') for r in records if r.get('data'))
|
records = server_data.get('records', [])
|
||||||
|
raw_output = server_data.get('raw_output', '')
|
||||||
|
|
||||||
|
if raw_output.startswith('Error:'):
|
||||||
|
errors[server] = raw_output
|
||||||
|
server_results[server] = set()
|
||||||
|
else:
|
||||||
|
server_results[server] = set(r.get('data', '') for r in records if r.get('data'))
|
||||||
|
else:
|
||||||
|
server_results[server] = set()
|
||||||
|
|
||||||
|
# Show results for each server
|
||||||
|
for server in servers:
|
||||||
|
records = server_results.get(server, set())
|
||||||
|
if server in errors:
|
||||||
|
f.write(f" {server:<12}: {errors[server]}\n")
|
||||||
|
elif records:
|
||||||
|
f.write(f" {server:<12}: {', '.join(sorted(records))}\n")
|
||||||
|
else:
|
||||||
|
f.write(f" {server:<12}: No records\n")
|
||||||
|
|
||||||
# Check for discrepancies
|
# Check for discrepancies
|
||||||
if len(server_results) > 1:
|
if len(server_results) > 1:
|
||||||
all_results = list(server_results.values())
|
unique_results = set(frozenset(result) for result in server_results.values())
|
||||||
if not all(result == all_results[0] for result in all_results):
|
if len(unique_results) > 1:
|
||||||
if not discrepancies_found:
|
f.write(f" ⚠️ INCONSISTENCY DETECTED between servers!\n")
|
||||||
discrepancies_found = True
|
discrepancies_found = True
|
||||||
f.write(f" ⚠️ {record_type} records differ between DNS servers:\n")
|
|
||||||
for server, records in server_results.items():
|
|
||||||
f.write(f" {server}: {', '.join(sorted(records)) if records else 'No records'}\n")
|
|
||||||
|
|
||||||
if not discrepancies_found:
|
if not discrepancies_found:
|
||||||
f.write(" ✅ All DNS servers return consistent results\n")
|
f.write(f"\n✅ All DNS servers return consistent results\n")
|
||||||
|
|
||||||
def create_summary_report(self, results: Dict[str, Any], filename: str) -> None:
|
def create_summary_report(self, results: Dict[str, Any], filename: str) -> None:
|
||||||
"""Create comprehensive human-readable summary report."""
|
"""Create comprehensive human-readable summary report including ALL collected data."""
|
||||||
with open(filename, 'w', encoding='utf-8') as f:
|
with open(filename, 'w', encoding='utf-8') as f:
|
||||||
f.write(f"DNS Reconnaissance Report\n")
|
f.write(f"DNS Reconnaissance Report\n")
|
||||||
f.write(f"{'='*50}\n")
|
f.write(f"{'='*50}\n")
|
||||||
@ -319,201 +371,438 @@ class DNSReconTool:
|
|||||||
def get_system_records(record_type):
|
def get_system_records(record_type):
|
||||||
return dns_data.get(record_type, {}).get('system', {}).get('records', [])
|
return dns_data.get(record_type, {}).get('system', {}).get('records', [])
|
||||||
|
|
||||||
# A Records (IPv4)
|
# Helper function to get all server records for a type
|
||||||
self._write_dns_section(f, "A Records (IPv4)", get_system_records('A'),
|
def get_all_server_records(record_type):
|
||||||
lambda r: r.get('data', 'N/A'))
|
servers = ['system', '1.1.1.1', '8.8.8.8', '9.9.9.9']
|
||||||
|
results = {}
|
||||||
|
for server in servers:
|
||||||
|
if record_type in dns_data and server in dns_data[record_type]:
|
||||||
|
server_data = dns_data[record_type][server]
|
||||||
|
results[server] = {
|
||||||
|
'records': server_data.get('records', []),
|
||||||
|
'raw_output': server_data.get('raw_output', ''),
|
||||||
|
'record_count': server_data.get('record_count', 0)
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
|
||||||
# AAAA Records (IPv6)
|
# A Records (IPv4) with TTL and all servers
|
||||||
self._write_dns_section(f, "AAAA Records (IPv6)", get_system_records('AAAA'),
|
f.write(f"\nA Records (IPv4):\n")
|
||||||
lambda r: r.get('data', 'N/A'))
|
f.write("-" * 16 + "\n")
|
||||||
|
a_servers = get_all_server_records('A')
|
||||||
|
if any(server_data['records'] for server_data in a_servers.values()):
|
||||||
|
for server, server_data in a_servers.items():
|
||||||
|
records = server_data['records']
|
||||||
|
if records:
|
||||||
|
f.write(f" {server}:\n")
|
||||||
|
for record in records:
|
||||||
|
ip = record.get('data', 'N/A')
|
||||||
|
ttl = record.get('ttl', 'N/A') if record.get('ttl') else 'Not shown'
|
||||||
|
f.write(f" {ip} (TTL: {ttl})\n")
|
||||||
|
elif server_data['raw_output'].startswith('Error:'):
|
||||||
|
f.write(f" {server}: {server_data['raw_output']}\n")
|
||||||
|
else:
|
||||||
|
f.write(f" {server}: No records found\n")
|
||||||
|
else:
|
||||||
|
f.write(" No A records found on any server\n")
|
||||||
|
|
||||||
# MX Records (Mail Servers)
|
# AAAA Records (IPv6) with TTL and all servers
|
||||||
|
f.write(f"\nAAAA Records (IPv6):\n")
|
||||||
|
f.write("-" * 17 + "\n")
|
||||||
|
aaaa_servers = get_all_server_records('AAAA')
|
||||||
|
if any(server_data['records'] for server_data in aaaa_servers.values()):
|
||||||
|
for server, server_data in aaaa_servers.items():
|
||||||
|
records = server_data['records']
|
||||||
|
if records:
|
||||||
|
f.write(f" {server}:\n")
|
||||||
|
for record in records:
|
||||||
|
ipv6 = record.get('data', 'N/A')
|
||||||
|
ttl = record.get('ttl', 'N/A') if record.get('ttl') else 'Not shown'
|
||||||
|
f.write(f" {ipv6} (TTL: {ttl})\n")
|
||||||
|
elif server_data['raw_output'].startswith('Error:'):
|
||||||
|
f.write(f" {server}: {server_data['raw_output']}\n")
|
||||||
|
else:
|
||||||
|
f.write(f" {server}: No records found\n")
|
||||||
|
else:
|
||||||
|
f.write(" No AAAA records found on any server\n")
|
||||||
|
|
||||||
|
# MX Records (Mail Servers) with TTL
|
||||||
mx_records = get_system_records('MX')
|
mx_records = get_system_records('MX')
|
||||||
|
f.write(f"\nMX Records (Mail Servers):\n")
|
||||||
|
f.write("-" * 26 + "\n")
|
||||||
if mx_records:
|
if mx_records:
|
||||||
f.write(f"\nMX Records (Mail Servers):\n")
|
|
||||||
for record in mx_records:
|
for record in mx_records:
|
||||||
data_parts = record.get('data', '').split()
|
data_parts = record.get('data', '').split()
|
||||||
priority = data_parts[0] if data_parts else 'N/A'
|
priority = data_parts[0] if data_parts else 'N/A'
|
||||||
server = ' '.join(data_parts[1:]) if len(data_parts) > 1 else 'N/A'
|
server = ' '.join(data_parts[1:]) if len(data_parts) > 1 else 'N/A'
|
||||||
f.write(f" Priority {priority}: {server}\n")
|
ttl = record.get('ttl', 'N/A') if record.get('ttl') else 'Not shown'
|
||||||
|
f.write(f" Priority {priority}: {server} (TTL: {ttl})\n")
|
||||||
else:
|
else:
|
||||||
f.write(f"\nMX Records (Mail Servers): None found\n")
|
f.write(" No MX records found\n")
|
||||||
|
|
||||||
# NS Records (Name Servers)
|
# NS Records (Name Servers) with TTL
|
||||||
self._write_dns_section(f, "NS Records (Name Servers)", get_system_records('NS'),
|
ns_records = get_system_records('NS')
|
||||||
lambda r: r.get('data', 'N/A'))
|
f.write(f"\nNS Records (Name Servers):\n")
|
||||||
|
f.write("-" * 26 + "\n")
|
||||||
|
if ns_records:
|
||||||
|
for record in ns_records:
|
||||||
|
ns = record.get('data', 'N/A')
|
||||||
|
ttl = record.get('ttl', 'N/A') if record.get('ttl') else 'Not shown'
|
||||||
|
f.write(f" {ns} (TTL: {ttl})\n")
|
||||||
|
else:
|
||||||
|
f.write(" No NS records found\n")
|
||||||
|
|
||||||
# CNAME Records
|
# CNAME Records with TTL
|
||||||
self._write_dns_section(f, "CNAME Records", get_system_records('CNAME'),
|
cname_records = get_system_records('CNAME')
|
||||||
lambda r: f"{r.get('name', 'N/A')} -> {r.get('data', 'N/A')}")
|
f.write(f"\nCNAME Records:\n")
|
||||||
|
f.write("-" * 14 + "\n")
|
||||||
|
if cname_records:
|
||||||
|
for record in cname_records:
|
||||||
|
name = record.get('name', 'N/A')
|
||||||
|
target = record.get('data', 'N/A')
|
||||||
|
ttl = record.get('ttl', 'N/A') if record.get('ttl') else 'Not shown'
|
||||||
|
f.write(f" {name} -> {target} (TTL: {ttl})\n")
|
||||||
|
else:
|
||||||
|
f.write(" No CNAME records found\n")
|
||||||
|
|
||||||
# TXT Records
|
# TXT Records with categorization and TTL
|
||||||
txt_records = get_system_records('TXT')
|
txt_records = get_system_records('TXT')
|
||||||
|
f.write(f"\nTXT Records:\n")
|
||||||
|
f.write("-" * 12 + "\n")
|
||||||
if txt_records:
|
if txt_records:
|
||||||
f.write(f"\nTXT Records:\n")
|
|
||||||
for record in txt_records:
|
for record in txt_records:
|
||||||
txt_data = record.get('data', '').strip()
|
txt_data = record.get('data', '').strip()
|
||||||
|
ttl = record.get('ttl', 'N/A') if record.get('ttl') else 'Not shown'
|
||||||
|
|
||||||
# Clean up quoted text
|
# Clean up quoted text
|
||||||
if txt_data.startswith('"') and txt_data.endswith('"'):
|
if txt_data.startswith('"') and txt_data.endswith('"'):
|
||||||
txt_data = txt_data[1:-1]
|
txt_data = txt_data[1:-1]
|
||||||
|
|
||||||
# Identify common TXT record types
|
# Identify common TXT record types
|
||||||
if txt_data.startswith('v=spf1'):
|
if txt_data.startswith('v=spf1'):
|
||||||
f.write(f" [SPF] {txt_data}\n")
|
f.write(f" [SPF] {txt_data} (TTL: {ttl})\n")
|
||||||
elif txt_data.startswith('v=DMARC1'):
|
elif txt_data.startswith('v=DMARC1'):
|
||||||
f.write(f" [DMARC] {txt_data}\n")
|
f.write(f" [DMARC] {txt_data} (TTL: {ttl})\n")
|
||||||
elif txt_data.startswith('v=DKIM1'):
|
elif txt_data.startswith('v=DKIM1'):
|
||||||
f.write(f" [DKIM] {txt_data}\n")
|
f.write(f" [DKIM] {txt_data} (TTL: {ttl})\n")
|
||||||
elif 'google-site-verification' in txt_data:
|
elif 'google-site-verification' in txt_data:
|
||||||
f.write(f" [Google Verification] {txt_data[:50]}...\n")
|
f.write(f" [Google Verification] {txt_data[:50]}... (TTL: {ttl})\n")
|
||||||
|
elif '_domainkey' in txt_data:
|
||||||
|
f.write(f" [Domain Key] {txt_data} (TTL: {ttl})\n")
|
||||||
|
elif 'facebook-domain-verification' in txt_data:
|
||||||
|
f.write(f" [Facebook Verification] {txt_data[:50]}... (TTL: {ttl})\n")
|
||||||
|
elif txt_data.startswith('MS='):
|
||||||
|
f.write(f" [Microsoft Verification] {txt_data} (TTL: {ttl})\n")
|
||||||
else:
|
else:
|
||||||
f.write(f" {txt_data}\n")
|
f.write(f" {txt_data} (TTL: {ttl})\n")
|
||||||
else:
|
else:
|
||||||
f.write(f"\nTXT Records: None found\n")
|
f.write(" No TXT records found\n")
|
||||||
|
|
||||||
# CAA Records (Certificate Authority Authorization)
|
# CAA Records (Certificate Authority Authorization) with TTL
|
||||||
caa_records = get_system_records('CAA')
|
caa_records = get_system_records('CAA')
|
||||||
|
f.write(f"\nCAA Records (Certificate Authority Authorization):\n")
|
||||||
|
f.write("-" * 48 + "\n")
|
||||||
if caa_records:
|
if caa_records:
|
||||||
f.write(f"\nCAA Records (Certificate Authority Authorization):\n")
|
|
||||||
for record in caa_records:
|
for record in caa_records:
|
||||||
data_parts = record.get('data', '').split()
|
data_parts = record.get('data', '').split()
|
||||||
|
ttl = record.get('ttl', 'N/A') if record.get('ttl') else 'Not shown'
|
||||||
if len(data_parts) >= 3:
|
if len(data_parts) >= 3:
|
||||||
flags = data_parts[0]
|
flags = data_parts[0]
|
||||||
tag = data_parts[1]
|
tag = data_parts[1]
|
||||||
value = ' '.join(data_parts[2:]).strip('"')
|
value = ' '.join(data_parts[2:]).strip('"')
|
||||||
f.write(f" {flags} {tag} {value}\n")
|
f.write(f" {flags} {tag} {value} (TTL: {ttl})\n")
|
||||||
else:
|
else:
|
||||||
f.write(f" {record.get('data', 'N/A')}\n")
|
f.write(f" {record.get('data', 'N/A')} (TTL: {ttl})\n")
|
||||||
else:
|
else:
|
||||||
f.write(f"\nCAA Records: None found\n")
|
f.write(" No CAA records found\n")
|
||||||
|
|
||||||
# SRV Records
|
# SRV Records with TTL
|
||||||
srv_records = get_system_records('SRV')
|
srv_records = get_system_records('SRV')
|
||||||
|
f.write(f"\nSRV Records (Service Records):\n")
|
||||||
|
f.write("-" * 30 + "\n")
|
||||||
if srv_records:
|
if srv_records:
|
||||||
f.write(f"\nSRV Records (Service Records):\n")
|
|
||||||
for record in srv_records:
|
for record in srv_records:
|
||||||
data_parts = record.get('data', '').split()
|
data_parts = record.get('data', '').split()
|
||||||
|
ttl = record.get('ttl', 'N/A') if record.get('ttl') else 'Not shown'
|
||||||
if len(data_parts) >= 4:
|
if len(data_parts) >= 4:
|
||||||
priority, weight, port, target = data_parts[:4]
|
priority, weight, port, target = data_parts[:4]
|
||||||
f.write(f" {record.get('name', 'N/A')}\n")
|
f.write(f" {record.get('name', 'N/A')}\n")
|
||||||
f.write(f" Priority: {priority}, Weight: {weight}\n")
|
f.write(f" Priority: {priority}, Weight: {weight}\n")
|
||||||
f.write(f" Port: {port}, Target: {target}\n")
|
f.write(f" Port: {port}, Target: {target}\n")
|
||||||
|
f.write(f" TTL: {ttl}\n")
|
||||||
else:
|
else:
|
||||||
f.write(f" {record.get('data', 'N/A')}\n")
|
f.write(f" {record.get('data', 'N/A')} (TTL: {ttl})\n")
|
||||||
else:
|
else:
|
||||||
f.write(f"\nSRV Records: None found\n")
|
f.write(" No SRV records found\n")
|
||||||
|
|
||||||
# SOA Record (Start of Authority)
|
# PTR Records (Reverse DNS) - MISSING FROM ORIGINAL
|
||||||
|
ptr_records = get_system_records('PTR')
|
||||||
|
f.write(f"\nPTR Records (Reverse DNS):\n")
|
||||||
|
f.write("-" * 26 + "\n")
|
||||||
|
if ptr_records:
|
||||||
|
for record in ptr_records:
|
||||||
|
ptr_data = record.get('data', 'N/A')
|
||||||
|
ttl = record.get('ttl', 'N/A') if record.get('ttl') else 'Not shown'
|
||||||
|
name = record.get('name', 'N/A')
|
||||||
|
f.write(f" {name} -> {ptr_data} (TTL: {ttl})\n")
|
||||||
|
else:
|
||||||
|
f.write(" No PTR records found\n")
|
||||||
|
|
||||||
|
# SOA Record (Start of Authority) with detailed parsing
|
||||||
soa_records = get_system_records('SOA')
|
soa_records = get_system_records('SOA')
|
||||||
|
f.write(f"\nSOA Record (Zone Authority):\n")
|
||||||
|
f.write("-" * 27 + "\n")
|
||||||
if soa_records:
|
if soa_records:
|
||||||
f.write(f"\nSOA Record (Zone Authority):\n")
|
|
||||||
for record in soa_records:
|
for record in soa_records:
|
||||||
data_parts = record.get('data', '').split()
|
data_parts = record.get('data', '').split()
|
||||||
|
ttl = record.get('ttl', 'N/A') if record.get('ttl') else 'Not shown'
|
||||||
if len(data_parts) >= 7:
|
if len(data_parts) >= 7:
|
||||||
primary_ns, admin_email = data_parts[:2]
|
primary_ns, admin_email = data_parts[:2]
|
||||||
serial, refresh, retry, expire, minimum = data_parts[2:7]
|
serial, refresh, retry, expire, minimum = data_parts[2:7]
|
||||||
f.write(f" Primary Name Server: {primary_ns}\n")
|
f.write(f" Primary Name Server: {primary_ns}\n")
|
||||||
f.write(f" Admin Email: {admin_email}\n")
|
f.write(f" Admin Email: {admin_email}\n")
|
||||||
f.write(f" Serial: {serial}\n")
|
f.write(f" Serial Number: {serial}\n")
|
||||||
f.write(f" Refresh: {refresh}s, Retry: {retry}s\n")
|
f.write(f" Refresh Interval: {refresh} seconds\n")
|
||||||
f.write(f" Expire: {expire}s, Minimum TTL: {minimum}s\n")
|
f.write(f" Retry Interval: {retry} seconds\n")
|
||||||
|
f.write(f" Expire Time: {expire} seconds\n")
|
||||||
|
f.write(f" Minimum TTL: {minimum} seconds\n")
|
||||||
|
f.write(f" Record TTL: {ttl}\n")
|
||||||
else:
|
else:
|
||||||
f.write(f" {record.get('data', 'N/A')}\n")
|
f.write(f" {record.get('data', 'N/A')} (TTL: {ttl})\n")
|
||||||
else:
|
else:
|
||||||
f.write(f"\nSOA Record: None found\n")
|
f.write(" No SOA record found\n")
|
||||||
|
|
||||||
# DNSSEC Information
|
# DNSSEC Information with detailed analysis
|
||||||
dnssec_data = dns_data.get('DNSSEC', {}).get('system', {})
|
dnssec_data = dns_data.get('DNSSEC', {}).get('system', {})
|
||||||
dnssec_output = dnssec_data.get('raw_output', '')
|
dnssec_output = dnssec_data.get('raw_output', '')
|
||||||
|
f.write(f"\nDNSSEC Status:\n")
|
||||||
|
f.write("-" * 14 + "\n")
|
||||||
if dnssec_output and not dnssec_output.startswith('Error:') and dnssec_output.strip():
|
if dnssec_output and not dnssec_output.startswith('Error:') and dnssec_output.strip():
|
||||||
f.write(f"\nDNSSEC Status:\n")
|
if 'RRSIG' in dnssec_output:
|
||||||
f.write("-" * 14 + "\n")
|
f.write(" ✅ DNSSEC is enabled (RRSIG records found)\n")
|
||||||
if 'RRSIG' in dnssec_output or 'DNSKEY' in dnssec_output:
|
elif 'DNSKEY' in dnssec_output:
|
||||||
f.write(f" ✅ DNSSEC is enabled\n")
|
f.write(" ✅ DNSSEC keys present (DNSKEY records found)\n")
|
||||||
|
elif 'NSEC' in dnssec_output or 'NSEC3' in dnssec_output:
|
||||||
|
f.write(" ✅ DNSSEC authenticated denial (NSEC/NSEC3 found)\n")
|
||||||
else:
|
else:
|
||||||
f.write(f" ❌ DNSSEC not detected\n")
|
f.write(" ❓ DNSSEC query returned data but no signatures detected\n")
|
||||||
else:
|
|
||||||
f.write(f"\nDNSSEC Status: Unable to determine\n")
|
|
||||||
|
|
||||||
# DNS Server Comparison (show discrepancies)
|
# Show sample DNSSEC records (first few lines)
|
||||||
f.write(f"\nDNS Server Comparison:\n")
|
dnssec_lines = [line.strip() for line in dnssec_output.split('\n') if line.strip()]
|
||||||
f.write("-" * 21 + "\n")
|
if dnssec_lines:
|
||||||
|
f.write(" Sample DNSSEC records:\n")
|
||||||
|
for line in dnssec_lines[:3]: # Show first 3 lines
|
||||||
|
f.write(f" {line}\n")
|
||||||
|
if len(dnssec_lines) > 3:
|
||||||
|
f.write(f" ... and {len(dnssec_lines) - 3} more\n")
|
||||||
|
else:
|
||||||
|
f.write(" ❌ DNSSEC not detected or query failed\n")
|
||||||
|
if dnssec_output.startswith('Error:'):
|
||||||
|
f.write(f" Error: {dnssec_output}\n")
|
||||||
|
|
||||||
|
# Complete DNS Server Comparison Table
|
||||||
|
f.write(f"\nComplete DNS Server Comparison:\n")
|
||||||
|
f.write("-" * 33 + "\n")
|
||||||
self._write_dns_server_comparison(f, dns_data)
|
self._write_dns_server_comparison(f, dns_data)
|
||||||
|
|
||||||
# Enhanced WHOIS Information
|
# Enhanced WHOIS Information with ALL parsed fields
|
||||||
whois_data = results.get('whois', {})
|
whois_data = results.get('whois', {})
|
||||||
|
f.write(f"\nWHOIS Information (Complete):\n")
|
||||||
|
f.write("-" * 29 + "\n")
|
||||||
if whois_data.get('parsed'):
|
if whois_data.get('parsed'):
|
||||||
f.write(f"\nWHOIS Information\n")
|
|
||||||
f.write("-" * 17 + "\n")
|
|
||||||
parsed = whois_data['parsed']
|
parsed = whois_data['parsed']
|
||||||
|
|
||||||
# Domain info
|
# Group fields by category for better organization
|
||||||
for field in ['domain_name', 'domain']:
|
domain_fields = ['domain_name', 'domain']
|
||||||
|
registrar_fields = ['registrar', 'sponsoring_registrar', 'registrar_whois_server', 'registrar_url']
|
||||||
|
date_fields = ['creation_date', 'created', 'expiration_date', 'registry_expiry_date', 'expires', 'updated_date', 'changed', 'last_updated']
|
||||||
|
status_fields = ['status', 'domain_status']
|
||||||
|
ns_fields = [k for k in parsed.keys() if 'name_server' in k.lower() or k.lower().startswith('nserver')]
|
||||||
|
contact_fields = [k for k in parsed.keys() if any(x in k.lower() for x in ['registrant', 'admin', 'tech', 'billing'])]
|
||||||
|
|
||||||
|
# Display organized sections
|
||||||
|
for field in domain_fields:
|
||||||
if field in parsed:
|
if field in parsed:
|
||||||
f.write(f"Domain: {parsed[field]}\n")
|
f.write(f" Domain: {parsed[field]}\n")
|
||||||
break
|
break
|
||||||
|
|
||||||
# Registrar info
|
for field in registrar_fields:
|
||||||
for field in ['registrar', 'sponsoring_registrar']:
|
|
||||||
if field in parsed:
|
if field in parsed:
|
||||||
f.write(f"Registrar: {parsed[field]}\n")
|
f.write(f" Registrar: {parsed[field]}\n")
|
||||||
break
|
break
|
||||||
|
|
||||||
# Important dates
|
# Show all date fields found
|
||||||
for field in ['creation_date', 'created']:
|
for field in date_fields:
|
||||||
if field in parsed:
|
if field in parsed:
|
||||||
f.write(f"Created: {parsed[field]}\n")
|
field_display = field.replace('_', ' ').title()
|
||||||
break
|
f.write(f" {field_display}: {parsed[field]}\n")
|
||||||
|
|
||||||
for field in ['expiration_date', 'registry_expiry_date', 'expires']:
|
for field in status_fields:
|
||||||
if field in parsed:
|
if field in parsed:
|
||||||
f.write(f"Expires: {parsed[field]}\n")
|
f.write(f" Status: {parsed[field]}\n")
|
||||||
break
|
|
||||||
|
|
||||||
for field in ['updated_date', 'changed', 'last_updated']:
|
|
||||||
if field in parsed:
|
|
||||||
f.write(f"Last Updated: {parsed[field]}\n")
|
|
||||||
break
|
|
||||||
|
|
||||||
# Status
|
|
||||||
for field in ['status', 'domain_status']:
|
|
||||||
if field in parsed:
|
|
||||||
f.write(f"Status: {parsed[field]}\n")
|
|
||||||
break
|
break
|
||||||
|
|
||||||
# Name servers from WHOIS
|
# Name servers from WHOIS
|
||||||
ns_fields = [k for k in parsed.keys() if 'name_server' in k.lower() or k.lower().startswith('nserver')]
|
|
||||||
if ns_fields:
|
if ns_fields:
|
||||||
f.write(f"WHOIS Name Servers:\n")
|
f.write(f" WHOIS Name Servers:\n")
|
||||||
for field in sorted(ns_fields)[:10]: # Limit to 10
|
for field in sorted(ns_fields):
|
||||||
f.write(f" {parsed[field]}\n")
|
value = parsed[field]
|
||||||
|
if isinstance(value, list):
|
||||||
|
for ns in value:
|
||||||
|
f.write(f" {ns}\n")
|
||||||
|
else:
|
||||||
|
f.write(f" {value}\n")
|
||||||
|
|
||||||
# Certificate transparency section
|
# Show any other significant fields not covered above
|
||||||
|
covered_fields = set(domain_fields + registrar_fields + date_fields + status_fields + ns_fields + contact_fields)
|
||||||
|
other_fields = [k for k in parsed.keys() if k not in covered_fields and not k.startswith('nserver')]
|
||||||
|
if other_fields:
|
||||||
|
f.write(f" Other WHOIS Data:\n")
|
||||||
|
for field in sorted(other_fields)[:10]: # Limit to prevent spam
|
||||||
|
field_display = field.replace('_', ' ').title()
|
||||||
|
value = parsed[field]
|
||||||
|
if isinstance(value, list):
|
||||||
|
value = ', '.join(value[:3]) # Show first 3 if list
|
||||||
|
value_display = value[:100] + '...' if len(str(value)) > 100 else str(value)
|
||||||
|
f.write(f" {field_display}: {value_display}\n")
|
||||||
|
|
||||||
|
raw_whois = whois_data.get('raw', '')
|
||||||
|
if raw_whois.startswith('Error:'):
|
||||||
|
f.write(f" WHOIS Error: {raw_whois}\n")
|
||||||
|
elif not whois_data.get('parsed'):
|
||||||
|
f.write(" No WHOIS data could be parsed\n")
|
||||||
|
|
||||||
|
# Certificate Transparency with comprehensive details
|
||||||
cert_data = results.get('certificate_transparency', {})
|
cert_data = results.get('certificate_transparency', {})
|
||||||
|
f.write(f"\nCertificate Transparency Logs:\n")
|
||||||
|
f.write("-" * 30 + "\n")
|
||||||
if cert_data.get('success'):
|
if cert_data.get('success'):
|
||||||
|
total_certs = cert_data.get('total_certificates', 0)
|
||||||
subdomain_count = cert_data.get('subdomain_count', 0)
|
subdomain_count = cert_data.get('subdomain_count', 0)
|
||||||
f.write(f"\nSubdomains from Certificate Logs ({subdomain_count} total):\n")
|
f.write(f" Total Certificates Found: {total_certs}\n")
|
||||||
f.write("-" * 45 + "\n")
|
f.write(f" Unique Subdomains Discovered: {subdomain_count}\n\n")
|
||||||
|
|
||||||
|
# Show certificate statistics by issuer
|
||||||
|
certificates = cert_data.get('certificates', [])
|
||||||
|
if certificates:
|
||||||
|
issuers = {}
|
||||||
|
for cert in certificates:
|
||||||
|
issuer = cert.get('issuer', 'Unknown')
|
||||||
|
issuers[issuer] = issuers.get(issuer, 0) + 1
|
||||||
|
|
||||||
|
f.write(" Certificate Issuers:\n")
|
||||||
|
for issuer, count in sorted(issuers.items(), key=lambda x: x[1], reverse=True):
|
||||||
|
f.write(f" {issuer}: {count} certificates\n")
|
||||||
|
f.write("\n")
|
||||||
|
|
||||||
|
# Show recent certificates with more details
|
||||||
|
if certificates:
|
||||||
|
f.write(" Recent SSL Certificates (detailed):\n")
|
||||||
|
for i, cert in enumerate(certificates[:10]): # Show top 10
|
||||||
|
f.write(f" Certificate #{i+1}:\n")
|
||||||
|
f.write(f" ID: {cert.get('id', 'N/A')}\n")
|
||||||
|
f.write(f" Common Name: {cert.get('common_name', 'N/A')}\n")
|
||||||
|
f.write(f" Issuer: {cert.get('issuer', 'N/A')}\n")
|
||||||
|
f.write(f" Valid From: {cert.get('not_before', 'N/A')}\n")
|
||||||
|
f.write(f" Valid Until: {cert.get('not_after', 'N/A')}\n")
|
||||||
|
f.write(f" Serial: {cert.get('serial_number', 'N/A')}\n")
|
||||||
|
|
||||||
|
# Show domains covered by this certificate
|
||||||
|
name_value = cert.get('name_value', '')
|
||||||
|
if name_value:
|
||||||
|
domains_in_cert = [d.strip() for d in name_value.split('\n')]
|
||||||
|
if len(domains_in_cert) > 1:
|
||||||
|
f.write(f" Covers {len(domains_in_cert)} domains: {', '.join(domains_in_cert[:5])}")
|
||||||
|
if len(domains_in_cert) > 5:
|
||||||
|
f.write(f" and {len(domains_in_cert) - 5} more")
|
||||||
|
f.write("\n")
|
||||||
|
f.write("\n")
|
||||||
|
|
||||||
|
# Show all discovered subdomains
|
||||||
subdomains = cert_data.get('unique_subdomains', [])
|
subdomains = cert_data.get('unique_subdomains', [])
|
||||||
|
if subdomains:
|
||||||
|
f.write(f" All Discovered Subdomains ({len(subdomains)} total):\n")
|
||||||
|
for subdomain in subdomains:
|
||||||
|
f.write(f" {subdomain}\n")
|
||||||
|
else:
|
||||||
|
error_msg = cert_data.get('message', 'Unknown error')
|
||||||
|
error_detail = cert_data.get('error', '')
|
||||||
|
f.write(f" Certificate Transparency Query Failed\n")
|
||||||
|
f.write(f" Error: {error_msg}\n")
|
||||||
|
if error_detail:
|
||||||
|
f.write(f" Details: {error_detail}\n")
|
||||||
|
|
||||||
display_count = min(50, len(subdomains))
|
# Enhanced Shodan Results with all available data
|
||||||
for subdomain in subdomains[:display_count]:
|
|
||||||
f.write(f" {subdomain}\n")
|
|
||||||
|
|
||||||
if len(subdomains) > display_count:
|
|
||||||
f.write(f" ... and {len(subdomains) - display_count} more\n")
|
|
||||||
|
|
||||||
# Shodan section
|
|
||||||
shodan_data = results.get('shodan', {})
|
shodan_data = results.get('shodan', {})
|
||||||
if shodan_data.get('success') and shodan_data.get('total_results', 0) > 0:
|
f.write(f"\nShodan Intelligence:\n")
|
||||||
f.write(f"\nShodan Results ({shodan_data.get('total_results', 0)} total):\n")
|
f.write("-" * 19 + "\n")
|
||||||
f.write("-" * 25 + "\n")
|
if shodan_data.get('success'):
|
||||||
for match in shodan_data.get('matches', [])[:5]:
|
total_results = shodan_data.get('total_results', 0)
|
||||||
f.write(f" IP: {match.get('ip_str', 'N/A')}\n")
|
f.write(f" Total Shodan Results: {total_results}\n\n")
|
||||||
f.write(f" Port: {match.get('port', 'N/A')}\n")
|
|
||||||
f.write(f" Service: {match.get('product', 'N/A')}\n")
|
matches = shodan_data.get('matches', [])
|
||||||
f.write(f" Organization: {match.get('org', 'N/A')}\n")
|
if matches:
|
||||||
f.write(" ---\n")
|
f.write(f" Detailed Host Information:\n")
|
||||||
|
for i, match in enumerate(matches):
|
||||||
|
f.write(f" Host #{i+1}:\n")
|
||||||
|
f.write(f" IP Address: {match.get('ip_str', 'N/A')}\n")
|
||||||
|
f.write(f" Port: {match.get('port', 'N/A')}\n")
|
||||||
|
f.write(f" Protocol: {match.get('transport', 'N/A')}\n")
|
||||||
|
f.write(f" Service: {match.get('product', 'N/A')}\n")
|
||||||
|
f.write(f" Version: {match.get('version', 'N/A')}\n")
|
||||||
|
f.write(f" Organization: {match.get('org', 'N/A')}\n")
|
||||||
|
f.write(f" ISP: {match.get('isp', 'N/A')}\n")
|
||||||
|
f.write(f" ASN: {match.get('asn', 'N/A')}\n")
|
||||||
|
|
||||||
|
# Location information
|
||||||
|
location = match.get('location', {})
|
||||||
|
if location:
|
||||||
|
city = location.get('city', 'N/A')
|
||||||
|
region = location.get('region_code', 'N/A')
|
||||||
|
country = location.get('country_name', 'N/A')
|
||||||
|
f.write(f" Location: {city}, {region}, {country}\n")
|
||||||
|
|
||||||
|
# SSL certificate information
|
||||||
|
if 'ssl' in match and match['ssl'].get('cert'):
|
||||||
|
cert = match['ssl']['cert']
|
||||||
|
f.write(f" SSL Certificate:\n")
|
||||||
|
f.write(f" Subject: {cert.get('subject', {}).get('CN', 'N/A')}\n")
|
||||||
|
f.write(f" Issuer: {cert.get('issuer', {}).get('CN', 'N/A')}\n")
|
||||||
|
f.write(f" Expires: {cert.get('expires', 'N/A')}\n")
|
||||||
|
|
||||||
|
# HTTP information if available
|
||||||
|
if 'http' in match:
|
||||||
|
http = match['http']
|
||||||
|
if 'title' in http:
|
||||||
|
f.write(f" HTTP Title: {http['title'][:100]}{'...' if len(http['title']) > 100 else ''}\n")
|
||||||
|
if 'server' in http:
|
||||||
|
f.write(f" HTTP Server: {http['server']}\n")
|
||||||
|
|
||||||
|
# Hostnames
|
||||||
|
hostnames = match.get('hostnames', [])
|
||||||
|
if hostnames:
|
||||||
|
f.write(f" Hostnames: {', '.join(hostnames)}\n")
|
||||||
|
|
||||||
|
f.write(f" Last Updated: {match.get('timestamp', 'N/A')}\n")
|
||||||
|
f.write(" ---\n")
|
||||||
|
|
||||||
|
# Show facets if available
|
||||||
|
facets = shodan_data.get('facets', {})
|
||||||
|
if facets:
|
||||||
|
f.write(f" Shodan Facets (aggregated data):\n")
|
||||||
|
for facet_name, facet_data in facets.items():
|
||||||
|
f.write(f" {facet_name}:\n")
|
||||||
|
for item in facet_data:
|
||||||
|
f.write(f" {item.get('value', 'N/A')}: {item.get('count', 'N/A')} occurrences\n")
|
||||||
|
else:
|
||||||
|
error_msg = shodan_data.get('message', 'Unknown error')
|
||||||
|
f.write(f" Shodan Query Status: Failed\n")
|
||||||
|
f.write(f" Reason: {error_msg}\n")
|
||||||
|
if 'error' in shodan_data:
|
||||||
|
f.write(f" Error Details: {shodan_data['error']}\n")
|
||||||
|
|
||||||
|
f.write(f"\n{'='*50}\n")
|
||||||
|
f.write(f"Report Generation Complete\n")
|
||||||
|
f.write(f"Total sections analyzed: DNS Records, WHOIS, Certificate Transparency, Shodan Intelligence\n")
|
||||||
|
|
||||||
def save_results(self, domain: str, results: Dict[str, Any]) -> None:
|
def save_results(self, domain: str, results: Dict[str, Any]) -> None:
|
||||||
"""Save results in multiple formats."""
|
"""Save results in multiple formats."""
|
||||||
@ -596,7 +885,7 @@ def main():
|
|||||||
print("\n⏹️ Reconnaissance interrupted by user")
|
print("\n⏹️ Reconnaissance interrupted by user")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"\n❌ Error during reconnaissance: {e}")
|
print(f"❌ Error during reconnaissance: {e}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
Loading…
x
Reference in New Issue
Block a user