diff --git a/dnsrecon.py b/dnsrecon.py index 166318d..01ee3e8 100644 --- a/dnsrecon.py +++ b/dnsrecon.py @@ -66,9 +66,9 @@ class DNSReconTool: def get_dns_records(self, domain: str, record_type: str, 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 "" - 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) @@ -78,24 +78,35 @@ class DNSReconTool: for line in output.split('\n'): line = line.strip() if line and not line.startswith(';') and not line.startswith('>>'): - # Split on whitespace, but preserve quoted strings - parts = line.split(None, 4) - if len(parts) >= 5: # name, ttl, class, type, data - records.append({ - 'name': parts[0].rstrip('.'), # Remove trailing dot - 'ttl': parts[1], - 'class': parts[2], - 'type': parts[3], - 'data': parts[4] - }) - elif len(parts) == 4: # Sometimes no data field - records.append({ - 'name': parts[0].rstrip('.'), - 'ttl': parts[1], - 'class': parts[2], - 'type': parts[3], - 'data': '' - }) + # Split on any whitespace (handles both tabs and spaces) + parts = line.split() + + if len(parts) >= 4: + name = parts[0].rstrip('.') + + # Check if second field is numeric (TTL) + if len(parts) >= 5 and parts[1].isdigit(): + # Format: name TTL class type data + ttl = parts[1] + dns_class = parts[2] + dns_type = parts[3] + data = ' '.join(parts[4:]) + else: + # Format: name class type data (no TTL shown) + ttl = '' + 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 { 'query': f"{domain} {record_type}", @@ -124,15 +135,23 @@ class DNSReconTool: dns_results = {} for record_type in record_types: + print(f" Querying {record_type} records...") dns_results[record_type] = {} for server in dns_servers: server_name = server or 'system' - dns_results[record_type][server_name] = self.get_dns_records( - domain, record_type, server - ) + result = self.get_dns_records(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 # Try DNSSEC validation + print(" Querying DNSSEC information...") dnssec_cmd = f"dig {domain} +dnssec +noall +answer" dns_results['DNSSEC'] = { 'system': { @@ -146,7 +165,7 @@ class DNSReconTool: return dns_results 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...") raw_whois = self.run_command(f"whois {domain}") @@ -161,12 +180,26 @@ class DNSReconTool: lines = raw_whois.split('\n') for line in lines: line = line.strip() - if ':' in line and not line.startswith('%') and not line.startswith('#'): - key, value = line.split(':', 1) - key = key.strip().lower().replace(' ', '_') + if ':' in line and not line.startswith('%') and not line.startswith('#') and not line.startswith('>>>'): + # Handle different WHOIS formats + 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() - if value: - whois_data['parsed'][key] = value + if value and key: + # 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 @@ -280,33 +313,52 @@ class DNSReconTool: def _write_dns_server_comparison(self, f, dns_data: Dict): """Compare responses from different DNS servers.""" 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 for record_type in record_types: if record_type in dns_data: + f.write(f"\n{record_type} Records:\n") server_results = {} + errors = {} + for server in servers: if server in dns_data[record_type]: - records = dns_data[record_type][server].get('records', []) - server_results[server] = set(r.get('data', '') for r in records if r.get('data')) + server_data = dns_data[record_type][server] + 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 if len(server_results) > 1: - all_results = list(server_results.values()) - if not all(result == all_results[0] for result in all_results): - if not discrepancies_found: - 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") + unique_results = set(frozenset(result) for result in server_results.values()) + if len(unique_results) > 1: + f.write(f" āš ļø INCONSISTENCY DETECTED between servers!\n") + discrepancies_found = True 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: - """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: f.write(f"DNS Reconnaissance Report\n") f.write(f"{'='*50}\n") @@ -319,201 +371,438 @@ class DNSReconTool: def get_system_records(record_type): return dns_data.get(record_type, {}).get('system', {}).get('records', []) - # A Records (IPv4) - self._write_dns_section(f, "A Records (IPv4)", get_system_records('A'), - lambda r: r.get('data', 'N/A')) + # Helper function to get all server records for a type + def get_all_server_records(record_type): + 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) - self._write_dns_section(f, "AAAA Records (IPv6)", get_system_records('AAAA'), - lambda r: r.get('data', 'N/A')) + # A Records (IPv4) with TTL and all servers + f.write(f"\nA Records (IPv4):\n") + 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') + f.write(f"\nMX Records (Mail Servers):\n") + f.write("-" * 26 + "\n") if mx_records: - f.write(f"\nMX Records (Mail Servers):\n") for record in mx_records: data_parts = record.get('data', '').split() priority = data_parts[0] if data_parts 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: - f.write(f"\nMX Records (Mail Servers): None found\n") + f.write(" No MX records found\n") - # NS Records (Name Servers) - self._write_dns_section(f, "NS Records (Name Servers)", get_system_records('NS'), - lambda r: r.get('data', 'N/A')) + # NS Records (Name Servers) with TTL + ns_records = get_system_records('NS') + 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 - self._write_dns_section(f, "CNAME Records", get_system_records('CNAME'), - lambda r: f"{r.get('name', 'N/A')} -> {r.get('data', 'N/A')}") + # CNAME Records with TTL + cname_records = get_system_records('CNAME') + 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') + f.write(f"\nTXT Records:\n") + f.write("-" * 12 + "\n") if txt_records: - f.write(f"\nTXT Records:\n") for record in txt_records: txt_data = record.get('data', '').strip() + ttl = record.get('ttl', 'N/A') if record.get('ttl') else 'Not shown' + # Clean up quoted text if txt_data.startswith('"') and txt_data.endswith('"'): txt_data = txt_data[1:-1] # Identify common TXT record types 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'): - f.write(f" [DMARC] {txt_data}\n") + f.write(f" [DMARC] {txt_data} (TTL: {ttl})\n") 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: - 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: - f.write(f" {txt_data}\n") + f.write(f" {txt_data} (TTL: {ttl})\n") 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') + f.write(f"\nCAA Records (Certificate Authority Authorization):\n") + f.write("-" * 48 + "\n") if caa_records: - f.write(f"\nCAA Records (Certificate Authority Authorization):\n") for record in caa_records: data_parts = record.get('data', '').split() + ttl = record.get('ttl', 'N/A') if record.get('ttl') else 'Not shown' if len(data_parts) >= 3: flags = data_parts[0] tag = data_parts[1] value = ' '.join(data_parts[2:]).strip('"') - f.write(f" {flags} {tag} {value}\n") + f.write(f" {flags} {tag} {value} (TTL: {ttl})\n") else: - f.write(f" {record.get('data', 'N/A')}\n") + f.write(f" {record.get('data', 'N/A')} (TTL: {ttl})\n") 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') + f.write(f"\nSRV Records (Service Records):\n") + f.write("-" * 30 + "\n") if srv_records: - f.write(f"\nSRV Records (Service Records):\n") for record in srv_records: data_parts = record.get('data', '').split() + ttl = record.get('ttl', 'N/A') if record.get('ttl') else 'Not shown' if len(data_parts) >= 4: priority, weight, port, target = data_parts[:4] f.write(f" {record.get('name', 'N/A')}\n") f.write(f" Priority: {priority}, Weight: {weight}\n") f.write(f" Port: {port}, Target: {target}\n") + f.write(f" TTL: {ttl}\n") else: - f.write(f" {record.get('data', 'N/A')}\n") + f.write(f" {record.get('data', 'N/A')} (TTL: {ttl})\n") 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') + f.write(f"\nSOA Record (Zone Authority):\n") + f.write("-" * 27 + "\n") if soa_records: - f.write(f"\nSOA Record (Zone Authority):\n") for record in soa_records: data_parts = record.get('data', '').split() + ttl = record.get('ttl', 'N/A') if record.get('ttl') else 'Not shown' if len(data_parts) >= 7: primary_ns, admin_email = data_parts[:2] serial, refresh, retry, expire, minimum = data_parts[2:7] f.write(f" Primary Name Server: {primary_ns}\n") f.write(f" Admin Email: {admin_email}\n") - f.write(f" Serial: {serial}\n") - f.write(f" Refresh: {refresh}s, Retry: {retry}s\n") - f.write(f" Expire: {expire}s, Minimum TTL: {minimum}s\n") + f.write(f" Serial Number: {serial}\n") + f.write(f" Refresh Interval: {refresh} seconds\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: - f.write(f" {record.get('data', 'N/A')}\n") + f.write(f" {record.get('data', 'N/A')} (TTL: {ttl})\n") 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_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(): - f.write(f"\nDNSSEC Status:\n") - f.write("-" * 14 + "\n") - if 'RRSIG' in dnssec_output or 'DNSKEY' in dnssec_output: - f.write(f" āœ… DNSSEC is enabled\n") + if 'RRSIG' in dnssec_output: + f.write(" āœ… DNSSEC is enabled (RRSIG records found)\n") + elif 'DNSKEY' in dnssec_output: + 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: - f.write(f" āŒ DNSSEC not detected\n") + f.write(" ā“ DNSSEC query returned data but no signatures detected\n") + + # Show sample DNSSEC records (first few lines) + dnssec_lines = [line.strip() for line in dnssec_output.split('\n') if line.strip()] + 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(f"\nDNSSEC Status: Unable to determine\n") + f.write(" āŒ DNSSEC not detected or query failed\n") + if dnssec_output.startswith('Error:'): + f.write(f" Error: {dnssec_output}\n") - # DNS Server Comparison (show discrepancies) - f.write(f"\nDNS Server Comparison:\n") - f.write("-" * 21 + "\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) - # Enhanced WHOIS Information + # Enhanced WHOIS Information with ALL parsed fields whois_data = results.get('whois', {}) + f.write(f"\nWHOIS Information (Complete):\n") + f.write("-" * 29 + "\n") if whois_data.get('parsed'): - f.write(f"\nWHOIS Information\n") - f.write("-" * 17 + "\n") parsed = whois_data['parsed'] - # Domain info - for field in ['domain_name', 'domain']: + # Group fields by category for better organization + 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: - f.write(f"Domain: {parsed[field]}\n") + f.write(f" Domain: {parsed[field]}\n") break - # Registrar info - for field in ['registrar', 'sponsoring_registrar']: + for field in registrar_fields: if field in parsed: - f.write(f"Registrar: {parsed[field]}\n") + f.write(f" Registrar: {parsed[field]}\n") break - # Important dates - for field in ['creation_date', 'created']: + # Show all date fields found + for field in date_fields: if field in parsed: - f.write(f"Created: {parsed[field]}\n") - break - - for field in ['expiration_date', 'registry_expiry_date', 'expires']: - if field in parsed: - f.write(f"Expires: {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 + field_display = field.replace('_', ' ').title() + f.write(f" {field_display}: {parsed[field]}\n") - # Status - for field in ['status', 'domain_status']: + for field in status_fields: if field in parsed: - f.write(f"Status: {parsed[field]}\n") + f.write(f" Status: {parsed[field]}\n") break # 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: - f.write(f"WHOIS Name Servers:\n") - for field in sorted(ns_fields)[:10]: # Limit to 10 - f.write(f" {parsed[field]}\n") + f.write(f" WHOIS Name Servers:\n") + for field in sorted(ns_fields): + value = parsed[field] + if isinstance(value, list): + for ns in value: + f.write(f" {ns}\n") + else: + f.write(f" {value}\n") + + # 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") - # Certificate transparency section + 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', {}) + f.write(f"\nCertificate Transparency Logs:\n") + f.write("-" * 30 + "\n") if cert_data.get('success'): + total_certs = cert_data.get('total_certificates', 0) subdomain_count = cert_data.get('subdomain_count', 0) - f.write(f"\nSubdomains from Certificate Logs ({subdomain_count} total):\n") - f.write("-" * 45 + "\n") + f.write(f" Total Certificates Found: {total_certs}\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', []) - - display_count = min(50, len(subdomains)) - 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") + 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") - # Shodan section + # Enhanced Shodan Results with all available data shodan_data = results.get('shodan', {}) - if shodan_data.get('success') and shodan_data.get('total_results', 0) > 0: - f.write(f"\nShodan Results ({shodan_data.get('total_results', 0)} total):\n") - f.write("-" * 25 + "\n") - for match in shodan_data.get('matches', [])[:5]: - f.write(f" IP: {match.get('ip_str', 'N/A')}\n") - f.write(f" Port: {match.get('port', 'N/A')}\n") - f.write(f" Service: {match.get('product', 'N/A')}\n") - f.write(f" Organization: {match.get('org', 'N/A')}\n") - f.write(" ---\n") + f.write(f"\nShodan Intelligence:\n") + f.write("-" * 19 + "\n") + if shodan_data.get('success'): + total_results = shodan_data.get('total_results', 0) + f.write(f" Total Shodan Results: {total_results}\n\n") + + matches = shodan_data.get('matches', []) + if matches: + 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: """Save results in multiple formats.""" @@ -596,7 +885,7 @@ def main(): print("\nā¹ļø Reconnaissance interrupted by user") sys.exit(0) except Exception as e: - print(f"\nāŒ Error during reconnaissance: {e}") + print(f"āŒ Error during reconnaissance: {e}") sys.exit(1) if __name__ == "__main__":