This commit is contained in:
overcuriousity
2025-09-13 23:45:36 +02:00
parent 41d556e2ce
commit b7a57f1552
11 changed files with 125 additions and 150 deletions

View File

@@ -1,28 +1,25 @@
"""
Core modules for DNSRecon passive reconnaissance tool.
Contains graph management, scanning orchestration, and forensic logging.
Phase 2: Enhanced with concurrent processing and real-time capabilities.
"""
from .graph_manager import GraphManager, NodeType, RelationshipType
from .scanner import Scanner, ScanStatus # Remove 'scanner' global instance
from .graph_manager import GraphManager, NodeType
from .scanner import Scanner, ScanStatus
from .logger import ForensicLogger, get_forensic_logger, new_session
from .session_manager import session_manager # Add session manager
from .session_config import SessionConfig, create_session_config # Add session config
from .session_manager import session_manager
from .session_config import SessionConfig, create_session_config
__all__ = [
'GraphManager',
'NodeType',
'RelationshipType',
'Scanner',
'ScanStatus',
# 'scanner', # Remove this - no more global scanner
'ForensicLogger',
'get_forensic_logger',
'new_session',
'session_manager', # Add this
'SessionConfig', # Add this
'create_session_config' # Add this
'session_manager',
'SessionConfig',
'create_session_config'
]
__version__ = "1.0.0-phase2"

View File

