This commit is contained in:
overcuriousity 2025-09-11 21:38:04 +02:00
parent b47e679992
commit 646b569ced
4 changed files with 163 additions and 91 deletions

16
app.py
View File

@ -115,27 +115,11 @@ def start_scan():
user_session_id, scanner = get_user_scanner() user_session_id, scanner = get_user_scanner()
print(f"Using session: {user_session_id}") print(f"Using session: {user_session_id}")
print(f"Scanner object ID: {id(scanner)}") print(f"Scanner object ID: {id(scanner)}")
print(f"Scanner status before start: {scanner.status}")
# Additional safety check - if scanner is somehow in running state, force reset
if scanner.status == 'running':
print(f"WARNING: Scanner in session {user_session_id} was already running - forcing reset")
scanner.stop_scan()
# Give it a moment to stop
import time
time.sleep(1)
# If still running, force status reset
if scanner.status == 'running':
print("WARNING: Force resetting scanner status from 'running' to 'idle'")
scanner.status = 'idle'
# Start scan # Start scan
print(f"Calling start_scan on scanner {id(scanner)}...") print(f"Calling start_scan on scanner {id(scanner)}...")
success = scanner.start_scan(target_domain, max_depth, clear_graph=clear_graph) success = scanner.start_scan(target_domain, max_depth, clear_graph=clear_graph)
print(f"scanner.start_scan returned: {success}")
print(f"Scanner status after start attempt: {scanner.status}")
if success: if success:
scan_session_id = scanner.logger.session_id scan_session_id = scanner.logger.session_id

View File

