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 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]:

View File

@ -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 = {

View File

@ -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:

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 {
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();
});

View File

@ -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;
}
}