@@ -22,28 +22,6 @@ class NodeType(Enum):
return self.value
class RelationshipType(Enum):
"""Enumeration of supported relationship types with confidence scores."""
SAN_CERTIFICATE = ("san", 0.9)
A_RECORD = ("a_record", 0.8)
AAAA_RECORD = ("aaaa_record", 0.8)
CNAME_RECORD = ("cname", 0.8)
MX_RECORD = ("mx_record", 0.7)
NS_RECORD = ("ns_record", 0.7)
PTR_RECORD = ("ptr_record", 0.8)
SOA_RECORD = ("soa_record", 0.7)
PASSIVE_DNS = ("passive_dns", 0.6)
ASN_MEMBERSHIP = ("asn", 0.7)
CORRELATED_TO = ("correlated_to", 0.9)
def __init__(self, relationship_name: str, default_confidence: float):
self.relationship_name = relationship_name
self.default_confidence = default_confidence
def __repr__(self):
return self.relationship_name
class GraphManager:
"""
Thread-safe graph manager for DNSRecon infrastructure mapping.
@@ -152,7 +130,7 @@ class GraphManager:
})
return all_correlations
def add_node(self, node_id: str, node_type: NodeType, attributes: Optional[Dict[str, Any]] = None,
def add_node(self, node_id: str, node_type: NodeType, attributes: Optional[Dict[str, Any]] = None,
description: str = "", metadata: Optional[Dict[str, Any]] = None) -> bool:
"""Add a node to the graph, update attributes, and process correlations."""
is_new_node = not self.graph.has_node(node_id)
@@ -180,19 +158,23 @@ class GraphManager:
for corr in correlations:
value = corr['value']
# FIXED: Check if the correlation value contains an existing node ID.
found_major_node_id = None
if isinstance(value, str):
# Check if the value contains ANY existing major node ID from the entire graph
for existing_node in self.graph.nodes():
if existing_node in value:
# Ensure the existing_node is a major type (domain/ip/asn) and is a substring of the correlation value
if (self.graph.nodes[existing_node].get('type') in [NodeType.DOMAIN.value, NodeType.IP.value, NodeType.ASN.value] and
existing_node in value):
found_major_node_id = existing_node
break
break # Found a major node, no need to check further
if found_major_node_id:
# An existing major node is part of the value; link to it directly.
for c_node_id in set(corr['nodes']):
if self.graph.has_node(c_node_id) and c_node_id != found_major_node_id:
self.add_edge(c_node_id, found_major_node_id, RelationshipType.CORRELATED_TO)
attribute = corr['sources'][0]['path'].split('.')[-1]
relationship_type = f"c_{attribute}"
self.add_edge(c_node_id, found_major_node_id, relationship_type, confidence_score=0.9)
continue # Skip creating a redundant correlation node
# Proceed to create a new correlation node if no major node was found.
@@ -211,22 +193,29 @@ class GraphManager:
existing_meta['sources'] = [{'node_id': nid, 'path': p} for nid, p in existing_sources]
for c_node_id in set(corr['nodes']):
self.add_edge(c_node_id, correlation_node_id, RelationshipType.CORRELATED_TO)
attribute = corr['sources'][0]['path'].split('.')[-1]
relationship_type = f"c_{attribute}"
self.add_edge(c_node_id, correlation_node_id, relationship_type, confidence_score=0.9)
self._update_correlation_index(node_id, attributes)
self.last_modified = datetime.now(timezone.utc).isoformat()
return is_new_node
def add_edge(self, source_id: str, target_id: str, relationship_type: RelationshipType,
confidence_score: Optional[float] = None, source_provider: str = "unknown",
def add_edge(self, source_id: str, target_id: str, relationship_type: str,
confidence_score: float = 0.5, source_provider: str = "unknown",
raw_data: Optional[Dict[str, Any]] = None) -> bool:
"""Add or update an edge between two nodes, ensuring nodes exist."""
# LOGIC FIX: Ensure both source and target nodes exist before adding an edge.
if not self.graph.has_node(source_id) or not self.graph.has_node(target_id):
return False
new_confidence = confidence_score or relationship_type.default_confidence
new_confidence = confidence_score
if relationship_type.startswith("c_"):
edge_label = relationship_type
else:
edge_label = f"{source_provider}_{relationship_type}"
if self.graph.has_edge(source_id, target_id):
# If edge exists, update confidence if the new score is higher.
if new_confidence > self.graph.edges[source_id, target_id].get('confidence_score', 0):
@@ -237,7 +226,7 @@ class GraphManager:
# Add a new edge with all attributes.
self.graph.add_edge(source_id, target_id,
relationship_type=relationship_type.relationship_name,
relationship_type=edge_label,
confidence_score=new_confidence,
source_provider=source_provider,
discovery_timestamp=datetime.now(timezone.utc).isoformat(),

View File

@@ -10,7 +10,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed, CancelledError,
from collections import defaultdict, deque
from datetime import datetime, timezone
from core.graph_manager import GraphManager, NodeType, RelationshipType
from core.graph_manager import GraphManager, NodeType
from core.logger import get_forensic_logger, new_session
from utils.helpers import _is_valid_ip, _is_valid_domain
from providers.base_provider import BaseProvider
@@ -28,7 +28,6 @@ class ScanStatus:
class Scanner:
"""
Main scanning orchestrator for DNSRecon passive reconnaissance.
Enhanced with reliable cross-process termination capabilities.
"""
def __init__(self, session_config=None):
@@ -507,7 +506,7 @@ class Scanner:
self.logger.log_relationship_discovery(
source_node=source,
target_node=rel_target,
relationship_type=rel_type.relationship_name,
relationship_type=rel_type,
confidence_score=confidence,
provider=provider_name,
raw_data=raw_data,
@@ -519,18 +518,18 @@ class Scanner:
if _is_valid_ip(rel_target):
self.graph.add_node(rel_target, NodeType.IP)
if self.graph.add_edge(source, rel_target, rel_type, confidence, provider_name, raw_data):
print(f"Added IP relationship: {source} -> {rel_target} ({rel_type.relationship_name})")
print(f"Added IP relationship: {source} -> {rel_target} ({rel_type})")
discovered_targets.add(rel_target)
elif rel_target.startswith('AS') and rel_target[2:].isdigit():
self.graph.add_node(rel_target, NodeType.ASN)
if self.graph.add_edge(source, rel_target, rel_type, confidence, provider_name, raw_data):
print(f"Added ASN relationship: {source} -> {rel_target} ({rel_type.relationship_name})")
print(f"Added ASN relationship: {source} -> {rel_target} ({rel_type})")
elif _is_valid_domain(rel_target):
self.graph.add_node(rel_target, NodeType.DOMAIN)
if self.graph.add_edge(source, rel_target, rel_type, confidence, provider_name, raw_data):
print(f"Added domain relationship: {source} -> {rel_target} ({rel_type.relationship_name})")
print(f"Added domain relationship: {source} -> {rel_target} ({rel_type})")
discovered_targets.add(rel_target)
self._collect_node_attributes(rel_target, provider_name, rel_type, source, raw_data, node_attributes[rel_target])
@@ -577,10 +576,10 @@ class Scanner:
return set(targets)
def _collect_node_attributes(self, node_id: str, provider_name: str, rel_type: RelationshipType,
def _collect_node_attributes(self, node_id: str, provider_name: str, rel_type: str,
target: str, raw_data: Dict[str, Any], attributes: Dict[str, Any]) -> None:
"""Collect and organize attributes for a node."""
self.logger.logger.debug(f"Collecting attributes for {node_id} from {provider_name}: {rel_type.relationship_name}")
self.logger.logger.debug(f"Collecting attributes for {node_id} from {provider_name}: {rel_type}")
if provider_name == 'dns':
record_type = raw_data.get('query_type', 'UNKNOWN')
@@ -590,7 +589,7 @@ class Scanner:
attributes.setdefault('dns_records', []).append(dns_entry)
elif provider_name == 'crtsh':
if rel_type == RelationshipType.SAN_CERTIFICATE:
if rel_type == "san_certificate":
domain_certs = raw_data.get('domain_certificates', {})
if node_id in domain_certs:
cert_summary = domain_certs[node_id]
@@ -604,7 +603,7 @@ class Scanner:
if key not in shodan_attributes or not shodan_attributes.get(key):
shodan_attributes[key] = value
if rel_type == RelationshipType.ASN_MEMBERSHIP:
if rel_type == "asn_membership":
attributes['asn'] = {
'id': target,
'description': raw_data.get('org', ''),
@@ -612,7 +611,7 @@ class Scanner:
'country': raw_data.get('country', '')
}
record_type_name = rel_type.relationship_name
record_type_name = rel_type
if record_type_name not in attributes:
attributes[record_type_name] = []
@@ -719,8 +718,7 @@ class Scanner:
'final_status': self.status,
'total_indicators_processed': self.indicators_processed,
'enabled_providers': list(provider_stats.keys()),
'session_id': self.session_id,
'forensic_note': 'Enhanced scanner with reliable cross-process termination'
'session_id': self.session_id
},
'graph_data': graph_data,
'forensic_audit': audit_trail,

View File

@@ -16,7 +16,6 @@ from core.scanner import Scanner
class SessionManager:
"""
Manages multiple scanner instances for concurrent user sessions using Redis.
Enhanced with reliable cross-process stop signal management and immediate state updates.
"""
def __init__(self, session_timeout_minutes: int = 60):
@@ -250,7 +249,7 @@ class SessionManager:
def get_session(self, session_id: str) -> Optional[Scanner]:
"""
Get scanner instance for a session from Redis with enhanced session ID management.
Get scanner instance for a session from Redis with session ID management.
"""
if not session_id:
return None