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()
print(f"Using session: {user_session_id}")
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
print(f"Calling start_scan on scanner {id(scanner)}...")
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:
scan_session_id = scanner.logger.session_id

View File

@ -145,44 +145,36 @@ class Scanner:
def start_scan(self, target_domain: str, max_depth: int = 2, clear_graph: bool = True) -> bool:
"""
Start a new reconnaissance scan with concurrent processing.
Enhanced with better debugging and state validation.
Args:
target_domain: Initial domain to investigate
max_depth: Maximum recursion depth
Returns:
bool: True if scan started successfully
Start a new reconnaissance scan.
Forcefully cleans up any previous scan thread before starting.
"""
print(f"=== STARTING SCAN IN SCANNER {id(self)} ===")
print(f"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}")
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
print(f"Initial scanner status: {self.status}")
# 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:
# Check if we have any providers
if not hasattr(self, 'providers') or not self.providers:
print(f"ERROR: No providers available in scanner {id(self)}, cannot start scan")
return False
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:
self.graph.clear()
self.current_target = target_domain.lower().strip()
@ -212,6 +204,7 @@ class Scanner:
except Exception as e:
print(f"ERROR: Exception in start_scan for scanner {id(self)}: {e}")
traceback.print_exc()
self.status = ScanStatus.FAILED
return False
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.")
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"})
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:
"""
Request immediate scan termination with aggressive cancellation.
Request immediate scan termination.
Acts on the thread's liveness, not just the 'RUNNING' status.
"""
try:
if self.status == ScanStatus.RUNNING:
print("=== INITIATING IMMEDIATE SCAN TERMINATION ===")
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:
print("Shutting down executor with immediate cancellation...")
self.executor.shutdown(wait=False, cancel_futures=True)
threading.Timer(2.0, self._force_stop_completion).start()
print("Immediate termination requested - ongoing requests will be cancelled")
return True
print("No active scan to stop")
return False
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:
self.status = ScanStatus.STOPPED
return False
print("=== INITIATING IMMEDIATE SCAN TERMINATION ===")
self.status = ScanStatus.STOPPED
self.stop_event.set()
if self.executor:
print("Shutting down executor with immediate cancellation...")
self.executor.shutdown(wait=False, cancel_futures=True)
print("Termination signal sent. The scan thread will stop shortly.")
return True
except Exception as e:
print(f"ERROR: Exception in stop_scan: {e}")
traceback.print_exc()

View File

@ -314,9 +314,39 @@ input[type="text"]:focus, select:focus {
.view-controls {
display: flex;
gap: 1.5rem;
align-items: center;
}
.filter-group {
display: flex;
align-items: center;
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 {
height: 800px;
position: relative;
@ -905,4 +935,39 @@ input[type="text"]:focus, select:focus {
transform: translateX(100%);
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

@ -740,26 +740,21 @@ class DNSReconApp {
this.elements.providerList.appendChild(providerItem);
}
}
/**
* Show node details modal
* @param {string} nodeId - Node identifier
* @param {Object} node - Node data
* Generates the HTML for the node details view.
* @param {Object} node - The node object.
* @returns {string} The HTML string for the node details.
*/
showNodeModal(nodeId, node) {
if (!this.elements.nodeModal) return;
if (this.elements.modalTitle) {
this.elements.modalTitle.textContent = `Node Details`;
}
generateNodeDetailsHtml(node) {
if(!node) return '<div class="detail-row"><span class="detail-value">Details not available.</span></div>';
let detailsHtml = '';
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 ||
(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 `
<div class="detail-row">
<span class="detail-label">${label} <span class="status-icon text-warning"></span></span>
@ -794,11 +789,9 @@ class DNSReconApp {
};
const metadata = node.metadata || {};
// General Node Info
detailsHtml += createDetailRow('Node Descriptor', node.nodeId);
// Display data based on node type
detailsHtml += createDetailRow('Node Descriptor', node.id);
switch (node.type) {
case 'domain':
detailsHtml += createDetailRow('DNS Records', metadata.dns_records);
@ -814,8 +807,7 @@ class DNSReconApp {
detailsHtml += createDetailRow('VirusTotal Data', metadata.virustotal);
break;
}
// Special handling for certificate data
if (metadata.certificate_data && Object.keys(metadata.certificate_data).length > 0) {
const cert = metadata.certificate_data;
detailsHtml += `<div class="detail-section-header">Certificate Summary</div>`;
@ -832,8 +824,7 @@ class DNSReconApp {
detailsHtml += createDetailRow('Valid Until', new Date(cert.latest_certificate.not_after).toLocaleString());
}
}
// Special handling for ASN data
if (metadata.asn_data && Object.keys(metadata.asn_data).length > 0) {
detailsHtml += `<div class="detail-section-header">ASN Information</div>`;
detailsHtml += createDetailRow('ASN', metadata.asn_data.asn);
@ -842,6 +833,44 @@ class DNSReconApp {
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) {
this.elements.modalDetails.innerHTML = detailsHtml;
}