progress
This commit is contained in:
parent
0c9cf00a3b
commit
c105ebbb4b
@ -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,9 +23,52 @@ 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."""
|
||||
now = time.time()
|
||||
@ -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]:
|
||||
|
@ -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 = {
|
||||
|
@ -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:
|
||||
|
107
static/script.js
107
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 {
|
||||
</div>
|
||||
`;
|
||||
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 =>
|
||||
`<span class="discovery-item">${hostname}</span>`
|
||||
).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 =>
|
||||
`<span class="discovery-item">${ip}</span>`
|
||||
).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 `<div class="activity-item">[${time}] ${activity.message}</div>`;
|
||||
}).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();
|
||||
});
|
181
static/style.css
181
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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user