This commit is contained in:
overcuriousity 2025-09-09 15:42:53 +02:00
parent 0c9cf00a3b
commit c105ebbb4b
5 changed files with 363 additions and 137 deletions

View File

@ -5,6 +5,7 @@ import requests
import json import json
import time import time
import logging import logging
import socket
from datetime import datetime from datetime import datetime
from typing import List, Optional, Set from typing import List, Optional, Set
from .data_structures import Certificate from .data_structures import Certificate
@ -22,9 +23,52 @@ class CertificateChecker:
self.config = config self.config = config
self.last_request = 0 self.last_request = 0
self.query_count = 0 self.query_count = 0
self.connection_failures = 0
self.max_connection_failures = 3 # Stop trying after 3 consecutive failures
logger.info("🔐 Certificate checker initialized") logger.info("🔐 Certificate checker initialized")
# Test connectivity to crt.sh on initialization
self._test_connectivity()
def _test_connectivity(self):
"""Test if we can reach crt.sh."""
try:
logger.info("🔗 Testing connectivity to crt.sh...")
# First test DNS resolution
try:
socket.gethostbyname('crt.sh')
logger.debug("✅ DNS resolution for crt.sh successful")
except socket.gaierror as e:
logger.warning(f"⚠️ DNS resolution failed for crt.sh: {e}")
return False
# Test HTTP connection with a simple request
response = requests.get(
self.CRT_SH_URL,
params={'q': 'example.com', 'output': 'json'},
timeout=10,
headers={'User-Agent': 'DNS-Recon-Tool/1.0'}
)
if response.status_code in [200, 404]: # 404 is also acceptable (no results)
logger.info("✅ crt.sh connectivity test successful")
return True
else:
logger.warning(f"⚠️ crt.sh returned status {response.status_code}")
return False
except requests.exceptions.ConnectionError as e:
logger.warning(f"⚠️ Cannot reach crt.sh: {e}")
return False
except requests.exceptions.Timeout:
logger.warning("⚠️ crt.sh connectivity test timed out")
return False
except Exception as e:
logger.warning(f"⚠️ Unexpected error testing crt.sh connectivity: {e}")
return False
def _rate_limit(self): def _rate_limit(self):
"""Apply rate limiting for crt.sh.""" """Apply rate limiting for crt.sh."""
now = time.time() now = time.time()
@ -33,7 +77,7 @@ class CertificateChecker:
if time_since_last < min_interval: if time_since_last < min_interval:
sleep_time = min_interval - time_since_last sleep_time = min_interval - time_since_last
logger.debug(f"⏸️ crt.sh rate limiting: sleeping for {sleep_time:.2f}s") logger.debug(f"⏸️ crt.sh rate limiting: sleeping for {sleep_time:.2f}s")
time.sleep(sleep_time) time.sleep(sleep_time)
self.last_request = time.time() self.last_request = time.time()
@ -41,7 +85,12 @@ class CertificateChecker:
def get_certificates(self, domain: str) -> List[Certificate]: def get_certificates(self, domain: str) -> List[Certificate]:
"""Get certificates for a domain from crt.sh.""" """Get certificates for a domain from crt.sh."""
logger.debug(f"🔐 Getting certificates for domain: {domain}") logger.debug(f"🔍 Getting certificates for domain: {domain}")
# Skip if we've had too many connection failures
if self.connection_failures >= self.max_connection_failures:
logger.warning(f"⚠️ Skipping certificate lookup for {domain} due to repeated connection failures")
return []
certificates = [] certificates = []
@ -49,9 +98,10 @@ class CertificateChecker:
domain_certs = self._query_crt_sh(domain) domain_certs = self._query_crt_sh(domain)
certificates.extend(domain_certs) certificates.extend(domain_certs)
# Also query for wildcard certificates # Also query for wildcard certificates (if the main query succeeded)
wildcard_certs = self._query_crt_sh(f"%.{domain}") if domain_certs or self.connection_failures < self.max_connection_failures:
certificates.extend(wildcard_certs) wildcard_certs = self._query_crt_sh(f"%.{domain}")
certificates.extend(wildcard_certs)
# Remove duplicates based on certificate ID # Remove duplicates based on certificate ID
unique_certs = {cert.id: cert for cert in certificates} unique_certs = {cert.id: cert for cert in certificates}
@ -65,13 +115,15 @@ class CertificateChecker:
return final_certs return final_certs
def _query_crt_sh(self, query: str) -> List[Certificate]: def _query_crt_sh(self, query: str) -> List[Certificate]:
"""Query crt.sh API with retry logic.""" """Query crt.sh API with retry logic and better error handling."""
certificates = [] certificates = []
self._rate_limit() self._rate_limit()
logger.debug(f"📡 Querying crt.sh for: {query}") logger.debug(f"📡 Querying crt.sh for: {query}")
max_retries = 3 max_retries = 2 # Reduced retries for faster failure
backoff_delays = [1, 3] # Shorter delays
for attempt in range(max_retries): for attempt in range(max_retries):
try: try:
params = { params = {
@ -111,50 +163,68 @@ class CertificateChecker:
certificates.append(certificate) certificates.append(certificate)
logger.debug(f"✅ Parsed certificate ID {certificate.id} for {query}") logger.debug(f"✅ Parsed certificate ID {certificate.id} for {query}")
else: else:
logger.debug(f"⚠️ Skipped certificate with invalid dates: {cert_data.get('id')}") logger.debug(f"⚠️ Skipped certificate with invalid dates: {cert_data.get('id')}")
except (ValueError, TypeError, KeyError) as e: except (ValueError, TypeError, KeyError) as e:
logger.debug(f"⚠️ Error parsing certificate data: {e}") logger.debug(f"⚠️ Error parsing certificate data: {e}")
continue # Skip malformed certificate data continue # Skip malformed certificate data
# Success! Reset connection failure counter
self.connection_failures = 0
logger.info(f"✅ Successfully processed {len(certificates)} certificates from crt.sh for {query}") logger.info(f"✅ Successfully processed {len(certificates)} certificates from crt.sh for {query}")
return certificates # Success, exit retry loop return certificates
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
logger.warning(f"❌ Invalid JSON response from crt.sh for {query}: {e}") logger.warning(f"❌ Invalid JSON response from crt.sh for {query}: {e}")
if attempt < max_retries - 1: if attempt < max_retries - 1:
time.sleep(2 ** attempt) # Exponential backoff time.sleep(backoff_delays[attempt])
continue continue
return certificates return certificates
elif response.status_code == 404:
# 404 is normal - no certificates found
logger.debug(f" No certificates found for {query} (404)")
self.connection_failures = 0 # Reset counter for successful connection
return certificates
elif response.status_code == 429: elif response.status_code == 429:
logger.warning(f"⚠️ crt.sh rate limit exceeded for {query}") logger.warning(f"⚠️ crt.sh rate limit exceeded for {query}")
if attempt < max_retries - 1: if attempt < max_retries - 1:
time.sleep(5) # Wait longer for rate limits time.sleep(5) # Wait longer for rate limits
continue continue
return certificates return certificates
else: else:
logger.warning(f"⚠️ crt.sh HTTP error for {query}: {response.status_code}") logger.warning(f"⚠️ crt.sh HTTP error for {query}: {response.status_code}")
if attempt < max_retries - 1: if attempt < max_retries - 1:
time.sleep(2) time.sleep(backoff_delays[attempt])
continue continue
return certificates return certificates
except requests.exceptions.Timeout: except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
logger.warning(f"⏱️ crt.sh query timeout for {query} (attempt {attempt+1}/{max_retries})") error_type = "Connection Error" if isinstance(e, requests.exceptions.ConnectionError) else "Timeout"
logger.warning(f"🌐 crt.sh {error_type} for {query} (attempt {attempt+1}/{max_retries}): {e}")
# Track connection failures
if isinstance(e, requests.exceptions.ConnectionError):
self.connection_failures += 1
if self.connection_failures >= self.max_connection_failures:
logger.error(f"❌ Too many connection failures to crt.sh. Disabling certificate lookups.")
return certificates
if attempt < max_retries - 1: if attempt < max_retries - 1:
time.sleep(2) time.sleep(backoff_delays[attempt])
continue continue
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.warning(f"🌐 crt.sh network error for {query} (attempt {attempt+1}/{max_retries}): {e}") logger.warning(f"🌐 crt.sh network error for {query} (attempt {attempt+1}/{max_retries}): {e}")
if attempt < max_retries - 1: if attempt < max_retries - 1:
time.sleep(2) time.sleep(backoff_delays[attempt])
continue continue
except Exception as e: except Exception as e:
logger.error(f"❌ Unexpected error querying crt.sh for {query}: {e}") logger.error(f"❌ Unexpected error querying crt.sh for {query}: {e}")
if attempt < max_retries - 1: if attempt < max_retries - 1:
time.sleep(2) time.sleep(backoff_delays[attempt])
continue continue
# If we get here, all retries failed # If we get here, all retries failed
@ -187,7 +257,7 @@ class CertificateChecker:
except ValueError: except ValueError:
pass pass
logger.debug(f"⚠️ Could not parse date: {date_str}") logger.debug(f"⚠️ Could not parse date: {date_str}")
return None return None
def extract_subdomains_from_certificates(self, certificates: List[Certificate]) -> Set[str]: def extract_subdomains_from_certificates(self, certificates: List[Certificate]) -> Set[str]:

View File

@ -34,23 +34,31 @@ class ReconnaissanceEngine:
self.shodan_client = ShodanClient(config.shodan_key, config) self.shodan_client = ShodanClient(config.shodan_key, config)
logger.info("✅ Shodan client initialized") logger.info("✅ Shodan client initialized")
else: else:
logger.info("⚠️ Shodan API key not provided, skipping Shodan integration") logger.info("⚠️ Shodan API key not provided, skipping Shodan integration")
self.virustotal_client = None self.virustotal_client = None
if config.virustotal_key: if config.virustotal_key:
self.virustotal_client = VirusTotalClient(config.virustotal_key, config) self.virustotal_client = VirusTotalClient(config.virustotal_key, config)
logger.info("✅ VirusTotal client initialized") logger.info("✅ VirusTotal client initialized")
else: else:
logger.info("⚠️ VirusTotal API key not provided, skipping VirusTotal integration") logger.info("⚠️ VirusTotal API key not provided, skipping VirusTotal integration")
# Progress tracking # Progress tracking
self.progress_callback = None self.progress_callback = None
self._lock = threading.Lock() self._lock = threading.Lock()
# Shared data object for live updates
self.shared_data = None
def set_progress_callback(self, callback): def set_progress_callback(self, callback):
"""Set callback for progress updates.""" """Set callback for progress updates."""
self.progress_callback = callback self.progress_callback = callback
def set_shared_data(self, shared_data: ReconData):
"""Set shared data object for live updates during web interface usage."""
self.shared_data = shared_data
logger.info("📊 Using shared data object for live updates")
def _update_progress(self, message: str, percentage: int = None): def _update_progress(self, message: str, percentage: int = None):
"""Update progress if callback is set.""" """Update progress if callback is set."""
logger.info(f"Progress: {message} ({percentage}%)" if percentage else f"Progress: {message}") logger.info(f"Progress: {message} ({percentage}%)" if percentage else f"Progress: {message}")
@ -59,7 +67,14 @@ class ReconnaissanceEngine:
def run_reconnaissance(self, target: str) -> ReconData: def run_reconnaissance(self, target: str) -> ReconData:
"""Run full reconnaissance on target.""" """Run full reconnaissance on target."""
self.data = ReconData() # Use shared data object if available, otherwise create new one
if self.shared_data is not None:
self.data = self.shared_data
logger.info("📊 Using shared data object for reconnaissance")
else:
self.data = ReconData()
logger.info("📊 Created new data object for reconnaissance")
self.data.start_time = datetime.now() self.data.start_time = datetime.now()
logger.info(f"🚀 Starting reconnaissance for target: {target}") logger.info(f"🚀 Starting reconnaissance for target: {target}")
@ -100,7 +115,7 @@ class ReconnaissanceEngine:
finally: finally:
self.data.end_time = datetime.now() self.data.end_time = datetime.now()
duration = self.data.end_time - self.data.start_time duration = self.data.end_time - self.data.start_time
logger.info(f"⏱️ Total reconnaissance time: {duration}") logger.info(f"⏱️ Total reconnaissance time: {duration}")
return self.data return self.data
@ -108,7 +123,7 @@ class ReconnaissanceEngine:
"""Expand hostname to all possible TLDs.""" """Expand hostname to all possible TLDs."""
logger.info(f"🌐 Fetching TLD list for hostname expansion") logger.info(f"🌐 Fetching TLD list for hostname expansion")
tlds = self.tld_fetcher.get_tlds() tlds = self.tld_fetcher.get_tlds()
logger.info(f"📝 Testing against {len(tlds)} TLDs") logger.info(f"🔍 Testing against {len(tlds)} TLDs")
targets = set() targets = set()
tested_count = 0 tested_count = 0
@ -182,7 +197,7 @@ class ReconnaissanceEngine:
self.data.add_ip_address(record.value) self.data.add_ip_address(record.value)
# Get certificates # Get certificates
logger.debug(f"🔐 Checking certificates for {hostname}") logger.debug(f"🔍 Checking certificates for {hostname}")
certificates = self.cert_checker.get_certificates(hostname) certificates = self.cert_checker.get_certificates(hostname)
if certificates: if certificates:
self.data.certificates[hostname] = certificates self.data.certificates[hostname] = certificates
@ -234,7 +249,7 @@ class ReconnaissanceEngine:
# Shodan lookups # Shodan lookups
if self.shodan_client: if self.shodan_client:
logger.info(f"🕵️ Starting Shodan lookups for {len(self.data.ip_addresses)} IPs") logger.info(f"🕵️ Starting Shodan lookups for {len(self.data.ip_addresses)} IPs")
shodan_success_count = 0 shodan_success_count = 0
for ip in self.data.ip_addresses: for ip in self.data.ip_addresses:
@ -248,16 +263,16 @@ class ReconnaissanceEngine:
else: else:
logger.debug(f"❌ No Shodan data for {ip}") logger.debug(f"❌ No Shodan data for {ip}")
except Exception as e: except Exception as e:
logger.warning(f"⚠️ Error querying Shodan for {ip}: {e}") logger.warning(f"⚠️ Error querying Shodan for {ip}: {e}")
logger.info(f"✅ Shodan lookups complete: {shodan_success_count}/{len(self.data.ip_addresses)} successful") logger.info(f"✅ Shodan lookups complete: {shodan_success_count}/{len(self.data.ip_addresses)} successful")
else: else:
logger.info("⚠️ Skipping Shodan lookups (no API key)") logger.info("⚠️ Skipping Shodan lookups (no API key)")
# VirusTotal lookups # VirusTotal lookups
if self.virustotal_client: if self.virustotal_client:
total_resources = len(self.data.ip_addresses) + len(self.data.hostnames) total_resources = len(self.data.ip_addresses) + len(self.data.hostnames)
logger.info(f"🛡️ Starting VirusTotal lookups for {total_resources} resources") logger.info(f"🛡️ Starting VirusTotal lookups for {total_resources} resources")
vt_success_count = 0 vt_success_count = 0
# Check IPs # Check IPs
@ -268,11 +283,11 @@ class ReconnaissanceEngine:
if result: if result:
self.data.add_virustotal_result(ip, result) self.data.add_virustotal_result(ip, result)
vt_success_count += 1 vt_success_count += 1
logger.info(f"🛡️ VirusTotal result for {ip}: {result.positives}/{result.total} detections") logger.info(f"🛡️ VirusTotal result for {ip}: {result.positives}/{result.total} detections")
else: else:
logger.debug(f"❌ No VirusTotal data for {ip}") logger.debug(f"❌ No VirusTotal data for {ip}")
except Exception as e: except Exception as e:
logger.warning(f"⚠️ Error querying VirusTotal for IP {ip}: {e}") logger.warning(f"⚠️ Error querying VirusTotal for IP {ip}: {e}")
# Check domains # Check domains
for hostname in self.data.hostnames: for hostname in self.data.hostnames:
@ -282,15 +297,15 @@ class ReconnaissanceEngine:
if result: if result:
self.data.add_virustotal_result(hostname, result) self.data.add_virustotal_result(hostname, result)
vt_success_count += 1 vt_success_count += 1
logger.info(f"🛡️ VirusTotal result for {hostname}: {result.positives}/{result.total} detections") logger.info(f"🛡️ VirusTotal result for {hostname}: {result.positives}/{result.total} detections")
else: else:
logger.debug(f"❌ No VirusTotal data for {hostname}") logger.debug(f"❌ No VirusTotal data for {hostname}")
except Exception as e: except Exception as e:
logger.warning(f"⚠️ Error querying VirusTotal for domain {hostname}: {e}") logger.warning(f"⚠️ Error querying VirusTotal for domain {hostname}: {e}")
logger.info(f"✅ VirusTotal lookups complete: {vt_success_count}/{total_resources} successful") logger.info(f"✅ VirusTotal lookups complete: {vt_success_count}/{total_resources} successful")
else: else:
logger.info("⚠️ Skipping VirusTotal lookups (no API key)") logger.info("⚠️ Skipping VirusTotal lookups (no API key)")
# Final external lookup summary # Final external lookup summary
ext_stats = { ext_stats = {

View File

@ -8,6 +8,7 @@ import logging
from .config import Config from .config import Config
from .reconnaissance import ReconnaissanceEngine from .reconnaissance import ReconnaissanceEngine
from .report_generator import ReportGenerator from .report_generator import ReportGenerator
from .data_structures import ReconData
# Set up logging for this module # Set up logging for this module
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -46,20 +47,23 @@ def create_app(config: Config):
) )
if not target: if not target:
logger.warning("⚠️ Scan request missing target") logger.warning("⚠️ Scan request missing target")
return jsonify({'error': 'Target is required'}), 400 return jsonify({'error': 'Target is required'}), 400
# Generate scan ID # Generate scan ID
scan_id = f"{target}_{int(time.time())}" scan_id = f"{target}_{int(time.time())}"
logger.info(f"🚀 Starting new scan: {scan_id} for target: {target}") logger.info(f"🚀 Starting new scan: {scan_id} for target: {target}")
# Initialize scan data # Create shared ReconData object for live updates
shared_data = ReconData()
# Initialize scan data with the shared data object
with scan_lock: with scan_lock:
active_scans[scan_id] = { active_scans[scan_id] = {
'status': 'starting', 'status': 'starting',
'progress': 0, 'progress': 0,
'message': 'Initializing...', 'message': 'Initializing...',
'data': None, 'data': shared_data, # Share the data object from the start!
'error': None, 'error': None,
'live_stats': { 'live_stats': {
'hostnames': 0, 'hostnames': 0,
@ -75,7 +79,7 @@ def create_app(config: Config):
# Start reconnaissance in background thread # Start reconnaissance in background thread
thread = threading.Thread( thread = threading.Thread(
target=run_reconnaissance_background, target=run_reconnaissance_background,
args=(scan_id, target, scan_config) args=(scan_id, target, scan_config, shared_data)
) )
thread.daemon = True thread.daemon = True
thread.start() thread.start()
@ -118,7 +122,7 @@ def create_app(config: Config):
report_gen = ReportGenerator(scan_data['data']) report_gen = ReportGenerator(scan_data['data'])
return jsonify({ return jsonify({
'json_report': scan_data['data'].to_json(), # This should now work properly 'json_report': scan_data['data'].to_json(),
'text_report': report_gen.generate_text_report() 'text_report': report_gen.generate_text_report()
}) })
except Exception as e: except Exception as e:
@ -134,37 +138,41 @@ def create_app(config: Config):
scan_data = active_scans[scan_id] scan_data = active_scans[scan_id]
if not scan_data['data']: # Now we always have a data object, even if it's empty initially
data_obj = scan_data['data']
if not data_obj:
return jsonify({ return jsonify({
'hostnames': [], 'hostnames': [],
'ip_addresses': [], 'ip_addresses': [],
'stats': scan_data['live_stats'] 'stats': scan_data['live_stats'],
'latest_discoveries': []
}) })
# Return current discoveries # Return current discoveries from the shared data object
return jsonify({ return jsonify({
'hostnames': sorted(list(scan_data['data'].hostnames)), 'hostnames': sorted(list(data_obj.hostnames)),
'ip_addresses': sorted(list(scan_data['data'].ip_addresses)), 'ip_addresses': sorted(list(data_obj.ip_addresses)),
'stats': scan_data['data'].get_stats(), 'stats': data_obj.get_stats(),
'latest_discoveries': scan_data.get('latest_discoveries', []) 'latest_discoveries': scan_data.get('latest_discoveries', [])
}) })
return app return app
def run_reconnaissance_background(scan_id: str, target: str, config: Config): def run_reconnaissance_background(scan_id: str, target: str, config: Config, shared_data: ReconData):
"""Run reconnaissance in background thread.""" """Run reconnaissance in background thread with shared data object."""
def update_progress(message: str, percentage: int = None): def update_progress(message: str, percentage: int = None):
"""Update scan progress.""" """Update scan progress and live statistics."""
with scan_lock: with scan_lock:
if scan_id in active_scans: if scan_id in active_scans:
active_scans[scan_id]['message'] = message active_scans[scan_id]['message'] = message
if percentage is not None: if percentage is not None:
active_scans[scan_id]['progress'] = percentage active_scans[scan_id]['progress'] = percentage
# Update live stats if we have data # Update live stats from the shared data object
if active_scans[scan_id]['data']: if shared_data:
active_scans[scan_id]['live_stats'] = active_scans[scan_id]['data'].get_stats() active_scans[scan_id]['live_stats'] = shared_data.get_stats()
# Add to latest discoveries (keep last 10) # Add to latest discoveries (keep last 10)
if 'latest_discoveries' not in active_scans[scan_id]: if 'latest_discoveries' not in active_scans[scan_id]:
@ -188,27 +196,30 @@ def run_reconnaissance_background(scan_id: str, target: str, config: Config):
engine = ReconnaissanceEngine(config) engine = ReconnaissanceEngine(config)
engine.set_progress_callback(update_progress) engine.set_progress_callback(update_progress)
# IMPORTANT: Pass the shared data object to the engine
engine.set_shared_data(shared_data)
# Update status # Update status
with scan_lock: with scan_lock:
active_scans[scan_id]['status'] = 'running' active_scans[scan_id]['status'] = 'running'
logger.info(f"🚀 Starting reconnaissance for: {target}") logger.info(f"🚀 Starting reconnaissance for: {target}")
# Run reconnaissance # Run reconnaissance - this will populate the shared_data object incrementally
data = engine.run_reconnaissance(target) final_data = engine.run_reconnaissance(target)
logger.info(f"✅ Reconnaissance completed for scan: {scan_id}") logger.info(f"✅ Reconnaissance completed for scan: {scan_id}")
# Update with results # Update with final results (the shared_data should already be populated)
with scan_lock: with scan_lock:
active_scans[scan_id]['status'] = 'completed' active_scans[scan_id]['status'] = 'completed'
active_scans[scan_id]['progress'] = 100 active_scans[scan_id]['progress'] = 100
active_scans[scan_id]['message'] = 'Reconnaissance completed' active_scans[scan_id]['message'] = 'Reconnaissance completed'
active_scans[scan_id]['data'] = data active_scans[scan_id]['data'] = final_data # This should be the same as shared_data
active_scans[scan_id]['live_stats'] = data.get_stats() active_scans[scan_id]['live_stats'] = final_data.get_stats()
# Log final statistics # Log final statistics
final_stats = data.get_stats() final_stats = final_data.get_stats()
logger.info(f"📊 Final stats for {scan_id}: {final_stats}") logger.info(f"📊 Final stats for {scan_id}: {final_stats}")
except Exception as e: except Exception as e:

View File

@ -1,4 +1,4 @@
// DNS Reconnaissance Tool - Enhanced Frontend JavaScript with Real-time Updates // DNS Reconnaissance Tool - Enhanced Frontend JavaScript with Debug Output
class ReconTool { class ReconTool {
constructor() { constructor() {
@ -6,9 +6,20 @@ class ReconTool {
this.pollInterval = null; this.pollInterval = null;
this.liveDataInterval = null; this.liveDataInterval = null;
this.currentReport = null; this.currentReport = null;
this.debugMode = true; // Enable debug logging
this.init(); this.init();
} }
debug(message, data = null) {
if (this.debugMode) {
if (data) {
console.log(`🔍 DEBUG: ${message}`, data);
} else {
console.log(`🔍 DEBUG: ${message}`);
}
}
}
init() { init() {
this.bindEvents(); this.bindEvents();
this.setupRealtimeElements(); this.setupRealtimeElements();
@ -67,6 +78,7 @@ class ReconTool {
</div> </div>
`; `;
progressSection.appendChild(liveDiv); progressSection.appendChild(liveDiv);
this.debug("Live discoveries container created");
} }
} }
@ -127,7 +139,7 @@ class ReconTool {
this.showProgressSection(); this.showProgressSection();
this.updateProgress(0, 'Starting scan...'); this.updateProgress(0, 'Starting scan...');
console.log('🚀 Starting scan with data:', scanData); this.debug('Starting scan with data:', scanData);
const response = await fetch('/api/scan', { const response = await fetch('/api/scan', {
method: 'POST', method: 'POST',
@ -148,7 +160,7 @@ class ReconTool {
} }
this.currentScanId = result.scan_id; this.currentScanId = result.scan_id;
console.log('✅ Scan started with ID:', this.currentScanId); this.debug('Scan started with ID:', this.currentScanId);
this.startPolling(); this.startPolling();
this.startLiveDataPolling(); this.startLiveDataPolling();
@ -160,6 +172,7 @@ class ReconTool {
} }
startPolling() { startPolling() {
this.debug('Starting status polling...');
// Poll every 2 seconds for status updates // Poll every 2 seconds for status updates
this.pollInterval = setInterval(() => { this.pollInterval = setInterval(() => {
this.checkScanStatus(); this.checkScanStatus();
@ -170,6 +183,7 @@ class ReconTool {
} }
startLiveDataPolling() { startLiveDataPolling() {
this.debug('Starting live data polling...');
// Poll every 3 seconds for live data updates // Poll every 3 seconds for live data updates
this.liveDataInterval = setInterval(() => { this.liveDataInterval = setInterval(() => {
this.updateLiveData(); this.updateLiveData();
@ -179,6 +193,9 @@ class ReconTool {
const liveSection = document.querySelector('.live-discoveries'); const liveSection = document.querySelector('.live-discoveries');
if (liveSection) { if (liveSection) {
liveSection.style.display = 'block'; liveSection.style.display = 'block';
this.debug('Live discoveries section made visible');
} else {
this.debug('ERROR: Live discoveries section not found!');
} }
// Also update immediately // Also update immediately
@ -208,12 +225,13 @@ class ReconTool {
// Update live stats // Update live stats
if (status.live_stats) { if (status.live_stats) {
this.debug('Received live stats:', status.live_stats);
this.updateLiveStats(status.live_stats); this.updateLiveStats(status.live_stats);
} }
// Check if completed // Check if completed
if (status.status === 'completed') { if (status.status === 'completed') {
console.log('✅ Scan completed'); this.debug('Scan completed, loading report...');
this.stopPolling(); this.stopPolling();
await this.loadScanReport(); await this.loadScanReport();
} else if (status.status === 'error') { } else if (status.status === 'error') {
@ -233,29 +251,37 @@ class ReconTool {
return; return;
} }
this.debug(`Fetching live data for scan: ${this.currentScanId}`);
try { try {
const response = await fetch(`/api/scan/${this.currentScanId}/live-data`); const response = await fetch(`/api/scan/${this.currentScanId}/live-data`);
if (!response.ok) { if (!response.ok) {
this.debug(`Live data request failed: HTTP ${response.status}`);
return; // Silently fail for live data return; // Silently fail for live data
} }
const data = await response.json(); const data = await response.json();
if (data.error) { if (data.error) {
this.debug('Live data error:', data.error);
return; // Silently fail for live data return; // Silently fail for live data
} }
this.debug('Received live data:', data);
// Update live discoveries // Update live discoveries
this.updateLiveDiscoveries(data); this.updateLiveDiscoveries(data);
} catch (error) { } catch (error) {
// Silently fail for live data updates // Silently fail for live data updates
console.debug('Live data update failed:', error); this.debug('Live data update failed:', error);
} }
} }
updateLiveStats(stats) { updateLiveStats(stats) {
this.debug('Updating live stats:', stats);
// Update the live statistics counters // Update the live statistics counters
const statElements = { const statElements = {
'liveHostnames': stats.hostnames || 0, 'liveHostnames': stats.hostnames || 0,
@ -269,54 +295,69 @@ class ReconTool {
Object.entries(statElements).forEach(([elementId, value]) => { Object.entries(statElements).forEach(([elementId, value]) => {
const element = document.getElementById(elementId); const element = document.getElementById(elementId);
if (element) { if (element) {
const currentValue = element.textContent;
element.textContent = value; element.textContent = value;
// Add a brief highlight effect when value changes if (currentValue !== value.toString()) {
if (element.textContent !== value.toString()) { this.debug(`Updated ${elementId}: ${currentValue} -> ${value}`);
// Add a brief highlight effect when value changes
element.style.backgroundColor = '#ff9900'; element.style.backgroundColor = '#ff9900';
setTimeout(() => { setTimeout(() => {
element.style.backgroundColor = ''; element.style.backgroundColor = '';
}, 1000); }, 1000);
} }
} else {
this.debug(`ERROR: Element ${elementId} not found!`);
} }
}); });
} }
updateLiveDiscoveries(data) { updateLiveDiscoveries(data) {
this.debug('Updating live discoveries with data:', data);
// Update hostnames list // Update hostnames list
const hostnameList = document.querySelector('#recentHostnames .hostname-list'); const hostnameList = document.querySelector('#recentHostnames .hostname-list');
if (hostnameList && data.hostnames) { if (hostnameList && data.hostnames && data.hostnames.length > 0) {
// Show last 10 hostnames // Show last 10 hostnames
const recentHostnames = data.hostnames.slice(-10); const recentHostnames = data.hostnames.slice(-10);
hostnameList.innerHTML = recentHostnames.map(hostname => hostnameList.innerHTML = recentHostnames.map(hostname =>
`<span class="discovery-item">${hostname}</span>` `<span class="discovery-item">${hostname}</span>`
).join(''); ).join('');
this.debug(`Updated hostname list with ${recentHostnames.length} items`);
} else if (hostnameList) {
this.debug(`No hostnames to display (${data.hostnames ? data.hostnames.length : 0} total)`);
} }
// Update IP addresses list // Update IP addresses list
const ipList = document.querySelector('#recentIPs .ip-list'); const ipList = document.querySelector('#recentIPs .ip-list');
if (ipList && data.ip_addresses) { if (ipList && data.ip_addresses && data.ip_addresses.length > 0) {
// Show last 10 IPs // Show last 10 IPs
const recentIPs = data.ip_addresses.slice(-10); const recentIPs = data.ip_addresses.slice(-10);
ipList.innerHTML = recentIPs.map(ip => ipList.innerHTML = recentIPs.map(ip =>
`<span class="discovery-item">${ip}</span>` `<span class="discovery-item">${ip}</span>`
).join(''); ).join('');
this.debug(`Updated IP list with ${recentIPs.length} items`);
} else if (ipList) {
this.debug(`No IPs to display (${data.ip_addresses ? data.ip_addresses.length : 0} total)`);
} }
// Update activity log // Update activity log
const activityList = document.querySelector('#activityLog .activity-list'); const activityList = document.querySelector('#activityLog .activity-list');
if (activityList && data.latest_discoveries) { if (activityList && data.latest_discoveries && data.latest_discoveries.length > 0) {
const activities = data.latest_discoveries.slice(-5); // Last 5 activities const activities = data.latest_discoveries.slice(-5); // Last 5 activities
activityList.innerHTML = activities.map(activity => { activityList.innerHTML = activities.map(activity => {
const time = new Date(activity.timestamp * 1000).toLocaleTimeString(); const time = new Date(activity.timestamp * 1000).toLocaleTimeString();
return `<div class="activity-item">[${time}] ${activity.message}</div>`; return `<div class="activity-item">[${time}] ${activity.message}</div>`;
}).join(''); }).join('');
this.debug(`Updated activity log with ${activities.length} items`);
} else if (activityList) {
this.debug(`No activities to display (${data.latest_discoveries ? data.latest_discoveries.length : 0} total)`);
} }
} }
async loadScanReport() { async loadScanReport() {
try { try {
console.log('📄 Loading scan report...'); this.debug('Loading scan report...');
const response = await fetch(`/api/scan/${this.currentScanId}/report`); const response = await fetch(`/api/scan/${this.currentScanId}/report`);
if (!response.ok) { if (!response.ok) {
@ -330,7 +371,7 @@ class ReconTool {
} }
this.currentReport = report; this.currentReport = report;
console.log('✅ Report loaded successfully'); this.debug('Report loaded successfully');
this.showResultsSection(); this.showResultsSection();
this.showReport('text'); // Default to text view this.showReport('text'); // Default to text view
@ -341,6 +382,7 @@ class ReconTool {
} }
stopPolling() { stopPolling() {
this.debug('Stopping polling intervals...');
if (this.pollInterval) { if (this.pollInterval) {
clearInterval(this.pollInterval); clearInterval(this.pollInterval);
this.pollInterval = null; this.pollInterval = null;
@ -355,18 +397,34 @@ class ReconTool {
document.getElementById('scanForm').style.display = 'none'; document.getElementById('scanForm').style.display = 'none';
document.getElementById('progressSection').style.display = 'block'; document.getElementById('progressSection').style.display = 'block';
document.getElementById('resultsSection').style.display = 'none'; document.getElementById('resultsSection').style.display = 'none';
this.debug('Showing progress section');
} }
showResultsSection() { showResultsSection() {
document.getElementById('scanForm').style.display = 'none'; document.getElementById('scanForm').style.display = 'none';
document.getElementById('progressSection').style.display = 'none'; document.getElementById('progressSection').style.display = 'block'; // Keep visible
document.getElementById('resultsSection').style.display = 'block'; document.getElementById('resultsSection').style.display = 'block';
// Hide live discoveries in results section // Change the title to show it's the final summary
const liveSection = document.querySelector('.live-discoveries'); const liveSection = document.querySelector('.live-discoveries');
if (liveSection) { if (liveSection) {
liveSection.style.display = 'none'; const title = liveSection.querySelector('h3');
if (title) {
title.textContent = '📊 Final Discovery Summary';
}
liveSection.style.display = 'block';
} }
// Hide just the progress bar and scan controls
const progressBar = document.querySelector('.progress-bar');
const progressMessage = document.getElementById('progressMessage');
const scanControls = document.querySelector('.scan-controls');
if (progressBar) progressBar.style.display = 'none';
if (progressMessage) progressMessage.style.display = 'none';
if (scanControls) scanControls.style.display = 'none';
this.debug('Showing results section with live discoveries');
} }
resetToForm() { resetToForm() {
@ -378,10 +436,23 @@ class ReconTool {
document.getElementById('progressSection').style.display = 'none'; document.getElementById('progressSection').style.display = 'none';
document.getElementById('resultsSection').style.display = 'none'; document.getElementById('resultsSection').style.display = 'none';
// Hide live discoveries // Show progress elements again
const progressBar = document.querySelector('.progress-bar');
const progressMessage = document.getElementById('progressMessage');
const scanControls = document.querySelector('.scan-controls');
if (progressBar) progressBar.style.display = 'block';
if (progressMessage) progressMessage.style.display = 'block';
if (scanControls) scanControls.style.display = 'block';
// Hide live discoveries and reset title
const liveSection = document.querySelector('.live-discoveries'); const liveSection = document.querySelector('.live-discoveries');
if (liveSection) { if (liveSection) {
liveSection.style.display = 'none'; liveSection.style.display = 'none';
const title = liveSection.querySelector('h3');
if (title) {
title.textContent = '🔍 Live Discoveries';
}
} }
// Clear form // Clear form
@ -389,6 +460,8 @@ class ReconTool {
document.getElementById('shodanKey').value = ''; document.getElementById('shodanKey').value = '';
document.getElementById('virustotalKey').value = ''; document.getElementById('virustotalKey').value = '';
document.getElementById('maxDepth').value = '2'; document.getElementById('maxDepth').value = '2';
this.debug('Reset to form view');
} }
updateProgress(percentage, message) { updateProgress(percentage, message) {
@ -477,6 +550,6 @@ class ReconTool {
// Initialize the application when DOM is loaded // Initialize the application when DOM is loaded
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
console.log('🌐 DNS Reconnaissance Tool initialized'); console.log('🌐 DNS Reconnaissance Tool initialized with debug mode');
new ReconTool(); new ReconTool();
}); });

View File

@ -211,7 +211,6 @@ header p {
word-wrap: break-word; word-wrap: break-word;
} }
.hostname-list, .ip-list { .hostname-list, .ip-list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@ -245,65 +244,7 @@ header p {
border-bottom: none; border-bottom: none;
} }
/* Responsive design adjustments */ /* Live Discoveries Base Styling */
@media (max-width: 768px) {
.container {
padding: 10px;
}
header h1 {
font-size: 2.2rem;
}
.scan-form, .progress-section, .results-section {
padding: 20px;
}
.btn-primary, .btn-secondary {
width: 100%;
margin-right: 0;
}
.results-controls {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.results-controls button {
flex: 1;
min-width: 120px;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.stat-item {
padding: 6px 8px;
}
.stat-label, .stat-value {
font-size: 0.8rem;
}
.hostname-list, .ip-list {
flex-direction: column;
align-items: flex-start;
}
}
/* Tactical loading spinner */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(199, 199, 199, 0.3);
border-radius: 50%;
border-top-color: #00ff41; /* Night-vision green spinner */
animation: spin 1s linear infinite;
}
.live-discoveries { .live-discoveries {
background: rgba(0, 20, 0, 0.6); background: rgba(0, 20, 0, 0.6);
border: 1px solid #00ff41; border: 1px solid #00ff41;
@ -319,6 +260,44 @@ header p {
letter-spacing: 1px; letter-spacing: 1px;
} }
/* Enhanced styling for live discoveries when shown in results view */
.results-section .live-discoveries {
background: rgba(0, 40, 0, 0.8);
border: 2px solid #00ff41;
border-radius: 4px;
padding: 20px;
margin-bottom: 25px;
box-shadow: 0 0 10px rgba(0, 255, 65, 0.3);
}
.results-section .live-discoveries h3 {
color: #00ff41;
text-shadow: 0 0 3px rgba(0, 255, 65, 0.5);
}
/* Ensure the progress section flows nicely when showing both progress and results */
.progress-section.with-results {
margin-bottom: 0;
border-bottom: none;
}
.results-section.with-live-data {
border-top: 1px solid #444;
padding-top: 20px;
}
/* Better spacing for the combined view */
.progress-section + .results-section {
margin-top: 0;
}
/* Hide specific progress elements while keeping the section visible */
.progress-section .progress-bar.hidden,
.progress-section #progressMessage.hidden,
.progress-section .scan-controls.hidden {
display: none !important;
}
.stats-grid { .stats-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
@ -348,6 +327,16 @@ header p {
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
/* Animation for final stats highlight */
@keyframes finalHighlight {
0% { background-color: #ff9900; }
100% { background-color: transparent; }
}
.stat-value.final {
animation: finalHighlight 2s ease-in-out;
}
.discoveries-list { .discoveries-list {
margin-top: 20px; margin-top: 20px;
} }
@ -374,9 +363,77 @@ header p {
font-size: 0.9rem; font-size: 0.9rem;
} }
/* Tactical loading spinner */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(199, 199, 199, 0.3);
border-radius: 50%;
border-top-color: #00ff41; /* Night-vision green spinner */
animation: spin 1s linear infinite;
}
@keyframes spin { @keyframes spin {
to { transform: rotate(360deg); } to { transform: rotate(360deg); }
} }
/* Responsive design adjustments */
@media (max-width: 768px) {
.container {
padding: 10px;
}
header h1 {
font-size: 2.2rem;
}
.scan-form, .progress-section, .results-section {
padding: 20px;
}
.btn-primary, .btn-secondary {
width: 100%;
margin-right: 0;
}
.results-controls {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.results-controls button {
flex: 1;
min-width: 120px;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.stat-item {
padding: 6px 8px;
}
.stat-label, .stat-value {
font-size: 0.8rem;
}
.hostname-list, .ip-list {
flex-direction: column;
align-items: flex-start;
}
/* Responsive adjustments for the combined view */
.results-section .live-discoveries {
padding: 15px;
margin-bottom: 15px;
}
.results-section .live-discoveries .stats-grid {
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
}