progress
This commit is contained in:
parent
0c9cf00a3b
commit
c105ebbb4b
@ -5,6 +5,7 @@ import requests
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
import socket
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Optional, Set
|
from typing import List, Optional, Set
|
||||||
from .data_structures import Certificate
|
from .data_structures import Certificate
|
||||||
@ -22,9 +23,52 @@ class CertificateChecker:
|
|||||||
self.config = config
|
self.config = config
|
||||||
self.last_request = 0
|
self.last_request = 0
|
||||||
self.query_count = 0
|
self.query_count = 0
|
||||||
|
self.connection_failures = 0
|
||||||
|
self.max_connection_failures = 3 # Stop trying after 3 consecutive failures
|
||||||
|
|
||||||
logger.info("🔐 Certificate checker initialized")
|
logger.info("🔐 Certificate checker initialized")
|
||||||
|
|
||||||
|
# Test connectivity to crt.sh on initialization
|
||||||
|
self._test_connectivity()
|
||||||
|
|
||||||
|
def _test_connectivity(self):
|
||||||
|
"""Test if we can reach crt.sh."""
|
||||||
|
try:
|
||||||
|
logger.info("🔗 Testing connectivity to crt.sh...")
|
||||||
|
|
||||||
|
# First test DNS resolution
|
||||||
|
try:
|
||||||
|
socket.gethostbyname('crt.sh')
|
||||||
|
logger.debug("✅ DNS resolution for crt.sh successful")
|
||||||
|
except socket.gaierror as e:
|
||||||
|
logger.warning(f"⚠️ DNS resolution failed for crt.sh: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Test HTTP connection with a simple request
|
||||||
|
response = requests.get(
|
||||||
|
self.CRT_SH_URL,
|
||||||
|
params={'q': 'example.com', 'output': 'json'},
|
||||||
|
timeout=10,
|
||||||
|
headers={'User-Agent': 'DNS-Recon-Tool/1.0'}
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code in [200, 404]: # 404 is also acceptable (no results)
|
||||||
|
logger.info("✅ crt.sh connectivity test successful")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.warning(f"⚠️ crt.sh returned status {response.status_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.exceptions.ConnectionError as e:
|
||||||
|
logger.warning(f"⚠️ Cannot reach crt.sh: {e}")
|
||||||
|
return False
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
logger.warning("⚠️ crt.sh connectivity test timed out")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"⚠️ Unexpected error testing crt.sh connectivity: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def _rate_limit(self):
|
def _rate_limit(self):
|
||||||
"""Apply rate limiting for crt.sh."""
|
"""Apply rate limiting for crt.sh."""
|
||||||
now = time.time()
|
now = time.time()
|
||||||
@ -33,7 +77,7 @@ class CertificateChecker:
|
|||||||
|
|
||||||
if time_since_last < min_interval:
|
if time_since_last < min_interval:
|
||||||
sleep_time = min_interval - time_since_last
|
sleep_time = min_interval - time_since_last
|
||||||
logger.debug(f"⏸️ crt.sh rate limiting: sleeping for {sleep_time:.2f}s")
|
logger.debug(f"⏸️ crt.sh rate limiting: sleeping for {sleep_time:.2f}s")
|
||||||
time.sleep(sleep_time)
|
time.sleep(sleep_time)
|
||||||
|
|
||||||
self.last_request = time.time()
|
self.last_request = time.time()
|
||||||
@ -41,7 +85,12 @@ class CertificateChecker:
|
|||||||
|
|
||||||
def get_certificates(self, domain: str) -> List[Certificate]:
|
def get_certificates(self, domain: str) -> List[Certificate]:
|
||||||
"""Get certificates for a domain from crt.sh."""
|
"""Get certificates for a domain from crt.sh."""
|
||||||
logger.debug(f"🔐 Getting certificates for domain: {domain}")
|
logger.debug(f"🔍 Getting certificates for domain: {domain}")
|
||||||
|
|
||||||
|
# Skip if we've had too many connection failures
|
||||||
|
if self.connection_failures >= self.max_connection_failures:
|
||||||
|
logger.warning(f"⚠️ Skipping certificate lookup for {domain} due to repeated connection failures")
|
||||||
|
return []
|
||||||
|
|
||||||
certificates = []
|
certificates = []
|
||||||
|
|
||||||
@ -49,9 +98,10 @@ class CertificateChecker:
|
|||||||
domain_certs = self._query_crt_sh(domain)
|
domain_certs = self._query_crt_sh(domain)
|
||||||
certificates.extend(domain_certs)
|
certificates.extend(domain_certs)
|
||||||
|
|
||||||
# Also query for wildcard certificates
|
# Also query for wildcard certificates (if the main query succeeded)
|
||||||
wildcard_certs = self._query_crt_sh(f"%.{domain}")
|
if domain_certs or self.connection_failures < self.max_connection_failures:
|
||||||
certificates.extend(wildcard_certs)
|
wildcard_certs = self._query_crt_sh(f"%.{domain}")
|
||||||
|
certificates.extend(wildcard_certs)
|
||||||
|
|
||||||
# Remove duplicates based on certificate ID
|
# Remove duplicates based on certificate ID
|
||||||
unique_certs = {cert.id: cert for cert in certificates}
|
unique_certs = {cert.id: cert for cert in certificates}
|
||||||
@ -65,13 +115,15 @@ class CertificateChecker:
|
|||||||
return final_certs
|
return final_certs
|
||||||
|
|
||||||
def _query_crt_sh(self, query: str) -> List[Certificate]:
|
def _query_crt_sh(self, query: str) -> List[Certificate]:
|
||||||
"""Query crt.sh API with retry logic."""
|
"""Query crt.sh API with retry logic and better error handling."""
|
||||||
certificates = []
|
certificates = []
|
||||||
self._rate_limit()
|
self._rate_limit()
|
||||||
|
|
||||||
logger.debug(f"📡 Querying crt.sh for: {query}")
|
logger.debug(f"📡 Querying crt.sh for: {query}")
|
||||||
|
|
||||||
max_retries = 3
|
max_retries = 2 # Reduced retries for faster failure
|
||||||
|
backoff_delays = [1, 3] # Shorter delays
|
||||||
|
|
||||||
for attempt in range(max_retries):
|
for attempt in range(max_retries):
|
||||||
try:
|
try:
|
||||||
params = {
|
params = {
|
||||||
@ -111,50 +163,68 @@ class CertificateChecker:
|
|||||||
certificates.append(certificate)
|
certificates.append(certificate)
|
||||||
logger.debug(f"✅ Parsed certificate ID {certificate.id} for {query}")
|
logger.debug(f"✅ Parsed certificate ID {certificate.id} for {query}")
|
||||||
else:
|
else:
|
||||||
logger.debug(f"⚠️ Skipped certificate with invalid dates: {cert_data.get('id')}")
|
logger.debug(f"⚠️ Skipped certificate with invalid dates: {cert_data.get('id')}")
|
||||||
|
|
||||||
except (ValueError, TypeError, KeyError) as e:
|
except (ValueError, TypeError, KeyError) as e:
|
||||||
logger.debug(f"⚠️ Error parsing certificate data: {e}")
|
logger.debug(f"⚠️ Error parsing certificate data: {e}")
|
||||||
continue # Skip malformed certificate data
|
continue # Skip malformed certificate data
|
||||||
|
|
||||||
|
# Success! Reset connection failure counter
|
||||||
|
self.connection_failures = 0
|
||||||
logger.info(f"✅ Successfully processed {len(certificates)} certificates from crt.sh for {query}")
|
logger.info(f"✅ Successfully processed {len(certificates)} certificates from crt.sh for {query}")
|
||||||
return certificates # Success, exit retry loop
|
return certificates
|
||||||
|
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
logger.warning(f"❌ Invalid JSON response from crt.sh for {query}: {e}")
|
logger.warning(f"❌ Invalid JSON response from crt.sh for {query}: {e}")
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
time.sleep(2 ** attempt) # Exponential backoff
|
time.sleep(backoff_delays[attempt])
|
||||||
continue
|
continue
|
||||||
return certificates
|
return certificates
|
||||||
|
|
||||||
|
elif response.status_code == 404:
|
||||||
|
# 404 is normal - no certificates found
|
||||||
|
logger.debug(f"ℹ️ No certificates found for {query} (404)")
|
||||||
|
self.connection_failures = 0 # Reset counter for successful connection
|
||||||
|
return certificates
|
||||||
|
|
||||||
elif response.status_code == 429:
|
elif response.status_code == 429:
|
||||||
logger.warning(f"⚠️ crt.sh rate limit exceeded for {query}")
|
logger.warning(f"⚠️ crt.sh rate limit exceeded for {query}")
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
time.sleep(5) # Wait longer for rate limits
|
time.sleep(5) # Wait longer for rate limits
|
||||||
continue
|
continue
|
||||||
return certificates
|
return certificates
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.warning(f"⚠️ crt.sh HTTP error for {query}: {response.status_code}")
|
logger.warning(f"⚠️ crt.sh HTTP error for {query}: {response.status_code}")
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
time.sleep(2)
|
time.sleep(backoff_delays[attempt])
|
||||||
continue
|
continue
|
||||||
return certificates
|
return certificates
|
||||||
|
|
||||||
except requests.exceptions.Timeout:
|
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
|
||||||
logger.warning(f"⏱️ crt.sh query timeout for {query} (attempt {attempt+1}/{max_retries})")
|
error_type = "Connection Error" if isinstance(e, requests.exceptions.ConnectionError) else "Timeout"
|
||||||
|
logger.warning(f"🌐 crt.sh {error_type} for {query} (attempt {attempt+1}/{max_retries}): {e}")
|
||||||
|
|
||||||
|
# Track connection failures
|
||||||
|
if isinstance(e, requests.exceptions.ConnectionError):
|
||||||
|
self.connection_failures += 1
|
||||||
|
if self.connection_failures >= self.max_connection_failures:
|
||||||
|
logger.error(f"❌ Too many connection failures to crt.sh. Disabling certificate lookups.")
|
||||||
|
return certificates
|
||||||
|
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
time.sleep(2)
|
time.sleep(backoff_delays[attempt])
|
||||||
continue
|
continue
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
logger.warning(f"🌐 crt.sh network error for {query} (attempt {attempt+1}/{max_retries}): {e}")
|
logger.warning(f"🌐 crt.sh network error for {query} (attempt {attempt+1}/{max_retries}): {e}")
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
time.sleep(2)
|
time.sleep(backoff_delays[attempt])
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Unexpected error querying crt.sh for {query}: {e}")
|
logger.error(f"❌ Unexpected error querying crt.sh for {query}: {e}")
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
time.sleep(2)
|
time.sleep(backoff_delays[attempt])
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# If we get here, all retries failed
|
# If we get here, all retries failed
|
||||||
@ -187,7 +257,7 @@ class CertificateChecker:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
logger.debug(f"⚠️ Could not parse date: {date_str}")
|
logger.debug(f"⚠️ Could not parse date: {date_str}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def extract_subdomains_from_certificates(self, certificates: List[Certificate]) -> Set[str]:
|
def extract_subdomains_from_certificates(self, certificates: List[Certificate]) -> Set[str]:
|
||||||
|
@ -34,23 +34,31 @@ class ReconnaissanceEngine:
|
|||||||
self.shodan_client = ShodanClient(config.shodan_key, config)
|
self.shodan_client = ShodanClient(config.shodan_key, config)
|
||||||
logger.info("✅ Shodan client initialized")
|
logger.info("✅ Shodan client initialized")
|
||||||
else:
|
else:
|
||||||
logger.info("⚠️ Shodan API key not provided, skipping Shodan integration")
|
logger.info("⚠️ Shodan API key not provided, skipping Shodan integration")
|
||||||
|
|
||||||
self.virustotal_client = None
|
self.virustotal_client = None
|
||||||
if config.virustotal_key:
|
if config.virustotal_key:
|
||||||
self.virustotal_client = VirusTotalClient(config.virustotal_key, config)
|
self.virustotal_client = VirusTotalClient(config.virustotal_key, config)
|
||||||
logger.info("✅ VirusTotal client initialized")
|
logger.info("✅ VirusTotal client initialized")
|
||||||
else:
|
else:
|
||||||
logger.info("⚠️ VirusTotal API key not provided, skipping VirusTotal integration")
|
logger.info("⚠️ VirusTotal API key not provided, skipping VirusTotal integration")
|
||||||
|
|
||||||
# Progress tracking
|
# Progress tracking
|
||||||
self.progress_callback = None
|
self.progress_callback = None
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
|
# Shared data object for live updates
|
||||||
|
self.shared_data = None
|
||||||
|
|
||||||
def set_progress_callback(self, callback):
|
def set_progress_callback(self, callback):
|
||||||
"""Set callback for progress updates."""
|
"""Set callback for progress updates."""
|
||||||
self.progress_callback = callback
|
self.progress_callback = callback
|
||||||
|
|
||||||
|
def set_shared_data(self, shared_data: ReconData):
|
||||||
|
"""Set shared data object for live updates during web interface usage."""
|
||||||
|
self.shared_data = shared_data
|
||||||
|
logger.info("📊 Using shared data object for live updates")
|
||||||
|
|
||||||
def _update_progress(self, message: str, percentage: int = None):
|
def _update_progress(self, message: str, percentage: int = None):
|
||||||
"""Update progress if callback is set."""
|
"""Update progress if callback is set."""
|
||||||
logger.info(f"Progress: {message} ({percentage}%)" if percentage else f"Progress: {message}")
|
logger.info(f"Progress: {message} ({percentage}%)" if percentage else f"Progress: {message}")
|
||||||
@ -59,7 +67,14 @@ class ReconnaissanceEngine:
|
|||||||
|
|
||||||
def run_reconnaissance(self, target: str) -> ReconData:
|
def run_reconnaissance(self, target: str) -> ReconData:
|
||||||
"""Run full reconnaissance on target."""
|
"""Run full reconnaissance on target."""
|
||||||
self.data = ReconData()
|
# Use shared data object if available, otherwise create new one
|
||||||
|
if self.shared_data is not None:
|
||||||
|
self.data = self.shared_data
|
||||||
|
logger.info("📊 Using shared data object for reconnaissance")
|
||||||
|
else:
|
||||||
|
self.data = ReconData()
|
||||||
|
logger.info("📊 Created new data object for reconnaissance")
|
||||||
|
|
||||||
self.data.start_time = datetime.now()
|
self.data.start_time = datetime.now()
|
||||||
|
|
||||||
logger.info(f"🚀 Starting reconnaissance for target: {target}")
|
logger.info(f"🚀 Starting reconnaissance for target: {target}")
|
||||||
@ -100,7 +115,7 @@ class ReconnaissanceEngine:
|
|||||||
finally:
|
finally:
|
||||||
self.data.end_time = datetime.now()
|
self.data.end_time = datetime.now()
|
||||||
duration = self.data.end_time - self.data.start_time
|
duration = self.data.end_time - self.data.start_time
|
||||||
logger.info(f"⏱️ Total reconnaissance time: {duration}")
|
logger.info(f"⏱️ Total reconnaissance time: {duration}")
|
||||||
|
|
||||||
return self.data
|
return self.data
|
||||||
|
|
||||||
@ -108,7 +123,7 @@ class ReconnaissanceEngine:
|
|||||||
"""Expand hostname to all possible TLDs."""
|
"""Expand hostname to all possible TLDs."""
|
||||||
logger.info(f"🌐 Fetching TLD list for hostname expansion")
|
logger.info(f"🌐 Fetching TLD list for hostname expansion")
|
||||||
tlds = self.tld_fetcher.get_tlds()
|
tlds = self.tld_fetcher.get_tlds()
|
||||||
logger.info(f"📝 Testing against {len(tlds)} TLDs")
|
logger.info(f"🔍 Testing against {len(tlds)} TLDs")
|
||||||
|
|
||||||
targets = set()
|
targets = set()
|
||||||
tested_count = 0
|
tested_count = 0
|
||||||
@ -182,7 +197,7 @@ class ReconnaissanceEngine:
|
|||||||
self.data.add_ip_address(record.value)
|
self.data.add_ip_address(record.value)
|
||||||
|
|
||||||
# Get certificates
|
# Get certificates
|
||||||
logger.debug(f"🔐 Checking certificates for {hostname}")
|
logger.debug(f"🔍 Checking certificates for {hostname}")
|
||||||
certificates = self.cert_checker.get_certificates(hostname)
|
certificates = self.cert_checker.get_certificates(hostname)
|
||||||
if certificates:
|
if certificates:
|
||||||
self.data.certificates[hostname] = certificates
|
self.data.certificates[hostname] = certificates
|
||||||
@ -234,7 +249,7 @@ class ReconnaissanceEngine:
|
|||||||
|
|
||||||
# Shodan lookups
|
# Shodan lookups
|
||||||
if self.shodan_client:
|
if self.shodan_client:
|
||||||
logger.info(f"🕵️ Starting Shodan lookups for {len(self.data.ip_addresses)} IPs")
|
logger.info(f"🕵️ Starting Shodan lookups for {len(self.data.ip_addresses)} IPs")
|
||||||
shodan_success_count = 0
|
shodan_success_count = 0
|
||||||
|
|
||||||
for ip in self.data.ip_addresses:
|
for ip in self.data.ip_addresses:
|
||||||
@ -248,16 +263,16 @@ class ReconnaissanceEngine:
|
|||||||
else:
|
else:
|
||||||
logger.debug(f"❌ No Shodan data for {ip}")
|
logger.debug(f"❌ No Shodan data for {ip}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"⚠️ Error querying Shodan for {ip}: {e}")
|
logger.warning(f"⚠️ Error querying Shodan for {ip}: {e}")
|
||||||
|
|
||||||
logger.info(f"✅ Shodan lookups complete: {shodan_success_count}/{len(self.data.ip_addresses)} successful")
|
logger.info(f"✅ Shodan lookups complete: {shodan_success_count}/{len(self.data.ip_addresses)} successful")
|
||||||
else:
|
else:
|
||||||
logger.info("⚠️ Skipping Shodan lookups (no API key)")
|
logger.info("⚠️ Skipping Shodan lookups (no API key)")
|
||||||
|
|
||||||
# VirusTotal lookups
|
# VirusTotal lookups
|
||||||
if self.virustotal_client:
|
if self.virustotal_client:
|
||||||
total_resources = len(self.data.ip_addresses) + len(self.data.hostnames)
|
total_resources = len(self.data.ip_addresses) + len(self.data.hostnames)
|
||||||
logger.info(f"🛡️ Starting VirusTotal lookups for {total_resources} resources")
|
logger.info(f"🛡️ Starting VirusTotal lookups for {total_resources} resources")
|
||||||
vt_success_count = 0
|
vt_success_count = 0
|
||||||
|
|
||||||
# Check IPs
|
# Check IPs
|
||||||
@ -268,11 +283,11 @@ class ReconnaissanceEngine:
|
|||||||
if result:
|
if result:
|
||||||
self.data.add_virustotal_result(ip, result)
|
self.data.add_virustotal_result(ip, result)
|
||||||
vt_success_count += 1
|
vt_success_count += 1
|
||||||
logger.info(f"🛡️ VirusTotal result for {ip}: {result.positives}/{result.total} detections")
|
logger.info(f"🛡️ VirusTotal result for {ip}: {result.positives}/{result.total} detections")
|
||||||
else:
|
else:
|
||||||
logger.debug(f"❌ No VirusTotal data for {ip}")
|
logger.debug(f"❌ No VirusTotal data for {ip}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"⚠️ Error querying VirusTotal for IP {ip}: {e}")
|
logger.warning(f"⚠️ Error querying VirusTotal for IP {ip}: {e}")
|
||||||
|
|
||||||
# Check domains
|
# Check domains
|
||||||
for hostname in self.data.hostnames:
|
for hostname in self.data.hostnames:
|
||||||
@ -282,15 +297,15 @@ class ReconnaissanceEngine:
|
|||||||
if result:
|
if result:
|
||||||
self.data.add_virustotal_result(hostname, result)
|
self.data.add_virustotal_result(hostname, result)
|
||||||
vt_success_count += 1
|
vt_success_count += 1
|
||||||
logger.info(f"🛡️ VirusTotal result for {hostname}: {result.positives}/{result.total} detections")
|
logger.info(f"🛡️ VirusTotal result for {hostname}: {result.positives}/{result.total} detections")
|
||||||
else:
|
else:
|
||||||
logger.debug(f"❌ No VirusTotal data for {hostname}")
|
logger.debug(f"❌ No VirusTotal data for {hostname}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"⚠️ Error querying VirusTotal for domain {hostname}: {e}")
|
logger.warning(f"⚠️ Error querying VirusTotal for domain {hostname}: {e}")
|
||||||
|
|
||||||
logger.info(f"✅ VirusTotal lookups complete: {vt_success_count}/{total_resources} successful")
|
logger.info(f"✅ VirusTotal lookups complete: {vt_success_count}/{total_resources} successful")
|
||||||
else:
|
else:
|
||||||
logger.info("⚠️ Skipping VirusTotal lookups (no API key)")
|
logger.info("⚠️ Skipping VirusTotal lookups (no API key)")
|
||||||
|
|
||||||
# Final external lookup summary
|
# Final external lookup summary
|
||||||
ext_stats = {
|
ext_stats = {
|
||||||
|
@ -8,6 +8,7 @@ import logging
|
|||||||
from .config import Config
|
from .config import Config
|
||||||
from .reconnaissance import ReconnaissanceEngine
|
from .reconnaissance import ReconnaissanceEngine
|
||||||
from .report_generator import ReportGenerator
|
from .report_generator import ReportGenerator
|
||||||
|
from .data_structures import ReconData
|
||||||
|
|
||||||
# Set up logging for this module
|
# Set up logging for this module
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -46,20 +47,23 @@ def create_app(config: Config):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not target:
|
if not target:
|
||||||
logger.warning("⚠️ Scan request missing target")
|
logger.warning("⚠️ Scan request missing target")
|
||||||
return jsonify({'error': 'Target is required'}), 400
|
return jsonify({'error': 'Target is required'}), 400
|
||||||
|
|
||||||
# Generate scan ID
|
# Generate scan ID
|
||||||
scan_id = f"{target}_{int(time.time())}"
|
scan_id = f"{target}_{int(time.time())}"
|
||||||
logger.info(f"🚀 Starting new scan: {scan_id} for target: {target}")
|
logger.info(f"🚀 Starting new scan: {scan_id} for target: {target}")
|
||||||
|
|
||||||
# Initialize scan data
|
# Create shared ReconData object for live updates
|
||||||
|
shared_data = ReconData()
|
||||||
|
|
||||||
|
# Initialize scan data with the shared data object
|
||||||
with scan_lock:
|
with scan_lock:
|
||||||
active_scans[scan_id] = {
|
active_scans[scan_id] = {
|
||||||
'status': 'starting',
|
'status': 'starting',
|
||||||
'progress': 0,
|
'progress': 0,
|
||||||
'message': 'Initializing...',
|
'message': 'Initializing...',
|
||||||
'data': None,
|
'data': shared_data, # Share the data object from the start!
|
||||||
'error': None,
|
'error': None,
|
||||||
'live_stats': {
|
'live_stats': {
|
||||||
'hostnames': 0,
|
'hostnames': 0,
|
||||||
@ -75,7 +79,7 @@ def create_app(config: Config):
|
|||||||
# Start reconnaissance in background thread
|
# Start reconnaissance in background thread
|
||||||
thread = threading.Thread(
|
thread = threading.Thread(
|
||||||
target=run_reconnaissance_background,
|
target=run_reconnaissance_background,
|
||||||
args=(scan_id, target, scan_config)
|
args=(scan_id, target, scan_config, shared_data)
|
||||||
)
|
)
|
||||||
thread.daemon = True
|
thread.daemon = True
|
||||||
thread.start()
|
thread.start()
|
||||||
@ -118,7 +122,7 @@ def create_app(config: Config):
|
|||||||
report_gen = ReportGenerator(scan_data['data'])
|
report_gen = ReportGenerator(scan_data['data'])
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'json_report': scan_data['data'].to_json(), # This should now work properly
|
'json_report': scan_data['data'].to_json(),
|
||||||
'text_report': report_gen.generate_text_report()
|
'text_report': report_gen.generate_text_report()
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -134,37 +138,41 @@ def create_app(config: Config):
|
|||||||
|
|
||||||
scan_data = active_scans[scan_id]
|
scan_data = active_scans[scan_id]
|
||||||
|
|
||||||
if not scan_data['data']:
|
# Now we always have a data object, even if it's empty initially
|
||||||
|
data_obj = scan_data['data']
|
||||||
|
|
||||||
|
if not data_obj:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'hostnames': [],
|
'hostnames': [],
|
||||||
'ip_addresses': [],
|
'ip_addresses': [],
|
||||||
'stats': scan_data['live_stats']
|
'stats': scan_data['live_stats'],
|
||||||
|
'latest_discoveries': []
|
||||||
})
|
})
|
||||||
|
|
||||||
# Return current discoveries
|
# Return current discoveries from the shared data object
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'hostnames': sorted(list(scan_data['data'].hostnames)),
|
'hostnames': sorted(list(data_obj.hostnames)),
|
||||||
'ip_addresses': sorted(list(scan_data['data'].ip_addresses)),
|
'ip_addresses': sorted(list(data_obj.ip_addresses)),
|
||||||
'stats': scan_data['data'].get_stats(),
|
'stats': data_obj.get_stats(),
|
||||||
'latest_discoveries': scan_data.get('latest_discoveries', [])
|
'latest_discoveries': scan_data.get('latest_discoveries', [])
|
||||||
})
|
})
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
def run_reconnaissance_background(scan_id: str, target: str, config: Config):
|
def run_reconnaissance_background(scan_id: str, target: str, config: Config, shared_data: ReconData):
|
||||||
"""Run reconnaissance in background thread."""
|
"""Run reconnaissance in background thread with shared data object."""
|
||||||
|
|
||||||
def update_progress(message: str, percentage: int = None):
|
def update_progress(message: str, percentage: int = None):
|
||||||
"""Update scan progress."""
|
"""Update scan progress and live statistics."""
|
||||||
with scan_lock:
|
with scan_lock:
|
||||||
if scan_id in active_scans:
|
if scan_id in active_scans:
|
||||||
active_scans[scan_id]['message'] = message
|
active_scans[scan_id]['message'] = message
|
||||||
if percentage is not None:
|
if percentage is not None:
|
||||||
active_scans[scan_id]['progress'] = percentage
|
active_scans[scan_id]['progress'] = percentage
|
||||||
|
|
||||||
# Update live stats if we have data
|
# Update live stats from the shared data object
|
||||||
if active_scans[scan_id]['data']:
|
if shared_data:
|
||||||
active_scans[scan_id]['live_stats'] = active_scans[scan_id]['data'].get_stats()
|
active_scans[scan_id]['live_stats'] = shared_data.get_stats()
|
||||||
|
|
||||||
# Add to latest discoveries (keep last 10)
|
# Add to latest discoveries (keep last 10)
|
||||||
if 'latest_discoveries' not in active_scans[scan_id]:
|
if 'latest_discoveries' not in active_scans[scan_id]:
|
||||||
@ -188,27 +196,30 @@ def run_reconnaissance_background(scan_id: str, target: str, config: Config):
|
|||||||
engine = ReconnaissanceEngine(config)
|
engine = ReconnaissanceEngine(config)
|
||||||
engine.set_progress_callback(update_progress)
|
engine.set_progress_callback(update_progress)
|
||||||
|
|
||||||
|
# IMPORTANT: Pass the shared data object to the engine
|
||||||
|
engine.set_shared_data(shared_data)
|
||||||
|
|
||||||
# Update status
|
# Update status
|
||||||
with scan_lock:
|
with scan_lock:
|
||||||
active_scans[scan_id]['status'] = 'running'
|
active_scans[scan_id]['status'] = 'running'
|
||||||
|
|
||||||
logger.info(f"🚀 Starting reconnaissance for: {target}")
|
logger.info(f"🚀 Starting reconnaissance for: {target}")
|
||||||
|
|
||||||
# Run reconnaissance
|
# Run reconnaissance - this will populate the shared_data object incrementally
|
||||||
data = engine.run_reconnaissance(target)
|
final_data = engine.run_reconnaissance(target)
|
||||||
|
|
||||||
logger.info(f"✅ Reconnaissance completed for scan: {scan_id}")
|
logger.info(f"✅ Reconnaissance completed for scan: {scan_id}")
|
||||||
|
|
||||||
# Update with results
|
# Update with final results (the shared_data should already be populated)
|
||||||
with scan_lock:
|
with scan_lock:
|
||||||
active_scans[scan_id]['status'] = 'completed'
|
active_scans[scan_id]['status'] = 'completed'
|
||||||
active_scans[scan_id]['progress'] = 100
|
active_scans[scan_id]['progress'] = 100
|
||||||
active_scans[scan_id]['message'] = 'Reconnaissance completed'
|
active_scans[scan_id]['message'] = 'Reconnaissance completed'
|
||||||
active_scans[scan_id]['data'] = data
|
active_scans[scan_id]['data'] = final_data # This should be the same as shared_data
|
||||||
active_scans[scan_id]['live_stats'] = data.get_stats()
|
active_scans[scan_id]['live_stats'] = final_data.get_stats()
|
||||||
|
|
||||||
# Log final statistics
|
# Log final statistics
|
||||||
final_stats = data.get_stats()
|
final_stats = final_data.get_stats()
|
||||||
logger.info(f"📊 Final stats for {scan_id}: {final_stats}")
|
logger.info(f"📊 Final stats for {scan_id}: {final_stats}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
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 {
|
class ReconTool {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -6,9 +6,20 @@ class ReconTool {
|
|||||||
this.pollInterval = null;
|
this.pollInterval = null;
|
||||||
this.liveDataInterval = null;
|
this.liveDataInterval = null;
|
||||||
this.currentReport = null;
|
this.currentReport = null;
|
||||||
|
this.debugMode = true; // Enable debug logging
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug(message, data = null) {
|
||||||
|
if (this.debugMode) {
|
||||||
|
if (data) {
|
||||||
|
console.log(`🔍 DEBUG: ${message}`, data);
|
||||||
|
} else {
|
||||||
|
console.log(`🔍 DEBUG: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
this.setupRealtimeElements();
|
this.setupRealtimeElements();
|
||||||
@ -67,6 +78,7 @@ class ReconTool {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
progressSection.appendChild(liveDiv);
|
progressSection.appendChild(liveDiv);
|
||||||
|
this.debug("Live discoveries container created");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +139,7 @@ class ReconTool {
|
|||||||
this.showProgressSection();
|
this.showProgressSection();
|
||||||
this.updateProgress(0, 'Starting scan...');
|
this.updateProgress(0, 'Starting scan...');
|
||||||
|
|
||||||
console.log('🚀 Starting scan with data:', scanData);
|
this.debug('Starting scan with data:', scanData);
|
||||||
|
|
||||||
const response = await fetch('/api/scan', {
|
const response = await fetch('/api/scan', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -148,7 +160,7 @@ class ReconTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.currentScanId = result.scan_id;
|
this.currentScanId = result.scan_id;
|
||||||
console.log('✅ Scan started with ID:', this.currentScanId);
|
this.debug('Scan started with ID:', this.currentScanId);
|
||||||
|
|
||||||
this.startPolling();
|
this.startPolling();
|
||||||
this.startLiveDataPolling();
|
this.startLiveDataPolling();
|
||||||
@ -160,6 +172,7 @@ class ReconTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
startPolling() {
|
startPolling() {
|
||||||
|
this.debug('Starting status polling...');
|
||||||
// Poll every 2 seconds for status updates
|
// Poll every 2 seconds for status updates
|
||||||
this.pollInterval = setInterval(() => {
|
this.pollInterval = setInterval(() => {
|
||||||
this.checkScanStatus();
|
this.checkScanStatus();
|
||||||
@ -170,6 +183,7 @@ class ReconTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
startLiveDataPolling() {
|
startLiveDataPolling() {
|
||||||
|
this.debug('Starting live data polling...');
|
||||||
// Poll every 3 seconds for live data updates
|
// Poll every 3 seconds for live data updates
|
||||||
this.liveDataInterval = setInterval(() => {
|
this.liveDataInterval = setInterval(() => {
|
||||||
this.updateLiveData();
|
this.updateLiveData();
|
||||||
@ -179,6 +193,9 @@ class ReconTool {
|
|||||||
const liveSection = document.querySelector('.live-discoveries');
|
const liveSection = document.querySelector('.live-discoveries');
|
||||||
if (liveSection) {
|
if (liveSection) {
|
||||||
liveSection.style.display = 'block';
|
liveSection.style.display = 'block';
|
||||||
|
this.debug('Live discoveries section made visible');
|
||||||
|
} else {
|
||||||
|
this.debug('ERROR: Live discoveries section not found!');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also update immediately
|
// Also update immediately
|
||||||
@ -208,12 +225,13 @@ class ReconTool {
|
|||||||
|
|
||||||
// Update live stats
|
// Update live stats
|
||||||
if (status.live_stats) {
|
if (status.live_stats) {
|
||||||
|
this.debug('Received live stats:', status.live_stats);
|
||||||
this.updateLiveStats(status.live_stats);
|
this.updateLiveStats(status.live_stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if completed
|
// Check if completed
|
||||||
if (status.status === 'completed') {
|
if (status.status === 'completed') {
|
||||||
console.log('✅ Scan completed');
|
this.debug('Scan completed, loading report...');
|
||||||
this.stopPolling();
|
this.stopPolling();
|
||||||
await this.loadScanReport();
|
await this.loadScanReport();
|
||||||
} else if (status.status === 'error') {
|
} else if (status.status === 'error') {
|
||||||
@ -233,29 +251,37 @@ class ReconTool {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.debug(`Fetching live data for scan: ${this.currentScanId}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/scan/${this.currentScanId}/live-data`);
|
const response = await fetch(`/api/scan/${this.currentScanId}/live-data`);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
this.debug(`Live data request failed: HTTP ${response.status}`);
|
||||||
return; // Silently fail for live data
|
return; // Silently fail for live data
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
|
this.debug('Live data error:', data.error);
|
||||||
return; // Silently fail for live data
|
return; // Silently fail for live data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.debug('Received live data:', data);
|
||||||
|
|
||||||
// Update live discoveries
|
// Update live discoveries
|
||||||
this.updateLiveDiscoveries(data);
|
this.updateLiveDiscoveries(data);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Silently fail for live data updates
|
// Silently fail for live data updates
|
||||||
console.debug('Live data update failed:', error);
|
this.debug('Live data update failed:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLiveStats(stats) {
|
updateLiveStats(stats) {
|
||||||
|
this.debug('Updating live stats:', stats);
|
||||||
|
|
||||||
// Update the live statistics counters
|
// Update the live statistics counters
|
||||||
const statElements = {
|
const statElements = {
|
||||||
'liveHostnames': stats.hostnames || 0,
|
'liveHostnames': stats.hostnames || 0,
|
||||||
@ -269,54 +295,69 @@ class ReconTool {
|
|||||||
Object.entries(statElements).forEach(([elementId, value]) => {
|
Object.entries(statElements).forEach(([elementId, value]) => {
|
||||||
const element = document.getElementById(elementId);
|
const element = document.getElementById(elementId);
|
||||||
if (element) {
|
if (element) {
|
||||||
|
const currentValue = element.textContent;
|
||||||
element.textContent = value;
|
element.textContent = value;
|
||||||
|
|
||||||
// Add a brief highlight effect when value changes
|
if (currentValue !== value.toString()) {
|
||||||
if (element.textContent !== value.toString()) {
|
this.debug(`Updated ${elementId}: ${currentValue} -> ${value}`);
|
||||||
|
// Add a brief highlight effect when value changes
|
||||||
element.style.backgroundColor = '#ff9900';
|
element.style.backgroundColor = '#ff9900';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
element.style.backgroundColor = '';
|
element.style.backgroundColor = '';
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.debug(`ERROR: Element ${elementId} not found!`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLiveDiscoveries(data) {
|
updateLiveDiscoveries(data) {
|
||||||
|
this.debug('Updating live discoveries with data:', data);
|
||||||
|
|
||||||
// Update hostnames list
|
// Update hostnames list
|
||||||
const hostnameList = document.querySelector('#recentHostnames .hostname-list');
|
const hostnameList = document.querySelector('#recentHostnames .hostname-list');
|
||||||
if (hostnameList && data.hostnames) {
|
if (hostnameList && data.hostnames && data.hostnames.length > 0) {
|
||||||
// Show last 10 hostnames
|
// Show last 10 hostnames
|
||||||
const recentHostnames = data.hostnames.slice(-10);
|
const recentHostnames = data.hostnames.slice(-10);
|
||||||
hostnameList.innerHTML = recentHostnames.map(hostname =>
|
hostnameList.innerHTML = recentHostnames.map(hostname =>
|
||||||
`<span class="discovery-item">${hostname}</span>`
|
`<span class="discovery-item">${hostname}</span>`
|
||||||
).join('');
|
).join('');
|
||||||
|
this.debug(`Updated hostname list with ${recentHostnames.length} items`);
|
||||||
|
} else if (hostnameList) {
|
||||||
|
this.debug(`No hostnames to display (${data.hostnames ? data.hostnames.length : 0} total)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update IP addresses list
|
// Update IP addresses list
|
||||||
const ipList = document.querySelector('#recentIPs .ip-list');
|
const ipList = document.querySelector('#recentIPs .ip-list');
|
||||||
if (ipList && data.ip_addresses) {
|
if (ipList && data.ip_addresses && data.ip_addresses.length > 0) {
|
||||||
// Show last 10 IPs
|
// Show last 10 IPs
|
||||||
const recentIPs = data.ip_addresses.slice(-10);
|
const recentIPs = data.ip_addresses.slice(-10);
|
||||||
ipList.innerHTML = recentIPs.map(ip =>
|
ipList.innerHTML = recentIPs.map(ip =>
|
||||||
`<span class="discovery-item">${ip}</span>`
|
`<span class="discovery-item">${ip}</span>`
|
||||||
).join('');
|
).join('');
|
||||||
|
this.debug(`Updated IP list with ${recentIPs.length} items`);
|
||||||
|
} else if (ipList) {
|
||||||
|
this.debug(`No IPs to display (${data.ip_addresses ? data.ip_addresses.length : 0} total)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update activity log
|
// Update activity log
|
||||||
const activityList = document.querySelector('#activityLog .activity-list');
|
const activityList = document.querySelector('#activityLog .activity-list');
|
||||||
if (activityList && data.latest_discoveries) {
|
if (activityList && data.latest_discoveries && data.latest_discoveries.length > 0) {
|
||||||
const activities = data.latest_discoveries.slice(-5); // Last 5 activities
|
const activities = data.latest_discoveries.slice(-5); // Last 5 activities
|
||||||
activityList.innerHTML = activities.map(activity => {
|
activityList.innerHTML = activities.map(activity => {
|
||||||
const time = new Date(activity.timestamp * 1000).toLocaleTimeString();
|
const time = new Date(activity.timestamp * 1000).toLocaleTimeString();
|
||||||
return `<div class="activity-item">[${time}] ${activity.message}</div>`;
|
return `<div class="activity-item">[${time}] ${activity.message}</div>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
this.debug(`Updated activity log with ${activities.length} items`);
|
||||||
|
} else if (activityList) {
|
||||||
|
this.debug(`No activities to display (${data.latest_discoveries ? data.latest_discoveries.length : 0} total)`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadScanReport() {
|
async loadScanReport() {
|
||||||
try {
|
try {
|
||||||
console.log('📄 Loading scan report...');
|
this.debug('Loading scan report...');
|
||||||
const response = await fetch(`/api/scan/${this.currentScanId}/report`);
|
const response = await fetch(`/api/scan/${this.currentScanId}/report`);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@ -330,7 +371,7 @@ class ReconTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.currentReport = report;
|
this.currentReport = report;
|
||||||
console.log('✅ Report loaded successfully');
|
this.debug('Report loaded successfully');
|
||||||
this.showResultsSection();
|
this.showResultsSection();
|
||||||
this.showReport('text'); // Default to text view
|
this.showReport('text'); // Default to text view
|
||||||
|
|
||||||
@ -341,6 +382,7 @@ class ReconTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stopPolling() {
|
stopPolling() {
|
||||||
|
this.debug('Stopping polling intervals...');
|
||||||
if (this.pollInterval) {
|
if (this.pollInterval) {
|
||||||
clearInterval(this.pollInterval);
|
clearInterval(this.pollInterval);
|
||||||
this.pollInterval = null;
|
this.pollInterval = null;
|
||||||
@ -355,18 +397,34 @@ class ReconTool {
|
|||||||
document.getElementById('scanForm').style.display = 'none';
|
document.getElementById('scanForm').style.display = 'none';
|
||||||
document.getElementById('progressSection').style.display = 'block';
|
document.getElementById('progressSection').style.display = 'block';
|
||||||
document.getElementById('resultsSection').style.display = 'none';
|
document.getElementById('resultsSection').style.display = 'none';
|
||||||
|
this.debug('Showing progress section');
|
||||||
}
|
}
|
||||||
|
|
||||||
showResultsSection() {
|
showResultsSection() {
|
||||||
document.getElementById('scanForm').style.display = 'none';
|
document.getElementById('scanForm').style.display = 'none';
|
||||||
document.getElementById('progressSection').style.display = 'none';
|
document.getElementById('progressSection').style.display = 'block'; // Keep visible
|
||||||
document.getElementById('resultsSection').style.display = 'block';
|
document.getElementById('resultsSection').style.display = 'block';
|
||||||
|
|
||||||
// Hide live discoveries in results section
|
// Change the title to show it's the final summary
|
||||||
const liveSection = document.querySelector('.live-discoveries');
|
const liveSection = document.querySelector('.live-discoveries');
|
||||||
if (liveSection) {
|
if (liveSection) {
|
||||||
liveSection.style.display = 'none';
|
const title = liveSection.querySelector('h3');
|
||||||
|
if (title) {
|
||||||
|
title.textContent = '📊 Final Discovery Summary';
|
||||||
|
}
|
||||||
|
liveSection.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide just the progress bar and scan controls
|
||||||
|
const progressBar = document.querySelector('.progress-bar');
|
||||||
|
const progressMessage = document.getElementById('progressMessage');
|
||||||
|
const scanControls = document.querySelector('.scan-controls');
|
||||||
|
|
||||||
|
if (progressBar) progressBar.style.display = 'none';
|
||||||
|
if (progressMessage) progressMessage.style.display = 'none';
|
||||||
|
if (scanControls) scanControls.style.display = 'none';
|
||||||
|
|
||||||
|
this.debug('Showing results section with live discoveries');
|
||||||
}
|
}
|
||||||
|
|
||||||
resetToForm() {
|
resetToForm() {
|
||||||
@ -378,10 +436,23 @@ class ReconTool {
|
|||||||
document.getElementById('progressSection').style.display = 'none';
|
document.getElementById('progressSection').style.display = 'none';
|
||||||
document.getElementById('resultsSection').style.display = 'none';
|
document.getElementById('resultsSection').style.display = 'none';
|
||||||
|
|
||||||
// Hide live discoveries
|
// Show progress elements again
|
||||||
|
const progressBar = document.querySelector('.progress-bar');
|
||||||
|
const progressMessage = document.getElementById('progressMessage');
|
||||||
|
const scanControls = document.querySelector('.scan-controls');
|
||||||
|
|
||||||
|
if (progressBar) progressBar.style.display = 'block';
|
||||||
|
if (progressMessage) progressMessage.style.display = 'block';
|
||||||
|
if (scanControls) scanControls.style.display = 'block';
|
||||||
|
|
||||||
|
// Hide live discoveries and reset title
|
||||||
const liveSection = document.querySelector('.live-discoveries');
|
const liveSection = document.querySelector('.live-discoveries');
|
||||||
if (liveSection) {
|
if (liveSection) {
|
||||||
liveSection.style.display = 'none';
|
liveSection.style.display = 'none';
|
||||||
|
const title = liveSection.querySelector('h3');
|
||||||
|
if (title) {
|
||||||
|
title.textContent = '🔍 Live Discoveries';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear form
|
// Clear form
|
||||||
@ -389,6 +460,8 @@ class ReconTool {
|
|||||||
document.getElementById('shodanKey').value = '';
|
document.getElementById('shodanKey').value = '';
|
||||||
document.getElementById('virustotalKey').value = '';
|
document.getElementById('virustotalKey').value = '';
|
||||||
document.getElementById('maxDepth').value = '2';
|
document.getElementById('maxDepth').value = '2';
|
||||||
|
|
||||||
|
this.debug('Reset to form view');
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProgress(percentage, message) {
|
updateProgress(percentage, message) {
|
||||||
@ -477,6 +550,6 @@ class ReconTool {
|
|||||||
|
|
||||||
// Initialize the application when DOM is loaded
|
// Initialize the application when DOM is loaded
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
console.log('🌐 DNS Reconnaissance Tool initialized');
|
console.log('🌐 DNS Reconnaissance Tool initialized with debug mode');
|
||||||
new ReconTool();
|
new ReconTool();
|
||||||
});
|
});
|
181
static/style.css
181
static/style.css
@ -211,7 +211,6 @@ header p {
|
|||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.hostname-list, .ip-list {
|
.hostname-list, .ip-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@ -245,65 +244,7 @@ header p {
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive design adjustments */
|
/* Live Discoveries Base Styling */
|
||||||
@media (max-width: 768px) {
|
|
||||||
.container {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
header h1 {
|
|
||||||
font-size: 2.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-form, .progress-section, .results-section {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary, .btn-secondary {
|
|
||||||
width: 100%;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.results-controls {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.results-controls button {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 120px;
|
|
||||||
}
|
|
||||||
.stats-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-item {
|
|
||||||
padding: 6px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-label, .stat-value {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hostname-list, .ip-list {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tactical loading spinner */
|
|
||||||
.loading {
|
|
||||||
display: inline-block;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border: 3px solid rgba(199, 199, 199, 0.3);
|
|
||||||
border-radius: 50%;
|
|
||||||
border-top-color: #00ff41; /* Night-vision green spinner */
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.live-discoveries {
|
.live-discoveries {
|
||||||
background: rgba(0, 20, 0, 0.6);
|
background: rgba(0, 20, 0, 0.6);
|
||||||
border: 1px solid #00ff41;
|
border: 1px solid #00ff41;
|
||||||
@ -319,6 +260,44 @@ header p {
|
|||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Enhanced styling for live discoveries when shown in results view */
|
||||||
|
.results-section .live-discoveries {
|
||||||
|
background: rgba(0, 40, 0, 0.8);
|
||||||
|
border: 2px solid #00ff41;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 255, 65, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-section .live-discoveries h3 {
|
||||||
|
color: #00ff41;
|
||||||
|
text-shadow: 0 0 3px rgba(0, 255, 65, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure the progress section flows nicely when showing both progress and results */
|
||||||
|
.progress-section.with-results {
|
||||||
|
margin-bottom: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-section.with-live-data {
|
||||||
|
border-top: 1px solid #444;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Better spacing for the combined view */
|
||||||
|
.progress-section + .results-section {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide specific progress elements while keeping the section visible */
|
||||||
|
.progress-section .progress-bar.hidden,
|
||||||
|
.progress-section #progressMessage.hidden,
|
||||||
|
.progress-section .scan-controls.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.stats-grid {
|
.stats-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
@ -348,6 +327,16 @@ header p {
|
|||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Animation for final stats highlight */
|
||||||
|
@keyframes finalHighlight {
|
||||||
|
0% { background-color: #ff9900; }
|
||||||
|
100% { background-color: transparent; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value.final {
|
||||||
|
animation: finalHighlight 2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
.discoveries-list {
|
.discoveries-list {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
@ -374,9 +363,77 @@ header p {
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tactical loading spinner */
|
||||||
|
.loading {
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 3px solid rgba(199, 199, 199, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top-color: #00ff41; /* Night-vision green spinner */
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
to { transform: rotate(360deg); }
|
to { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Responsive design adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-form, .progress-section, .results-section {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary, .btn-secondary {
|
||||||
|
width: 100%;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-controls {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-controls button {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
padding: 6px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label, .stat-value {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hostname-list, .ip-list {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments for the combined view */
|
||||||
|
.results-section .live-discoveries {
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-section .live-discoveries .stats-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user