@ -145,28 +145,29 @@ class Scanner:
def start_scan(self, target_domain: str, max_depth: int = 2, clear_graph: bool = True) -> bool: def start_scan(self, target_domain: str, max_depth: int = 2, clear_graph: bool = True) -> bool:
""" """
Start a new reconnaissance scan with concurrent processing. Start a new reconnaissance scan.
Enhanced with better debugging and state validation. Forcefully cleans up any previous scan thread before starting.
Args:
target_domain: Initial domain to investigate
max_depth: Maximum recursion depth
Returns:
bool: True if scan started successfully
""" """
print(f"=== STARTING SCAN IN SCANNER {id(self)} ===") print(f"=== STARTING SCAN IN SCANNER {id(self)} ===")
print(f"Scanner status: {self.status}") print(f"Initial scanner status: {self.status}")
print(f"Target domain: '{target_domain}', Max depth: {max_depth}")
print(f"Available providers: {len(self.providers) if hasattr(self, 'providers') else 0}") # If a thread is still alive from a previous scan, we must wait for it to die.
if self.scan_thread and self.scan_thread.is_alive():
print("A previous scan thread is still alive. Sending termination signal and waiting...")
self.stop_scan()
self.scan_thread.join(10.0) # Wait up to 10 seconds
if self.scan_thread.is_alive():
print("ERROR: The previous scan thread is unresponsive and could not be stopped. Please restart the application.")
self.status = ScanStatus.FAILED
return False
print("Previous scan thread terminated successfully.")
# Reset state for the new scan
self.status = ScanStatus.IDLE
print(f"Scanner state is now clean for a new scan.")
try: try:
if self.status == ScanStatus.RUNNING:
print(f"ERROR: Scan already running in scanner {id(self)}, rejecting new scan")
print(f"Current target: {self.current_target}")
print(f"Current depth: {self.current_depth}")
return False
# Check if we have any providers # Check if we have any providers
if not hasattr(self, 'providers') or not self.providers: if not hasattr(self, 'providers') or not self.providers:
print(f"ERROR: No providers available in scanner {id(self)}, cannot start scan") print(f"ERROR: No providers available in scanner {id(self)}, cannot start scan")
@ -174,15 +175,6 @@ class Scanner:
print(f"Scanner {id(self)} validation passed, providers available: {[p.get_name() for p in self.providers]}") print(f"Scanner {id(self)} validation passed, providers available: {[p.get_name() for p in self.providers]}")
# Stop any existing scan thread
if self.scan_thread and self.scan_thread.is_alive():
print(f"Stopping existing scan thread in scanner {id(self)}...")
self.stop_event.set()
self.scan_thread.join(timeout=5.0)
if self.scan_thread.is_alive():
print(f"WARNING: Could not stop existing thread in scanner {id(self)}")
return False
if clear_graph: if clear_graph:
self.graph.clear() self.graph.clear()
self.current_target = target_domain.lower().strip() self.current_target = target_domain.lower().strip()
@ -212,6 +204,7 @@ class Scanner:
except Exception as e: except Exception as e:
print(f"ERROR: Exception in start_scan for scanner {id(self)}: {e}") print(f"ERROR: Exception in start_scan for scanner {id(self)}: {e}")
traceback.print_exc() traceback.print_exc()
self.status = ScanStatus.FAILED
return False return False
def _execute_scan(self, target_domain: str, max_depth: int) -> None: def _execute_scan(self, target_domain: str, max_depth: int) -> None:
@ -525,7 +518,13 @@ class Scanner:
""" """
print(f"Large number of {rel_type.name} relationships for {source}. Creating a large entity node.") print(f"Large number of {rel_type.name} relationships for {source}. Creating a large entity node.")
entity_name = f"Large collection of {rel_type.name} for {source}" entity_name = f"Large collection of {rel_type.name} for {source}"
self.graph.add_node(entity_name, NodeType.LARGE_ENTITY, metadata={"count": len(targets)}) node_type = 'unknown'
if targets:
if _is_valid_domain(targets[0]):
node_type = 'domain'
elif _is_valid_ip(targets[0]):
node_type = 'ip'
self.graph.add_node(entity_name, NodeType.LARGE_ENTITY, metadata={"count": len(targets), "nodes": targets, "node_type": node_type})
self.graph.add_edge(source, entity_name, rel_type, 0.9, provider_name, {"info": "Aggregated node"}) self.graph.add_edge(source, entity_name, rel_type, 0.9, provider_name, {"info": "Aggregated node"})
def _safe_provider_query(self, provider, target: str, is_ip: bool) -> List[Tuple[str, str, RelationshipType, float, Dict[str, Any]]]: def _safe_provider_query(self, provider, target: str, is_ip: bool) -> List[Tuple[str, str, RelationshipType, float, Dict[str, Any]]]:
@ -543,32 +542,27 @@ class Scanner:
def stop_scan(self) -> bool: def stop_scan(self) -> bool:
""" """
Request immediate scan termination with aggressive cancellation. Request immediate scan termination.
Acts on the thread's liveness, not just the 'RUNNING' status.
""" """
try: try:
if not self.scan_thread or not self.scan_thread.is_alive():
print("No active scan thread to stop.")
# Cleanup state if inconsistent
if self.status == ScanStatus.RUNNING: if self.status == ScanStatus.RUNNING:
self.status = ScanStatus.STOPPED
return False
print("=== INITIATING IMMEDIATE SCAN TERMINATION ===") print("=== INITIATING IMMEDIATE SCAN TERMINATION ===")
self.status = ScanStatus.STOPPED
self.stop_event.set() self.stop_event.set()
for provider in self.providers:
try:
if hasattr(provider, 'session'):
provider.session.close()
print(f"Closed HTTP session for provider: {provider.get_name()}")
except Exception as e:
print(f"Error closing session for {provider.get_name()}: {e}")
if self.executor: if self.executor:
print("Shutting down executor with immediate cancellation...") print("Shutting down executor with immediate cancellation...")
self.executor.shutdown(wait=False, cancel_futures=True) self.executor.shutdown(wait=False, cancel_futures=True)
threading.Timer(2.0, self._force_stop_completion).start() print("Termination signal sent. The scan thread will stop shortly.")
print("Immediate termination requested - ongoing requests will be cancelled")
return True return True
print("No active scan to stop")
return False
except Exception as e: except Exception as e:
print(f"ERROR: Exception in stop_scan: {e}") print(f"ERROR: Exception in stop_scan: {e}")
traceback.print_exc() traceback.print_exc()

View File

@ -314,9 +314,39 @@ input[type="text"]:focus, select:focus {
.view-controls { .view-controls {
display: flex; display: flex;
gap: 1.5rem;
align-items: center;
}
.filter-group {
display: flex;
align-items: center;
gap: 0.5rem; gap: 0.5rem;
} }
.filter-group label {
font-size: 0.9rem;
color: #999;
}
.filter-group select,
.filter-group input[type="range"] {
background-color: #1a1a1a;
border: 1px solid #555;
color: #c7c7c7;
padding: 0.25rem 0.5rem;
}
.filter-group select {
max-width: 150px;
}
#confidence-value {
min-width: 30px;
text-align: center;
color: #00ff41;
}
.graph-container { .graph-container {
height: 800px; height: 800px;
position: relative; position: relative;
@ -906,3 +936,38 @@ input[type="text"]:focus, select:focus {
opacity: 0; opacity: 0;
} }
} }
/* dnsrecon/static/css/main.css */
/* ... (at the end of the file) */
.large-entity-nodes-list {
margin-top: 1rem;
}
.large-entity-node-details {
margin-bottom: 0.5rem;
border: 1px solid #333;
border-radius: 3px;
}
.large-entity-node-details summary {
padding: 0.5rem;
background-color: #3a3a3a;
cursor: pointer;
outline: none;
}
.large-entity-node-details summary:hover {
background-color: #4a4a4a;
}
.large-entity-node-details .detail-row {
margin-left: 1rem;
margin-right: 1rem;
}
.large-entity-node-details .detail-section-header {
margin-left: 1rem;
margin-right: 1rem;
}

