also allow ip lookups in scan
This commit is contained in:
parent
c076ee028f
commit
e2d4e12057
19
app.py
19
app.py
@ -14,6 +14,7 @@ import io
|
||||
from core.session_manager import session_manager
|
||||
from config import config
|
||||
from core.graph_manager import NodeType
|
||||
from utils.helpers import is_valid_target
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
@ -65,18 +66,20 @@ def start_scan():
|
||||
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'target_domain' not in data:
|
||||
return jsonify({'success': False, 'error': 'Missing target_domain in request'}), 400
|
||||
if not data or 'target' not in data:
|
||||
return jsonify({'success': False, 'error': 'Missing target in request'}), 400
|
||||
|
||||
target_domain = data['target_domain'].strip()
|
||||
target = data['target'].strip()
|
||||
max_depth = data.get('max_depth', config.default_recursion_depth)
|
||||
clear_graph = data.get('clear_graph', True)
|
||||
|
||||
print(f"Parsed - target_domain: '{target_domain}', max_depth: {max_depth}, clear_graph: {clear_graph}")
|
||||
print(f"Parsed - target: '{target}', max_depth: {max_depth}, clear_graph: {clear_graph}")
|
||||
|
||||
# Validation
|
||||
if not target_domain:
|
||||
return jsonify({'success': False, 'error': 'Target domain cannot be empty'}), 400
|
||||
if not target:
|
||||
return jsonify({'success': False, 'error': 'Target cannot be empty'}), 400
|
||||
if not is_valid_target(target):
|
||||
return jsonify({'success': False, 'error': 'Invalid target format. Please enter a valid domain or IP address.'}), 400
|
||||
if not isinstance(max_depth, int) or not 1 <= max_depth <= 5:
|
||||
return jsonify({'success': False, 'error': 'Max depth must be an integer between 1 and 5'}), 400
|
||||
|
||||
@ -101,7 +104,7 @@ def start_scan():
|
||||
|
||||
print(f"Using scanner {id(scanner)} in session {user_session_id}")
|
||||
|
||||
success = scanner.start_scan(target_domain, max_depth, clear_graph=clear_graph)
|
||||
success = scanner.start_scan(target, max_depth, clear_graph=clear_graph)
|
||||
|
||||
if success:
|
||||
return jsonify({
|
||||
@ -120,7 +123,7 @@ def start_scan():
|
||||
print(f"ERROR: Exception in start_scan endpoint: {e}")
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'error': f'Internal server error: {str(e)}'}), 500
|
||||
|
||||
|
||||
@app.route('/api/scan/stop', methods=['POST'])
|
||||
def stop_scan():
|
||||
"""Stop the current scan with immediate GUI feedback."""
|
||||
|
@ -204,7 +204,7 @@ class Scanner:
|
||||
self._initialize_providers()
|
||||
print("Session configuration updated")
|
||||
|
||||
def start_scan(self, target_domain: str, max_depth: int = 2, clear_graph: bool = True) -> bool:
|
||||
def start_scan(self, target: str, max_depth: int = 2, clear_graph: bool = True) -> bool:
|
||||
"""Start a new reconnaissance scan with proper cleanup of previous scans."""
|
||||
print(f"=== STARTING SCAN IN SCANNER {id(self)} ===")
|
||||
print(f"Session ID: {self.session_id}")
|
||||
@ -268,7 +268,7 @@ class Scanner:
|
||||
|
||||
if clear_graph:
|
||||
self.graph.clear()
|
||||
self.current_target = target_domain.lower().strip()
|
||||
self.current_target = target.lower().strip()
|
||||
self.max_depth = max_depth
|
||||
self.current_depth = 0
|
||||
|
||||
@ -304,76 +304,80 @@ class Scanner:
|
||||
self._update_session_state()
|
||||
return False
|
||||
|
||||
def _execute_scan(self, target_domain: str, max_depth: int) -> None:
|
||||
def _execute_scan(self, target: str, max_depth: int) -> None:
|
||||
"""Execute the reconnaissance scan with proper termination handling."""
|
||||
print(f"_execute_scan started for {target_domain} with depth {max_depth}")
|
||||
print(f"_execute_scan started for {target} with depth {max_depth}")
|
||||
self.executor = ThreadPoolExecutor(max_workers=self.max_workers)
|
||||
processed_targets = set()
|
||||
|
||||
self.task_queue.append((target_domain, 0, False))
|
||||
self.task_queue.append((target, 0, False))
|
||||
|
||||
try:
|
||||
self.status = ScanStatus.RUNNING
|
||||
self._update_session_state()
|
||||
|
||||
enabled_providers = [provider.get_name() for provider in self.providers]
|
||||
self.logger.log_scan_start(target_domain, max_depth, enabled_providers)
|
||||
self.graph.add_node(target_domain, NodeType.DOMAIN)
|
||||
self._initialize_provider_states(target_domain)
|
||||
self.logger.log_scan_start(target, max_depth, enabled_providers)
|
||||
|
||||
# Determine initial node type
|
||||
node_type = NodeType.IP if _is_valid_ip(target) else NodeType.DOMAIN
|
||||
self.graph.add_node(target, node_type)
|
||||
|
||||
self._initialize_provider_states(target)
|
||||
|
||||
# **IMPROVED**: Better termination checking in main loop
|
||||
# Better termination checking in main loop
|
||||
while self.task_queue and not self._is_stop_requested():
|
||||
try:
|
||||
target, depth, is_large_entity_member = self.task_queue.popleft()
|
||||
target_item, depth, is_large_entity_member = self.task_queue.popleft()
|
||||
except IndexError:
|
||||
# Queue became empty during processing
|
||||
break
|
||||
|
||||
if target in processed_targets:
|
||||
if target_item in processed_targets:
|
||||
continue
|
||||
|
||||
if depth > max_depth:
|
||||
continue
|
||||
|
||||
# **NEW**: Track this target as currently processing
|
||||
# Track this target as currently processing
|
||||
with self.processing_lock:
|
||||
if self._is_stop_requested():
|
||||
print(f"Stop requested before processing {target}")
|
||||
print(f"Stop requested before processing {target_item}")
|
||||
break
|
||||
self.currently_processing.add(target)
|
||||
self.currently_processing.add(target_item)
|
||||
|
||||
try:
|
||||
self.current_depth = depth
|
||||
self.current_indicator = target
|
||||
self.current_indicator = target_item
|
||||
self._update_session_state()
|
||||
|
||||
# **IMPROVED**: More frequent stop checking during processing
|
||||
# More frequent stop checking during processing
|
||||
if self._is_stop_requested():
|
||||
print(f"Stop requested during processing setup for {target}")
|
||||
print(f"Stop requested during processing setup for {target_item}")
|
||||
break
|
||||
|
||||
new_targets, large_entity_members, success = self._query_providers_for_target(target, depth, is_large_entity_member)
|
||||
new_targets, large_entity_members, success = self._query_providers_for_target(target_item, depth, is_large_entity_member)
|
||||
|
||||
# **NEW**: Check stop signal after provider queries
|
||||
# Check stop signal after provider queries
|
||||
if self._is_stop_requested():
|
||||
print(f"Stop requested after querying providers for {target}")
|
||||
print(f"Stop requested after querying providers for {target_item}")
|
||||
break
|
||||
|
||||
if not success:
|
||||
self.target_retries[target] += 1
|
||||
if self.target_retries[target] <= self.config.max_retries_per_target:
|
||||
print(f"Re-queueing target {target} (attempt {self.target_retries[target]})")
|
||||
self.task_queue.append((target, depth, is_large_entity_member))
|
||||
self.target_retries[target_item] += 1
|
||||
if self.target_retries[target_item] <= self.config.max_retries_per_target:
|
||||
print(f"Re-queueing target {target_item} (attempt {self.target_retries[target_item]})")
|
||||
self.task_queue.append((target_item, depth, is_large_entity_member))
|
||||
self.tasks_re_enqueued += 1
|
||||
else:
|
||||
print(f"ERROR: Max retries exceeded for target {target}")
|
||||
print(f"ERROR: Max retries exceeded for target {target_item}")
|
||||
self.scan_failed_due_to_retries = True
|
||||
self._log_target_processing_error(target, "Max retries exceeded")
|
||||
self._log_target_processing_error(target_item, "Max retries exceeded")
|
||||
else:
|
||||
processed_targets.add(target)
|
||||
processed_targets.add(target_item)
|
||||
self.indicators_completed += 1
|
||||
|
||||
# **NEW**: Only add new targets if not stopped
|
||||
# Only add new targets if not stopped
|
||||
if not self._is_stop_requested():
|
||||
for new_target in new_targets:
|
||||
if new_target not in processed_targets:
|
||||
@ -384,11 +388,11 @@ class Scanner:
|
||||
self.task_queue.append((member, depth, True))
|
||||
|
||||
finally:
|
||||
# **NEW**: Always remove from processing set
|
||||
# Always remove from processing set
|
||||
with self.processing_lock:
|
||||
self.currently_processing.discard(target)
|
||||
self.currently_processing.discard(target_item)
|
||||
|
||||
# **NEW**: Log termination reason
|
||||
# Log termination reason
|
||||
if self._is_stop_requested():
|
||||
print("Scan terminated due to stop request")
|
||||
self.logger.logger.info("Scan terminated by user request")
|
||||
@ -402,7 +406,7 @@ class Scanner:
|
||||
self.status = ScanStatus.FAILED
|
||||
self.logger.logger.error(f"Scan failed: {e}")
|
||||
finally:
|
||||
# **NEW**: Clear processing state on exit
|
||||
# Clear processing state on exit
|
||||
with self.processing_lock:
|
||||
self.currently_processing.clear()
|
||||
|
||||
|
@ -49,7 +49,7 @@ class DNSReconApp {
|
||||
console.log('Initializing DOM elements...');
|
||||
this.elements = {
|
||||
// Form elements
|
||||
targetDomain: document.getElementById('target-domain'),
|
||||
targetInput: document.getElementById('target-input'),
|
||||
maxDepth: document.getElementById('max-depth'),
|
||||
startScan: document.getElementById('start-scan'),
|
||||
addToGraph: document.getElementById('add-to-graph'),
|
||||
@ -87,7 +87,7 @@ class DNSReconApp {
|
||||
};
|
||||
|
||||
// Verify critical elements exist
|
||||
const requiredElements = ['targetDomain', 'startScan', 'scanStatus'];
|
||||
const requiredElements = ['targetInput', 'startScan', 'scanStatus'];
|
||||
for (const elementName of requiredElements) {
|
||||
if (!this.elements[elementName]) {
|
||||
throw new Error(`Required element '${elementName}' not found in DOM`);
|
||||
@ -156,7 +156,7 @@ class DNSReconApp {
|
||||
this.elements.configureSettings.addEventListener('click', () => this.showSettingsModal());
|
||||
|
||||
// Enter key support for target domain input
|
||||
this.elements.targetDomain.addEventListener('keypress', (e) => {
|
||||
this.elements.targetInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter' && !this.isScanning) {
|
||||
console.log('Enter key pressed in domain input');
|
||||
this.startScan();
|
||||
@ -238,23 +238,23 @@ class DNSReconApp {
|
||||
console.log('=== STARTING SCAN ===');
|
||||
|
||||
try {
|
||||
const targetDomain = this.elements.targetDomain.value.trim();
|
||||
const target = this.elements.targetInput.value.trim();
|
||||
const maxDepth = parseInt(this.elements.maxDepth.value);
|
||||
|
||||
console.log(`Target domain: "${targetDomain}", Max depth: ${maxDepth}`);
|
||||
console.log(`Target: "${target}", Max depth: ${maxDepth}`);
|
||||
|
||||
// Validation
|
||||
if (!targetDomain) {
|
||||
console.log('Validation failed: empty domain');
|
||||
this.showError('Please enter a target domain');
|
||||
this.elements.targetDomain.focus();
|
||||
if (!target) {
|
||||
console.log('Validation failed: empty target');
|
||||
this.showError('Please enter a target domain or IP');
|
||||
this.elements.targetInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isValidDomain(targetDomain)) {
|
||||
console.log(`Validation failed: invalid domain format for "${targetDomain}"`);
|
||||
this.showError('Please enter a valid domain name (e.g., example.com)');
|
||||
this.elements.targetDomain.focus();
|
||||
if (!this.isValidTarget(target)) {
|
||||
console.log(`Validation failed: invalid target format for "${target}"`);
|
||||
this.showError('Please enter a valid domain name (e.g., example.com) or IP address (e.g., 8.8.8.8)');
|
||||
this.elements.targetInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -265,7 +265,7 @@ class DNSReconApp {
|
||||
console.log('Making API call to start scan...');
|
||||
|
||||
const requestData = {
|
||||
target_domain: targetDomain,
|
||||
target: target,
|
||||
max_depth: maxDepth,
|
||||
clear_graph: clearGraph
|
||||
};
|
||||
@ -284,7 +284,7 @@ class DNSReconApp {
|
||||
this.graphManager.clear();
|
||||
}
|
||||
|
||||
console.log(`Scan started for ${targetDomain} with depth ${maxDepth}`);
|
||||
console.log(`Scan started for ${target} with depth ${maxDepth}`);
|
||||
|
||||
// Start polling immediately with faster interval for responsiveness
|
||||
this.startPolling(1000);
|
||||
@ -685,7 +685,7 @@ class DNSReconApp {
|
||||
this.elements.stopScan.classList.remove('loading');
|
||||
this.elements.stopScan.innerHTML = '<span class="btn-icon">[STOP]</span><span>Terminate Scan</span>';
|
||||
}
|
||||
if (this.elements.targetDomain) this.elements.targetDomain.disabled = true;
|
||||
if (this.elements.targetInput) this.elements.targetInput.disabled = true;
|
||||
if (this.elements.maxDepth) this.elements.maxDepth.disabled = true;
|
||||
if (this.elements.configureSettings) this.elements.configureSettings.disabled = true;
|
||||
break;
|
||||
@ -708,7 +708,7 @@ class DNSReconApp {
|
||||
this.elements.stopScan.disabled = true;
|
||||
this.elements.stopScan.innerHTML = '<span class="btn-icon">[STOP]</span><span>Terminate Scan</span>';
|
||||
}
|
||||
if (this.elements.targetDomain) this.elements.targetDomain.disabled = false;
|
||||
if (this.elements.targetInput) this.elements.targetInput.disabled = false;
|
||||
if (this.elements.maxDepth) this.elements.maxDepth.disabled = false;
|
||||
if (this.elements.configureSettings) this.elements.configureSettings.disabled = false;
|
||||
break;
|
||||
@ -2065,65 +2065,48 @@ class DNSReconApp {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate domain name - improved validation
|
||||
* Validate target (domain or IP)
|
||||
* @param {string} target - Target to validate
|
||||
* @returns {boolean} True if valid
|
||||
*/
|
||||
isValidTarget(target) {
|
||||
return this.isValidDomain(target) || this.isValidIp(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate domain name
|
||||
* @param {string} domain - Domain to validate
|
||||
* @returns {boolean} True if valid
|
||||
*/
|
||||
isValidDomain(domain) {
|
||||
console.log(`Validating domain: "${domain}"`);
|
||||
|
||||
// Basic checks
|
||||
if (!domain || typeof domain !== 'string') {
|
||||
console.log('Validation failed: empty or non-string domain');
|
||||
if (!domain || typeof domain !== 'string' || domain.length > 253 || /^\d{1,3}(\.\d{1,3}){3}$/.test(domain)) {
|
||||
return false;
|
||||
}
|
||||
if (domain.length > 253) {
|
||||
console.log('Validation failed: domain too long');
|
||||
return false;
|
||||
}
|
||||
if (domain.startsWith('.') || domain.endsWith('.')) {
|
||||
console.log('Validation failed: domain starts or ends with dot');
|
||||
return false;
|
||||
}
|
||||
if (domain.includes('..')) {
|
||||
console.log('Validation failed: domain contains double dots');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Split into parts and validate each
|
||||
const parts = domain.split('.');
|
||||
if (parts.length < 2) {
|
||||
console.log('Validation failed: domain has less than 2 parts');
|
||||
if (parts.length < 2 || parts.some(part => !/^[a-zA-Z0-9-]{1,63}$/.test(part) || part.startsWith('-') || part.endsWith('-'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check each part
|
||||
for (const part of parts) {
|
||||
if (!part || part.length > 63) {
|
||||
console.log(`Validation failed: invalid part "${part}"`);
|
||||
return false;
|
||||
}
|
||||
if (part.startsWith('-') || part.endsWith('-')) {
|
||||
console.log(`Validation failed: part "${part}" starts or ends with hyphen`);
|
||||
return false;
|
||||
}
|
||||
if (!/^[a-zA-Z0-9-]+$/.test(part)) {
|
||||
console.log(`Validation failed: part "${part}" contains invalid characters`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check TLD (last part) is alphabetic
|
||||
const tld = parts[parts.length - 1];
|
||||
if (!/^[a-zA-Z]{2,}$/.test(tld)) {
|
||||
console.log(`Validation failed: invalid TLD "${tld}"`);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('Domain validation passed');
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate IP address
|
||||
* @param {string} ip - IP to validate
|
||||
* @returns {boolean} True if valid
|
||||
*/
|
||||
isValidIp(ip) {
|
||||
console.log(`Validating IP: "${ip}"`);
|
||||
const parts = ip.split('.');
|
||||
if (parts.length !== 4) {
|
||||
return false;
|
||||
}
|
||||
return parts.every(part => {
|
||||
const num = parseInt(part, 10);
|
||||
return !isNaN(num) && num >= 0 && num <= 255 && String(num) === part;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format status text for display
|
||||
* @param {string} status - Raw status
|
||||
|
@ -32,8 +32,8 @@
|
||||
|
||||
<div class="form-container">
|
||||
<div class="input-group">
|
||||
<label for="target-domain">Target Domain</label>
|
||||
<input type="text" id="target-domain" placeholder="example.com" autocomplete="off">
|
||||
<label for="target-input">Target Domain or IP</label>
|
||||
<input type="text" id="target-input" placeholder="example.com or 8.8.8.8" autocomplete="off">
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
|
@ -48,3 +48,15 @@ def _is_valid_ip(ip: str) -> bool:
|
||||
|
||||
except (ValueError, AttributeError):
|
||||
return False
|
||||
|
||||
def is_valid_target(target: str) -> bool:
|
||||
"""
|
||||
Checks if the target is a valid domain or IP address.
|
||||
|
||||
Args:
|
||||
target: The target string to validate.
|
||||
|
||||
Returns:
|
||||
True if the target is a valid domain or IP, False otherwise.
|
||||
"""
|
||||
return _is_valid_domain(target) or _is_valid_ip(target)
|
Loading…
x
Reference in New Issue
Block a user