140 lines
4.5 KiB
Python
140 lines
4.5 KiB
Python
# File: src/web_app.py
|
|
"""Flask web application for reconnaissance tool."""
|
|
|
|
from flask import Flask, render_template, request, jsonify, send_from_directory
|
|
import threading
|
|
import time
|
|
from .config import Config
|
|
from .reconnaissance import ReconnaissanceEngine
|
|
from .report_generator import ReportGenerator
|
|
|
|
# Global variables for tracking ongoing scans
|
|
active_scans = {}
|
|
scan_lock = threading.Lock()
|
|
|
|
def create_app(config: Config):
|
|
"""Create Flask application."""
|
|
app = Flask(__name__,
|
|
template_folder='../templates',
|
|
static_folder='../static')
|
|
|
|
app.config['SECRET_KEY'] = 'recon-tool-secret-key'
|
|
|
|
@app.route('/')
|
|
def index():
|
|
"""Main page."""
|
|
return render_template('index.html')
|
|
|
|
@app.route('/api/scan', methods=['POST'])
|
|
def start_scan():
|
|
"""Start a new reconnaissance scan."""
|
|
data = request.get_json()
|
|
target = data.get('target')
|
|
scan_config = Config.from_args(
|
|
shodan_key=data.get('shodan_key'),
|
|
virustotal_key=data.get('virustotal_key'),
|
|
max_depth=data.get('max_depth', 2)
|
|
)
|
|
|
|
if not target:
|
|
return jsonify({'error': 'Target is required'}), 400
|
|
|
|
# Generate scan ID
|
|
scan_id = f"{target}_{int(time.time())}"
|
|
|
|
# Initialize scan data
|
|
with scan_lock:
|
|
active_scans[scan_id] = {
|
|
'status': 'starting',
|
|
'progress': 0,
|
|
'message': 'Initializing...',
|
|
'data': None,
|
|
'error': None
|
|
}
|
|
|
|
# Start reconnaissance in background thread
|
|
thread = threading.Thread(
|
|
target=run_reconnaissance_background,
|
|
args=(scan_id, target, scan_config)
|
|
)
|
|
thread.daemon = True
|
|
thread.start()
|
|
|
|
return jsonify({'scan_id': scan_id})
|
|
|
|
@app.route('/api/scan/<scan_id>/status')
|
|
def get_scan_status(scan_id):
|
|
"""Get scan status and progress."""
|
|
with scan_lock:
|
|
if scan_id not in active_scans:
|
|
return jsonify({'error': 'Scan not found'}), 404
|
|
|
|
scan_data = active_scans[scan_id].copy()
|
|
|
|
# Convert ReconData object to a dict to make it JSON serializable
|
|
if scan_data.get('data'):
|
|
scan_data['data'] = scan_data['data'].to_dict()
|
|
|
|
return jsonify(scan_data)
|
|
|
|
@app.route('/api/scan/<scan_id>/report')
|
|
def get_scan_report(scan_id):
|
|
"""Get scan report."""
|
|
with scan_lock:
|
|
if scan_id not in active_scans:
|
|
return jsonify({'error': 'Scan not found'}), 404
|
|
|
|
scan_data = active_scans[scan_id]
|
|
|
|
if scan_data['status'] != 'completed' or not scan_data['data']:
|
|
return jsonify({'error': 'Scan not completed'}), 400
|
|
|
|
# Generate report
|
|
report_gen = ReportGenerator(scan_data['data'])
|
|
|
|
return jsonify({
|
|
'json_report': scan_data['data'].to_dict(), # Use to_dict for a clean JSON object
|
|
'text_report': report_gen.generate_text_report()
|
|
})
|
|
|
|
return app
|
|
|
|
def run_reconnaissance_background(scan_id: str, target: str, config: Config):
|
|
"""Run reconnaissance in background thread."""
|
|
|
|
def update_progress(message: str, percentage: int = None):
|
|
"""Update scan progress."""
|
|
with scan_lock:
|
|
if scan_id in active_scans:
|
|
active_scans[scan_id]['message'] = message
|
|
if percentage is not None:
|
|
active_scans[scan_id]['progress'] = percentage
|
|
|
|
try:
|
|
# Initialize engine
|
|
engine = ReconnaissanceEngine(config)
|
|
engine.set_progress_callback(update_progress)
|
|
|
|
# Update status
|
|
with scan_lock:
|
|
active_scans[scan_id]['status'] = 'running'
|
|
|
|
# Run reconnaissance
|
|
data = engine.run_reconnaissance(target)
|
|
|
|
# Update with results
|
|
with scan_lock:
|
|
active_scans[scan_id]['status'] = 'completed'
|
|
active_scans[scan_id]['progress'] = 100
|
|
active_scans[scan_id]['message'] = 'Reconnaissance completed'
|
|
active_scans[scan_id]['data'] = data
|
|
|
|
except Exception as e:
|
|
# Handle errors
|
|
with scan_lock:
|
|
active_scans[scan_id]['status'] = 'error'
|
|
active_scans[scan_id]['error'] = str(e)
|
|
active_scans[scan_id]['message'] = f'Error: {str(e)}'
|
|
|
|
|