View File

@ -742,24 +742,19 @@ class DNSReconApp {
} }
/** /**
* Show node details modal * Generates the HTML for the node details view.
* @param {string} nodeId - Node identifier * @param {Object} node - The node object.
* @param {Object} node - Node data * @returns {string} The HTML string for the node details.
*/ */
showNodeModal(nodeId, node) { generateNodeDetailsHtml(node) {
if (!this.elements.nodeModal) return; if(!node) return '<div class="detail-row"><span class="detail-value">Details not available.</span></div>';
if (this.elements.modalTitle) {
this.elements.modalTitle.textContent = `Node Details`;
}
let detailsHtml = ''; let detailsHtml = '';
const createDetailRow = (label, value, statusIcon = '') => { const createDetailRow = (label, value, statusIcon = '') => {
const baseId = `detail-${label.replace(/[^a-zA-Z0-9]/g, '-')}`; const baseId = `detail-${node.id.replace(/[^a-zA-Z0-9]/g, '-')}-${label.replace(/[^a-zA-Z0-9]/g, '-')}`;
if (value === null || value === undefined || if (value === null || value === undefined ||
(Array.isArray(value) && value.length === 0) || (Array.isArray(value) && value.length === 0) ||
(typeof value === 'object' && Object.keys(value).length === 0)) { (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0)) {
return ` return `
<div class="detail-row"> <div class="detail-row">
<span class="detail-label">${label} <span class="status-icon text-warning"></span></span> <span class="detail-label">${label} <span class="status-icon text-warning"></span></span>
@ -795,10 +790,8 @@ class DNSReconApp {
const metadata = node.metadata || {}; const metadata = node.metadata || {};
// General Node Info detailsHtml += createDetailRow('Node Descriptor', node.id);
detailsHtml += createDetailRow('Node Descriptor', node.nodeId);
// Display data based on node type
switch (node.type) { switch (node.type) {
case 'domain': case 'domain':
detailsHtml += createDetailRow('DNS Records', metadata.dns_records); detailsHtml += createDetailRow('DNS Records', metadata.dns_records);
@ -815,7 +808,6 @@ class DNSReconApp {
break; break;
} }
// Special handling for certificate data
if (metadata.certificate_data && Object.keys(metadata.certificate_data).length > 0) { if (metadata.certificate_data && Object.keys(metadata.certificate_data).length > 0) {
const cert = metadata.certificate_data; const cert = metadata.certificate_data;
detailsHtml += `<div class="detail-section-header">Certificate Summary</div>`; detailsHtml += `<div class="detail-section-header">Certificate Summary</div>`;
@ -833,7 +825,6 @@ class DNSReconApp {
} }
} }
// Special handling for ASN data
if (metadata.asn_data && Object.keys(metadata.asn_data).length > 0) { if (metadata.asn_data && Object.keys(metadata.asn_data).length > 0) {
detailsHtml += `<div class="detail-section-header">ASN Information</div>`; detailsHtml += `<div class="detail-section-header">ASN Information</div>`;
detailsHtml += createDetailRow('ASN', metadata.asn_data.asn); detailsHtml += createDetailRow('ASN', metadata.asn_data.asn);
@ -842,6 +833,44 @@ class DNSReconApp {
detailsHtml += createDetailRow('Country', metadata.asn_data.country); detailsHtml += createDetailRow('Country', metadata.asn_data.country);
} }
return detailsHtml;
}
/**
* Show node details modal
* @param {string} nodeId - Node identifier
* @param {Object} node - Node data
*/
showNodeModal(nodeId, node) {
if (!this.elements.nodeModal) return;
if (this.elements.modalTitle) {
this.elements.modalTitle.textContent = `Node Details`;
}
let detailsHtml = '';
if (node.type === 'large_entity') {
const metadata = node.metadata || {};
const nodes = metadata.nodes || [];
const node_type = metadata.node_type || 'nodes';
detailsHtml += `<div class="detail-section-header">Contains ${metadata.count} ${node_type}s</div>`;
detailsHtml += '<div class="large-entity-nodes-list">';
for(const innerNodeId of nodes) {
const innerNode = this.graphManager.nodes.get(innerNodeId);
detailsHtml += `<details class="large-entity-node-details">`;
detailsHtml += `<summary>${innerNodeId}</summary>`;
detailsHtml += this.generateNodeDetailsHtml(innerNode);
detailsHtml += `</details>`;
}
detailsHtml += '</div>';
} else {
detailsHtml = this.generateNodeDetailsHtml(node);
}
if (this.elements.modalDetails) { if (this.elements.modalDetails) {
this.elements.modalDetails.innerHTML = detailsHtml; this.elements.modalDetails.innerHTML = detailsHtml;
} }