Autarch/web/routes/offense.py
DigiJ ffe47c51b5 Initial public release — AUTARCH v1.0.0
Full security platform with web dashboard, 16 Flask blueprints, 26 modules,
autonomous AI agent, WebUSB hardware support, and Archon Android companion app.

Includes Hash Toolkit, debug console, anti-stalkerware shield, Metasploit/RouterSploit
integration, WireGuard VPN, OSINT reconnaissance, and multi-backend LLM support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:57:32 -08:00

227 lines
7.4 KiB
Python

"""Offense category route - MSF status, module search, sessions, module browsing, module execution."""
import json
import threading
import uuid
from flask import Blueprint, render_template, request, jsonify, Response
from web.auth import login_required
_running_jobs: dict = {} # job_id -> threading.Event (stop signal)
offense_bp = Blueprint('offense', __name__, url_prefix='/offense')
@offense_bp.route('/')
@login_required
def index():
from core.menu import MainMenu
menu = MainMenu()
menu.load_modules()
modules = {k: v for k, v in menu.modules.items() if v.category == 'offense'}
return render_template('offense.html', modules=modules)
@offense_bp.route('/status')
@login_required
def status():
"""Get MSF connection status."""
try:
from core.msf_interface import get_msf_interface
msf = get_msf_interface()
connected = msf.is_connected
result = {'connected': connected}
if connected:
try:
settings = msf.manager.get_settings()
result['host'] = settings.get('host', 'localhost')
result['port'] = settings.get('port', 55553)
except Exception:
pass
return jsonify(result)
except Exception:
return jsonify({'connected': False})
@offense_bp.route('/search', methods=['POST'])
@login_required
def search():
"""Search MSF modules (offline library first, then live if connected)."""
data = request.get_json(silent=True) or {}
query = data.get('query', '').strip()
if not query:
return jsonify({'error': 'No search query provided'})
# Search offline library first
try:
from core.msf_modules import search_modules as offline_search
results = offline_search(query, max_results=30)
modules = [{'path': r['path'], 'name': r.get('name', ''), 'description': r.get('description', '')} for r in results]
except Exception:
modules = []
# If no offline results and MSF is connected, try live search
if not modules:
try:
from core.msf_interface import get_msf_interface
msf = get_msf_interface()
if msf.is_connected:
live_results = msf.search_modules(query)
modules = [{'path': r, 'name': r.split('/')[-1] if isinstance(r, str) else '', 'description': ''} for r in live_results[:30]]
except Exception:
pass
return jsonify({'modules': modules})
@offense_bp.route('/sessions')
@login_required
def sessions():
"""List active MSF sessions."""
try:
from core.msf_interface import get_msf_interface
msf = get_msf_interface()
if not msf.is_connected:
return jsonify({'sessions': {}, 'error': 'Not connected to MSF'})
sessions_data = msf.list_sessions()
# Convert session data to serializable format
result = {}
for sid, sinfo in sessions_data.items():
if isinstance(sinfo, dict):
result[str(sid)] = sinfo
else:
result[str(sid)] = {
'type': getattr(sinfo, 'type', ''),
'tunnel_peer': getattr(sinfo, 'tunnel_peer', ''),
'info': getattr(sinfo, 'info', ''),
'target_host': getattr(sinfo, 'target_host', ''),
}
return jsonify({'sessions': result})
except Exception as e:
return jsonify({'sessions': {}, 'error': str(e)})
@offense_bp.route('/modules/<module_type>')
@login_required
def browse_modules(module_type):
"""Browse modules by type from offline library."""
page = request.args.get('page', 1, type=int)
per_page = 20
try:
from core.msf_modules import get_modules_by_type
all_modules = get_modules_by_type(module_type)
start = (page - 1) * per_page
end = start + per_page
page_modules = all_modules[start:end]
modules = [{'path': m['path'], 'name': m.get('name', '')} for m in page_modules]
return jsonify({
'modules': modules,
'total': len(all_modules),
'page': page,
'has_more': end < len(all_modules),
})
except Exception as e:
return jsonify({'modules': [], 'error': str(e)})
@offense_bp.route('/module/info', methods=['POST'])
@login_required
def module_info():
"""Get module info."""
data = request.get_json(silent=True) or {}
module_path = data.get('module_path', '').strip()
if not module_path:
return jsonify({'error': 'No module path provided'})
# Try offline library first
try:
from core.msf_modules import get_module_info
info = get_module_info(module_path)
if info:
return jsonify({
'path': module_path,
'name': info.get('name', ''),
'description': info.get('description', ''),
'author': info.get('author', []),
'platforms': info.get('platforms', []),
'reliability': info.get('reliability', ''),
'options': info.get('options', []),
'notes': info.get('notes', ''),
})
except Exception:
pass
# Try live MSF
try:
from core.msf_interface import get_msf_interface
msf = get_msf_interface()
if msf.is_connected:
info = msf.get_module_info(module_path)
if info:
return jsonify({
'path': module_path,
**info
})
except Exception:
pass
return jsonify({'error': f'Module not found: {module_path}'})
@offense_bp.route('/module/run', methods=['POST'])
@login_required
def run_module():
"""Run an MSF module and stream output via SSE."""
data = request.get_json(silent=True) or {}
module_path = data.get('module_path', '').strip()
options = data.get('options', {})
if not module_path:
return jsonify({'error': 'No module_path provided'})
job_id = str(uuid.uuid4())
stop_event = threading.Event()
_running_jobs[job_id] = stop_event
def generate():
yield f"data: {json.dumps({'status': 'running', 'job_id': job_id})}\n\n"
try:
from core.msf_interface import get_msf_interface
msf = get_msf_interface()
if not msf.is_connected:
yield f"data: {json.dumps({'error': 'Not connected to MSF'})}\n\n"
return
result = msf.run_module(module_path, options)
for line in (result.cleaned_output or '').splitlines():
if stop_event.is_set():
break
yield f"data: {json.dumps({'line': line})}\n\n"
yield f"data: {json.dumps({'done': True, 'findings': result.findings, 'services': result.services, 'open_ports': result.open_ports})}\n\n"
except Exception as e:
yield f"data: {json.dumps({'error': str(e)})}\n\n"
finally:
_running_jobs.pop(job_id, None)
return Response(generate(), mimetype='text/event-stream',
headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'})
@offense_bp.route('/module/stop', methods=['POST'])
@login_required
def stop_module():
"""Stop a running module job."""
data = request.get_json(silent=True) or {}
job_id = data.get('job_id', '')
ev = _running_jobs.get(job_id)
if ev:
ev.set()
return jsonify({'stopped': bool(ev)})