From c105ebbb4b99a00378170ab9fc64de33e274545b Mon Sep 17 00:00:00 2001 From: overcuriousity Date: Tue, 9 Sep 2025 15:42:53 +0200 Subject: [PATCH] progress --- src/certificate_checker.py | 110 ++++++++++++++++++---- src/reconnaissance.py | 45 ++++++--- src/web_app.py | 57 +++++++----- static/script.js | 107 ++++++++++++++++++---- static/style.css | 181 ++++++++++++++++++++++++------------- 5 files changed, 363 insertions(+), 137 deletions(-) diff --git a/src/certificate_checker.py b/src/certificate_checker.py index 66798fb..e546792 100644 --- a/src/certificate_checker.py +++ b/src/certificate_checker.py @@ -5,6 +5,7 @@ import requests import json import time import logging +import socket from datetime import datetime from typing import List, Optional, Set from .data_structures import Certificate @@ -22,8 +23,51 @@ class CertificateChecker: self.config = config self.last_request = 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") + + # 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): """Apply rate limiting for crt.sh.""" @@ -33,7 +77,7 @@ class CertificateChecker: if time_since_last < min_interval: 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) self.last_request = time.time() @@ -41,7 +85,12 @@ class CertificateChecker: def get_certificates(self, domain: str) -> List[Certificate]: """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 = [] @@ -49,9 +98,10 @@ class CertificateChecker: domain_certs = self._query_crt_sh(domain) certificates.extend(domain_certs) - # Also query for wildcard certificates - wildcard_certs = self._query_crt_sh(f"%.{domain}") - certificates.extend(wildcard_certs) + # Also query for wildcard certificates (if the main query succeeded) + if domain_certs or self.connection_failures < self.max_connection_failures: + wildcard_certs = self._query_crt_sh(f"%.{domain}") + certificates.extend(wildcard_certs) # Remove duplicates based on certificate ID unique_certs = {cert.id: cert for cert in certificates} @@ -65,13 +115,15 @@ class CertificateChecker: return final_certs 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 = [] self._rate_limit() 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): try: params = { @@ -111,50 +163,68 @@ class CertificateChecker: certificates.append(certificate) logger.debug(f"βœ… Parsed certificate ID {certificate.id} for {query}") 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: - logger.debug(f"⚠️ Error parsing certificate data: {e}") + logger.debug(f"⚠️ Error parsing certificate data: {e}") 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}") - return certificates # Success, exit retry loop + return certificates except json.JSONDecodeError as e: logger.warning(f"❌ Invalid JSON response from crt.sh for {query}: {e}") if attempt < max_retries - 1: - time.sleep(2 ** attempt) # Exponential backoff + time.sleep(backoff_delays[attempt]) continue 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: - 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: time.sleep(5) # Wait longer for rate limits continue return certificates 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: - time.sleep(2) + time.sleep(backoff_delays[attempt]) continue return certificates - except requests.exceptions.Timeout: - logger.warning(f"⏱️ crt.sh query timeout for {query} (attempt {attempt+1}/{max_retries})") + except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e: + 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: - time.sleep(2) + time.sleep(backoff_delays[attempt]) continue + except requests.exceptions.RequestException as e: logger.warning(f"🌐 crt.sh network error for {query} (attempt {attempt+1}/{max_retries}): {e}") if attempt < max_retries - 1: - time.sleep(2) + time.sleep(backoff_delays[attempt]) continue except Exception as e: logger.error(f"❌ Unexpected error querying crt.sh for {query}: {e}") if attempt < max_retries - 1: - time.sleep(2) + time.sleep(backoff_delays[attempt]) continue # If we get here, all retries failed @@ -187,7 +257,7 @@ class CertificateChecker: except ValueError: pass - logger.debug(f"⚠️ Could not parse date: {date_str}") + logger.debug(f"⚠️ Could not parse date: {date_str}") return None def extract_subdomains_from_certificates(self, certificates: List[Certificate]) -> Set[str]: diff --git a/src/reconnaissance.py b/src/reconnaissance.py index 6471042..6ac93ee 100644 --- a/src/reconnaissance.py +++ b/src/reconnaissance.py @@ -34,23 +34,31 @@ class ReconnaissanceEngine: self.shodan_client = ShodanClient(config.shodan_key, config) logger.info("βœ… Shodan client initialized") 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 if config.virustotal_key: self.virustotal_client = VirusTotalClient(config.virustotal_key, config) logger.info("βœ… VirusTotal client initialized") else: - logger.info("⚠️ VirusTotal API key not provided, skipping VirusTotal integration") + logger.info("⚠️ VirusTotal API key not provided, skipping VirusTotal integration") # Progress tracking self.progress_callback = None self._lock = threading.Lock() + + # Shared data object for live updates + self.shared_data = None def set_progress_callback(self, callback): """Set callback for progress updates.""" 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): """Update progress if callback is set.""" 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: """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() logger.info(f"πŸš€ Starting reconnaissance for target: {target}") @@ -100,7 +115,7 @@ class ReconnaissanceEngine: finally: self.data.end_time = datetime.now() 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 @@ -108,7 +123,7 @@ class ReconnaissanceEngine: """Expand hostname to all possible TLDs.""" logger.info(f"🌐 Fetching TLD list for hostname expansion") tlds = self.tld_fetcher.get_tlds() - logger.info(f"πŸ“ Testing against {len(tlds)} TLDs") + logger.info(f"πŸ” Testing against {len(tlds)} TLDs") targets = set() tested_count = 0 @@ -182,7 +197,7 @@ class ReconnaissanceEngine: self.data.add_ip_address(record.value) # Get certificates - logger.debug(f"πŸ” Checking certificates for {hostname}") + logger.debug(f"πŸ” Checking certificates for {hostname}") certificates = self.cert_checker.get_certificates(hostname) if certificates: self.data.certificates[hostname] = certificates @@ -234,7 +249,7 @@ class ReconnaissanceEngine: # Shodan lookups 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 for ip in self.data.ip_addresses: @@ -248,16 +263,16 @@ class ReconnaissanceEngine: else: logger.debug(f"❌ No Shodan data for {ip}") 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") else: - logger.info("⚠️ Skipping Shodan lookups (no API key)") + logger.info("⚠️ Skipping Shodan lookups (no API key)") # VirusTotal lookups if self.virustotal_client: 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 # Check IPs @@ -268,11 +283,11 @@ class ReconnaissanceEngine: if result: self.data.add_virustotal_result(ip, result) 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: logger.debug(f"❌ No VirusTotal data for {ip}") 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 for hostname in self.data.hostnames: @@ -282,15 +297,15 @@ class ReconnaissanceEngine: if result: self.data.add_virustotal_result(hostname, result) 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: logger.debug(f"❌ No VirusTotal data for {hostname}") 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") else: - logger.info("⚠️ Skipping VirusTotal lookups (no API key)") + logger.info("⚠️ Skipping VirusTotal lookups (no API key)") # Final external lookup summary ext_stats = { diff --git a/src/web_app.py b/src/web_app.py index f8c4d99..29aa203 100644 --- a/src/web_app.py +++ b/src/web_app.py @@ -8,6 +8,7 @@ import logging from .config import Config from .reconnaissance import ReconnaissanceEngine from .report_generator import ReportGenerator +from .data_structures import ReconData # Set up logging for this module logger = logging.getLogger(__name__) @@ -46,20 +47,23 @@ def create_app(config: Config): ) if not target: - logger.warning("⚠️ Scan request missing target") + logger.warning("⚠️ Scan request missing target") return jsonify({'error': 'Target is required'}), 400 # Generate scan ID scan_id = f"{target}_{int(time.time())}" 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: active_scans[scan_id] = { 'status': 'starting', 'progress': 0, 'message': 'Initializing...', - 'data': None, + 'data': shared_data, # Share the data object from the start! 'error': None, 'live_stats': { 'hostnames': 0, @@ -75,7 +79,7 @@ def create_app(config: Config): # Start reconnaissance in background thread thread = threading.Thread( target=run_reconnaissance_background, - args=(scan_id, target, scan_config) + args=(scan_id, target, scan_config, shared_data) ) thread.daemon = True thread.start() @@ -118,7 +122,7 @@ def create_app(config: Config): report_gen = ReportGenerator(scan_data['data']) 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() }) except Exception as e: @@ -134,37 +138,41 @@ def create_app(config: Config): 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({ 'hostnames': [], '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({ - 'hostnames': sorted(list(scan_data['data'].hostnames)), - 'ip_addresses': sorted(list(scan_data['data'].ip_addresses)), - 'stats': scan_data['data'].get_stats(), + 'hostnames': sorted(list(data_obj.hostnames)), + 'ip_addresses': sorted(list(data_obj.ip_addresses)), + 'stats': data_obj.get_stats(), 'latest_discoveries': scan_data.get('latest_discoveries', []) }) return app -def run_reconnaissance_background(scan_id: str, target: str, config: Config): - """Run reconnaissance in background thread.""" +def run_reconnaissance_background(scan_id: str, target: str, config: Config, shared_data: ReconData): + """Run reconnaissance in background thread with shared data object.""" def update_progress(message: str, percentage: int = None): - """Update scan progress.""" + """Update scan progress and live statistics.""" with scan_lock: if scan_id in active_scans: active_scans[scan_id]['message'] = message if percentage is not None: active_scans[scan_id]['progress'] = percentage - # Update live stats if we have data - if active_scans[scan_id]['data']: - active_scans[scan_id]['live_stats'] = active_scans[scan_id]['data'].get_stats() + # Update live stats from the shared data object + if shared_data: + active_scans[scan_id]['live_stats'] = shared_data.get_stats() # Add to latest discoveries (keep last 10) 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.set_progress_callback(update_progress) + # IMPORTANT: Pass the shared data object to the engine + engine.set_shared_data(shared_data) + # Update status with scan_lock: active_scans[scan_id]['status'] = 'running' logger.info(f"πŸš€ Starting reconnaissance for: {target}") - # Run reconnaissance - data = engine.run_reconnaissance(target) + # Run reconnaissance - this will populate the shared_data object incrementally + final_data = engine.run_reconnaissance(target) 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: active_scans[scan_id]['status'] = 'completed' active_scans[scan_id]['progress'] = 100 active_scans[scan_id]['message'] = 'Reconnaissance completed' - active_scans[scan_id]['data'] = data - active_scans[scan_id]['live_stats'] = data.get_stats() + active_scans[scan_id]['data'] = final_data # This should be the same as shared_data + active_scans[scan_id]['live_stats'] = final_data.get_stats() # Log final statistics - final_stats = data.get_stats() + final_stats = final_data.get_stats() logger.info(f"πŸ“Š Final stats for {scan_id}: {final_stats}") except Exception as e: diff --git a/static/script.js b/static/script.js index f9cccea..343a5d1 100644 --- a/static/script.js +++ b/static/script.js @@ -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 { constructor() { @@ -6,9 +6,20 @@ class ReconTool { this.pollInterval = null; this.liveDataInterval = null; this.currentReport = null; + this.debugMode = true; // Enable debug logging this.init(); } + debug(message, data = null) { + if (this.debugMode) { + if (data) { + console.log(`πŸ” DEBUG: ${message}`, data); + } else { + console.log(`πŸ” DEBUG: ${message}`); + } + } + } + init() { this.bindEvents(); this.setupRealtimeElements(); @@ -67,6 +78,7 @@ class ReconTool { `; progressSection.appendChild(liveDiv); + this.debug("Live discoveries container created"); } } @@ -127,7 +139,7 @@ class ReconTool { this.showProgressSection(); 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', { method: 'POST', @@ -148,7 +160,7 @@ class ReconTool { } this.currentScanId = result.scan_id; - console.log('βœ… Scan started with ID:', this.currentScanId); + this.debug('Scan started with ID:', this.currentScanId); this.startPolling(); this.startLiveDataPolling(); @@ -160,6 +172,7 @@ class ReconTool { } startPolling() { + this.debug('Starting status polling...'); // Poll every 2 seconds for status updates this.pollInterval = setInterval(() => { this.checkScanStatus(); @@ -170,6 +183,7 @@ class ReconTool { } startLiveDataPolling() { + this.debug('Starting live data polling...'); // Poll every 3 seconds for live data updates this.liveDataInterval = setInterval(() => { this.updateLiveData(); @@ -179,6 +193,9 @@ class ReconTool { const liveSection = document.querySelector('.live-discoveries'); if (liveSection) { liveSection.style.display = 'block'; + this.debug('Live discoveries section made visible'); + } else { + this.debug('ERROR: Live discoveries section not found!'); } // Also update immediately @@ -208,12 +225,13 @@ class ReconTool { // Update live stats if (status.live_stats) { + this.debug('Received live stats:', status.live_stats); this.updateLiveStats(status.live_stats); } // Check if completed if (status.status === 'completed') { - console.log('βœ… Scan completed'); + this.debug('Scan completed, loading report...'); this.stopPolling(); await this.loadScanReport(); } else if (status.status === 'error') { @@ -233,29 +251,37 @@ class ReconTool { return; } + this.debug(`Fetching live data for scan: ${this.currentScanId}`); + try { const response = await fetch(`/api/scan/${this.currentScanId}/live-data`); if (!response.ok) { + this.debug(`Live data request failed: HTTP ${response.status}`); return; // Silently fail for live data } const data = await response.json(); if (data.error) { + this.debug('Live data error:', data.error); return; // Silently fail for live data } + this.debug('Received live data:', data); + // Update live discoveries this.updateLiveDiscoveries(data); } catch (error) { // Silently fail for live data updates - console.debug('Live data update failed:', error); + this.debug('Live data update failed:', error); } } updateLiveStats(stats) { + this.debug('Updating live stats:', stats); + // Update the live statistics counters const statElements = { 'liveHostnames': stats.hostnames || 0, @@ -269,54 +295,69 @@ class ReconTool { Object.entries(statElements).forEach(([elementId, value]) => { const element = document.getElementById(elementId); if (element) { + const currentValue = element.textContent; element.textContent = value; - // Add a brief highlight effect when value changes - if (element.textContent !== value.toString()) { + if (currentValue !== value.toString()) { + this.debug(`Updated ${elementId}: ${currentValue} -> ${value}`); + // Add a brief highlight effect when value changes element.style.backgroundColor = '#ff9900'; setTimeout(() => { element.style.backgroundColor = ''; }, 1000); } + } else { + this.debug(`ERROR: Element ${elementId} not found!`); } }); } updateLiveDiscoveries(data) { + this.debug('Updating live discoveries with data:', data); + // Update hostnames list const hostnameList = document.querySelector('#recentHostnames .hostname-list'); - if (hostnameList && data.hostnames) { + if (hostnameList && data.hostnames && data.hostnames.length > 0) { // Show last 10 hostnames const recentHostnames = data.hostnames.slice(-10); hostnameList.innerHTML = recentHostnames.map(hostname => `${hostname}` ).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 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 const recentIPs = data.ip_addresses.slice(-10); ipList.innerHTML = recentIPs.map(ip => `${ip}` ).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 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 activityList.innerHTML = activities.map(activity => { const time = new Date(activity.timestamp * 1000).toLocaleTimeString(); return `
[${time}] ${activity.message}
`; }).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() { try { - console.log('πŸ“„ Loading scan report...'); + this.debug('Loading scan report...'); const response = await fetch(`/api/scan/${this.currentScanId}/report`); if (!response.ok) { @@ -330,7 +371,7 @@ class ReconTool { } this.currentReport = report; - console.log('βœ… Report loaded successfully'); + this.debug('Report loaded successfully'); this.showResultsSection(); this.showReport('text'); // Default to text view @@ -341,6 +382,7 @@ class ReconTool { } stopPolling() { + this.debug('Stopping polling intervals...'); if (this.pollInterval) { clearInterval(this.pollInterval); this.pollInterval = null; @@ -355,18 +397,34 @@ class ReconTool { document.getElementById('scanForm').style.display = 'none'; document.getElementById('progressSection').style.display = 'block'; document.getElementById('resultsSection').style.display = 'none'; + this.debug('Showing progress section'); } showResultsSection() { 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'; - // Hide live discoveries in results section + // Change the title to show it's the final summary const liveSection = document.querySelector('.live-discoveries'); 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() { @@ -378,10 +436,23 @@ class ReconTool { document.getElementById('progressSection').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'); if (liveSection) { liveSection.style.display = 'none'; + const title = liveSection.querySelector('h3'); + if (title) { + title.textContent = 'πŸ” Live Discoveries'; + } } // Clear form @@ -389,6 +460,8 @@ class ReconTool { document.getElementById('shodanKey').value = ''; document.getElementById('virustotalKey').value = ''; document.getElementById('maxDepth').value = '2'; + + this.debug('Reset to form view'); } updateProgress(percentage, message) { @@ -477,6 +550,6 @@ class ReconTool { // Initialize the application when DOM is loaded document.addEventListener('DOMContentLoaded', () => { - console.log('🌐 DNS Reconnaissance Tool initialized'); + console.log('🌐 DNS Reconnaissance Tool initialized with debug mode'); new ReconTool(); }); \ No newline at end of file diff --git a/static/style.css b/static/style.css index 14a06aa..bd9b69a 100644 --- a/static/style.css +++ b/static/style.css @@ -211,7 +211,6 @@ header p { word-wrap: break-word; } - .hostname-list, .ip-list { display: flex; flex-wrap: wrap; @@ -245,65 +244,7 @@ header p { border-bottom: none; } -/* 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; - } -} - -/* 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 Base Styling */ .live-discoveries { background: rgba(0, 20, 0, 0.6); border: 1px solid #00ff41; @@ -319,6 +260,44 @@ header p { 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 { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); @@ -348,6 +327,16 @@ header p { 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 { margin-top: 20px; } @@ -374,9 +363,77 @@ header p { 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 { 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; + } } \ No newline at end of file