diff --git a/app/__init__.py b/app/__init__.py index 1aafdb8..3ea6a6f 100755 --- a/app/__init__.py +++ b/app/__init__.py @@ -93,6 +93,7 @@ def create_app(config_class=Config): from app.file_manager import bp as file_manager_bp # Import file_manager blueprint from app.notes import notes_bp from app.metaspidey import metaspidey_bp + from app.bettermitm import bettermitm_bp app.register_blueprint(auth_bp) app.register_blueprint(core_bp) @@ -104,6 +105,7 @@ def create_app(config_class=Config): app.register_blueprint(file_manager_bp, url_prefix='/file_manager') # Register file_manager blueprint app.register_blueprint(notes_bp, url_prefix='/notes') app.register_blueprint(metaspidey_bp, url_prefix='/metaspidey') + app.register_blueprint(bettermitm_bp, url_prefix='/bettermitm') try: from app.gui import init_gui_module, register_gui_commands, gui_context_processor diff --git a/app/bettermitm/README.md b/app/bettermitm/README.md new file mode 100644 index 0000000..ed8024c --- /dev/null +++ b/app/bettermitm/README.md @@ -0,0 +1,326 @@ +# BetterMITM - Advanced Bettercap GUI Interface + +BetterMITM es una interfaz web avanzada para Bettercap que proporciona una GUI completa para operaciones de seguridad de red y pruebas de penetración. + +## Características Principales + +### 🎯 **Gestión de Dispositivos** +- Descubrimiento automático de dispositivos en red +- Tracking en tiempo real de hosts activos +- Información detallada de cada dispositivo (MAC, IP, vendor, OS) +- Sistema de targeting para ataques específicos +- Visualización de mapa de red + +### ⚡ **Módulos de Ataque** +- **ARP Spoofing**: Ataques man-in-the-middle bidireccionales +- **DNS Spoofing**: Redirección de dominios con reglas personalizadas +- **HTTP Proxy**: Proxy transparente con logging e inyección +- **Packet Sniffing**: Captura de paquetes con filtros BPF +- **Network Discovery**: Escaneo comprehensivo de redes + +### 📊 **Monitoreo en Tiempo Real** +- Dashboard con estadísticas actualizadas +- Console interactiva para comandos Bettercap +- Logs de actividad y eventos de seguridad +- Notificaciones push para eventos importantes +- Estado de ataques activos + +### 🔧 **Configuración Avanzada** +- Selección de interfaces de red +- Configuración de parámetros de ataque +- Scripts personalizados de Bettercap +- Exportación e importación de datos +- Control de emergencia (stop all) + +## Requisitos del Sistema + +### Software Necesario +```bash +# Instalar Bettercap +sudo apt update && sudo apt install bettercap + +# O usando Go (recomendado para última versión) +go install github.com/bettercap/bettercap@latest +``` + +### Dependencias Python +Las dependencias se instalan automáticamente con el framework: +- `requests>=2.31.0` - Para comunicación con API de Bettercap +- `psutil>=5.9.8` - Para información de interfaces de red +- `flask-wtf>=1.2.1` - Para formularios web seguros + +### Permisos del Sistema +```bash +# Dar permisos a Bettercap para operaciones de red +sudo setcap cap_net_raw,cap_net_admin=eip $(which bettercap) + +# O ejecutar como root (no recomendado para producción) +sudo bettercap +``` + +## Configuración Inicial + +### 1. Configurar Bettercap +```bash +# Crear archivo de configuración +mkdir -p ~/.bettercap +cat > ~/.bettercap/config.yml << EOF +api: + rest: + enabled: true + port: 8081 + username: admin + password: admin123 + certificate: "" + key: "" +EOF +``` + +### 2. Verificar Interfaces de Red +```bash +# Listar interfaces disponibles +ip link show + +# Para WiFi, habilitar modo monitor (opcional) +sudo airmon-ng start wlan0 +``` + +## Uso de BetterMITM + +### Inicio Rápido + +1. **Acceder a la interfaz**: Navegar a `/bettermitm` +2. **Iniciar Bettercap**: Hacer clic en "Start Bettercap" y seleccionar interfaz +3. **Descubrir red**: Ir a "Network Discovery" y iniciar escaneo +4. **Configurar ataques**: Usar los módulos de ataque según necesidades + +### Dashboard Principal + +El dashboard proporciona: +- **Estado de Bettercap**: Running/Stopped con interfaz activa +- **Estadísticas**: Total de dispositivos, activos, targeted, bajo ataque +- **Mapa de red**: Visualización gráfica de dispositivos descubiertos +- **Acciones rápidas**: Botones para operaciones comunes + +### Módulos de Ataque + +#### ARP Spoofing +``` +Target IP: 192.168.1.100 (dispositivo objetivo) +Gateway IP: 192.168.1.1 (router/gateway) +☑ Bidirectional: Ataque en ambas direcciones +☑ Continuous: Mantener ataque activo +``` + +#### DNS Spoofing +``` +Target Domain: example.com +Spoofed IP: 192.168.1.50 (tu servidor) +☐ Spoof All Domains: Redirigir todos los dominios +``` + +#### HTTP Proxy +``` +Proxy Port: 8080 +☑ Transparent: Proxy transparente +☑ Log Requests: Registrar peticiones HTTP +Custom JS: alert('Intercepted!'); (opcional) +``` + +#### Packet Sniffer +``` +Protocols: ☑ TCP ☑ UDP ☑ HTTP ☐ HTTPS +BPF Filter: host 192.168.1.100 or port 80 +Max Packets: 1000 (0 para ilimitado) +``` + +### Consola Interactiva + +La consola permite ejecutar comandos Bettercap directamente: +``` +bettercap> help +bettercap> net.recon on +bettercap> net.show +bettercap> set arp.spoof.targets 192.168.1.100 +bettercap> arp.spoof on +``` + +## Casos de Uso Comunes + +### 1. Auditoria de Red Corporativa +```bash +1. Escanear red: Network Discovery → Start Scan +2. Identificar dispositivos críticos +3. Verificar segmentación de red +4. Documentar vulnerabilidades encontradas +``` + +### 2. Pruebas de Penetración WiFi +```bash +1. Configurar interfaz en modo monitor +2. Descubrir redes disponibles +3. Capturar handshakes WiFi +4. Analizar tráfico de red +``` + +### 3. Man-in-the-Middle Testing +```bash +1. ARP Spoof entre cliente y gateway +2. Activar HTTP proxy para interceptar tráfico +3. Analizar datos sensibles +4. DNS spoof para phishing tests +``` + +### 4. Análisis de Tráfico de Red +```bash +1. Configurar packet sniffer con filtros +2. Capturar tráfico específico +3. Analizar protocolos y servicios +4. Identificar anomalías +``` + +## Seguridad y Consideraciones + +### ⚠️ **Advertencias Importantes** + +1. **Solo para uso autorizado**: Usar únicamente en redes propias o con permiso explícito +2. **Impacto en red**: Los ataques pueden afectar el rendimiento de la red +3. **Detección**: Las herramientas de seguridad pueden detectar estas actividades +4. **Responsabilidad legal**: El uso no autorizado puede tener consecuencias legales + +### 🔒 **Buenas Prácticas** + +- Usar en entornos de laboratorio/testing +- Obtener autorizaciones por escrito +- Documentar todas las actividades +- Tener plan de rollback para emergencias +- No interceptar datos personales sin consentimiento + +### 🛡️ **Medidas de Protección** + +```python +# El sistema incluye protecciones automáticas: +- Rate limiting para prevenir abuso +- Logging completo de actividades +- Autenticación requerida +- CSRF protection habilitada +- Timeouts para operaciones largas +``` + +## Troubleshooting + +### Errores Comunes + +#### Bettercap no inicia +```bash +# Verificar permisos +getcap $(which bettercap) + +# Verificar puerto disponible +netstat -tulnp | grep 8081 + +# Verificar interfaz válida +ip link show +``` + +#### No se descubren dispositivos +```bash +# Verificar conectividad +ping -c 1 192.168.1.1 + +# Verificar tabla ARP +arp -a + +# Usar escaneo más agresivo +nmap -sn 192.168.1.0/24 +``` + +#### Ataques no funcionan +```bash +# Verificar routing +route -n + +# Verificar iptables +sudo iptables -L + +# Verificar forwarding IP +cat /proc/sys/net/ipv4/ip_forward +``` + +### Logs y Debugging + +Los logs se encuentran en: +- **Sistema**: `/var/log/coresecframe/` +- **Bettercap**: `~/.bettercap/log` +- **Aplicación**: Console tab en la interfaz + +## API Reference + +### Endpoints Principales + +```http +GET /bettermitm/api/status # Estado actual +POST /bettermitm/api/start # Iniciar Bettercap +POST /bettermitm/api/stop # Detener Bettercap +GET /bettermitm/api/hosts # Dispositivos descubiertos +POST /bettermitm/api/scan/start # Iniciar escaneo +POST /bettermitm/api/arp/start # Iniciar ARP spoofing +POST /bettermitm/api/dns/start # Iniciar DNS spoofing +POST /bettermitm/api/command # Ejecutar comando custom +``` + +### Eventos WebSocket (Futuro) + +```javascript +// Real-time events para actualizaciones +socket.on('device_discovered', (device) => {...}); +socket.on('attack_status', (status) => {...}); +socket.on('packet_captured', (packet) => {...}); +``` + +## Desarrollo y Contribución + +### Estructura del Código +``` +app/bettermitm/ +├── __init__.py # Blueprint registration +├── routes.py # API endpoints y web routes +├── forms.py # Formularios WTF +├── bettercap_manager.py # Gestión de Bettercap +├── device_tracker.py # Tracking de dispositivos +└── templates/ + └── bettermitm/ + └── index.html # Interfaz principal +``` + +### Agregar Nuevas Funciones + +1. **Nuevo módulo de ataque**: Agregar en `bettercap_manager.py` +2. **Nueva interfaz**: Modificar `templates/index.html` y `static/js/bettermitm.js` +3. **Nuevos formularios**: Agregar en `forms.py` +4. **Nuevas rutas API**: Agregar en `routes.py` + +## Changelog + +### v1.0.0 (Inicial) +- ✅ Dashboard con estadísticas en tiempo real +- ✅ Network Discovery y device tracking +- ✅ ARP/DNS/HTTP spoofing +- ✅ Packet sniffer con filtros +- ✅ Console interactiva +- ✅ Sistema de targeting de dispositivos +- ✅ Export/import de datos +- ✅ Emergency stop functionality + +### Próximas Funciones (Roadmap) +- 🔄 WiFi handshake capture +- 🔄 Eventos WebSocket en tiempo real +- 🔄 Plugins personalizados +- 🔄 Reporting automático +- 🔄 Integration con otras herramientas +- 🔄 Mobile interface +- 🔄 Docker deployment + +--- + +**⚠️ Disclaimer**: Esta herramienta está diseñada exclusivamente para pruebas de seguridad autorizadas y propósitos educativos. El uso indebido de esta herramienta puede violar leyes locales e internacionales. Los usuarios son completamente responsables del uso ético y legal de esta aplicación. \ No newline at end of file diff --git a/app/bettermitm/__init__.py b/app/bettermitm/__init__.py new file mode 100644 index 0000000..11e3199 --- /dev/null +++ b/app/bettermitm/__init__.py @@ -0,0 +1,16 @@ +""" +BetterMITM - Advanced Bettercap GUI Interface +Enhanced web interface for network security testing with Bettercap +""" + +from flask import Blueprint + +bettermitm_bp = Blueprint( + 'bettermitm', + __name__, + template_folder='templates', + static_folder='static', + url_prefix='/bettermitm' +) + +from . import routes \ No newline at end of file diff --git a/app/bettermitm/bettercap_manager.py b/app/bettermitm/bettercap_manager.py new file mode 100644 index 0000000..afca390 --- /dev/null +++ b/app/bettermitm/bettercap_manager.py @@ -0,0 +1,549 @@ +""" +Bettercap Manager +Handles communication with Bettercap via REST API and command execution +""" + +import json +import time +import requests +import subprocess +import threading +import logging +from datetime import datetime +from typing import Dict, List, Optional, Any +import psutil +import os +import signal + + +class BettercapManager: + """Manages Bettercap instances and API communication""" + + def __init__(self): + self.api_url = "http://127.0.0.1:8081/" + self.username = "admin" + self.password = "admin123" + self.session_token = None + self.process = None + self.is_running = False + self.logger = logging.getLogger(__name__) + + # Configure logging + if not self.logger.handlers: + handler = logging.StreamHandler() + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + handler.setFormatter(formatter) + self.logger.addHandler(handler) + self.logger.setLevel(logging.INFO) + + # State tracking + self.discovered_hosts = {} + self.active_attacks = {} + self.captured_packets = [] + self.network_interfaces = [] + self.current_interface = None + + # Callbacks for real-time updates + self.callbacks = { + 'host_discovered': [], + 'packet_captured': [], + 'attack_status': [], + 'error': [] + } + + def start_bettercap(self, interface: str = None, api_port: int = 8081, sudo_password: str = None) -> bool: + """Start Bettercap with REST API enabled""" + try: + if self.is_running: + self.logger.warning("Bettercap is already running") + return True + + # Check if bettercap is available + base_cmd = ['bettercap'] if not sudo_password else ['sudo', '-S', 'bettercap'] + try: + test_cmd = ['bettercap', '-version'] if not sudo_password else ['sudo', '-n', 'bettercap', '-version'] + subprocess.run(test_cmd, capture_output=True, check=True, timeout=5) + except subprocess.CalledProcessError as e: + if sudo_password: + self.logger.info("Sudo access required for Bettercap (this is normal)") + else: + self.logger.error(f"Bettercap version check failed: {e}") + return False + except subprocess.TimeoutExpired: + self.logger.error("Bettercap version check timed out") + return False + except FileNotFoundError: + self.logger.error("Bettercap not found in PATH. Please install bettercap first.") + return False + + # Build command + cmd = base_cmd.copy() + + if interface: + # Validate interface exists + available_interfaces = [iface['name'] for iface in self.get_network_interfaces()] + if interface not in available_interfaces: + self.logger.error(f"Interface '{interface}' not found. Available: {available_interfaces}") + return False + cmd.extend(["-iface", interface]) + self.current_interface = interface + + # Enable REST API with more robust configuration + api_config = [ + "api.rest on", + f"set api.rest.port {api_port}", + f"set api.rest.username {self.username}", + f"set api.rest.password {self.password}", + "set api.rest.allow-origin *" + ] + + cmd.extend(["-eval", "; ".join(api_config)]) + + # Start process + self.logger.info(f"Starting Bettercap: {' '.join(cmd)}") + + if sudo_password: + # Start process with sudo and password + self.process = subprocess.Popen( + cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1, + universal_newlines=True + ) + # Send password to sudo + try: + self.process.stdin.write(sudo_password + '\n') + self.process.stdin.flush() + self.process.stdin.close() + except Exception as e: + self.logger.error(f"Failed to send sudo password: {e}") + self.process.terminate() + return False + else: + # Start process normally + self.process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1, + universal_newlines=True + ) + + # Wait for process to start and check if it's still running + time.sleep(2) + if self.process.poll() is not None: + # Process has already terminated + stdout, stderr = self.process.communicate() + self.logger.error(f"Bettercap process terminated immediately:") + self.logger.error(f"STDOUT: {stdout}") + self.logger.error(f"STDERR: {stderr}") + return False + + # Update API URL + self.api_url = f"http://127.0.0.1:{api_port}" + + # Wait for API to be ready with multiple attempts + max_attempts = 10 + for attempt in range(max_attempts): + self.logger.info(f"Attempting API connection (attempt {attempt + 1}/{max_attempts})") + time.sleep(1) + + if self.authenticate(): + self.is_running = True + self.logger.info("Bettercap started successfully") + + # Start monitoring thread + monitor_thread = threading.Thread(target=self._monitor_bettercap, daemon=True) + monitor_thread.start() + + return True + + # Check if process is still running + if self.process.poll() is not None: + stdout, stderr = self.process.communicate() + self.logger.error(f"Bettercap process died during startup:") + self.logger.error(f"STDOUT: {stdout}") + self.logger.error(f"STDERR: {stderr}") + return False + + self.logger.error("Failed to authenticate with Bettercap API after multiple attempts") + self.stop_bettercap() + return False + + except Exception as e: + self.logger.error(f"Failed to start Bettercap: {e}") + if hasattr(self, 'process') and self.process: + try: + stdout, stderr = self.process.communicate(timeout=1) + self.logger.error(f"Process output - STDOUT: {stdout}") + self.logger.error(f"Process output - STDERR: {stderr}") + except: + pass + return False + + def stop_bettercap(self) -> bool: + """Stop Bettercap process""" + try: + if self.process: + self.process.terminate() + time.sleep(2) + + if self.process.poll() is None: + self.process.kill() + + self.process = None + + self.is_running = False + self.session_token = None + self.logger.info("Bettercap stopped") + return True + + except Exception as e: + self.logger.error(f"Error stopping Bettercap: {e}") + return False + + def authenticate(self) -> bool: + """Authenticate with Bettercap API""" + try: + auth_data = { + "username": self.username, + "password": self.password + } + + response = requests.post( + f"{self.api_url}/api/session", + json=auth_data, + timeout=5 + ) + + if response.status_code == 200: + self.session_token = response.cookies.get('session') + return True + + return False + + except Exception as e: + self.logger.error(f"Authentication failed: {e}") + return False + + def execute_command(self, command: str) -> Dict[str, Any]: + """Execute a command via Bettercap API""" + try: + if not self.session_token: + if not self.authenticate(): + return {"success": False, "error": "Authentication failed"} + + cookies = {"session": self.session_token} + + response = requests.post( + f"{self.api_url}/api/session", + json={"cmd": command}, + cookies=cookies, + timeout=10 + ) + + if response.status_code == 200: + return {"success": True, "data": response.json()} + else: + return {"success": False, "error": f"HTTP {response.status_code}"} + + except Exception as e: + self.logger.error(f"Command execution failed: {e}") + return {"success": False, "error": str(e)} + + def get_network_interfaces(self) -> List[Dict[str, str]]: + """Get available network interfaces""" + interfaces = [] + try: + for interface, addrs in psutil.net_if_addrs().items(): + for addr in addrs: + if addr.family == 2: # IPv4 + interfaces.append({ + 'name': interface, + 'ip': addr.address, + 'netmask': addr.netmask + }) + break + except Exception as e: + self.logger.error(f"Error getting interfaces: {e}") + + return interfaces + + def start_network_discovery(self) -> Dict[str, Any]: + """Start network discovery""" + commands = [ + "net.recon on", + "net.probe on" + ] + + results = [] + for cmd in commands: + result = self.execute_command(cmd) + results.append(result) + + return {"success": all(r["success"] for r in results), "results": results} + + def stop_network_discovery(self) -> Dict[str, Any]: + """Stop network discovery""" + commands = [ + "net.recon off", + "net.probe off" + ] + + results = [] + for cmd in commands: + result = self.execute_command(cmd) + results.append(result) + + return {"success": all(r["success"] for r in results), "results": results} + + def get_discovered_hosts(self) -> List[Dict[str, Any]]: + """Get list of discovered network hosts""" + result = self.execute_command("net.show") + + if result["success"]: + try: + # Parse hosts from the response + hosts = [] + # Implementation depends on Bettercap's response format + return hosts + except Exception as e: + self.logger.error(f"Error parsing hosts: {e}") + + return [] + + def start_arp_spoofing(self, target_ip: str, gateway_ip: str, bidirectional: bool = True) -> Dict[str, Any]: + """Start ARP spoofing attack""" + try: + commands = [ + f"set arp.spoof.targets {target_ip}", + "arp.spoof on" + ] + + if bidirectional: + commands.insert(1, f"set arp.spoof.fullduplex true") + + attack_id = f"arp_{target_ip}_{int(time.time())}" + + results = [] + for cmd in commands: + result = self.execute_command(cmd) + results.append(result) + + if all(r["success"] for r in results): + self.active_attacks[attack_id] = { + 'type': 'arp_spoofing', + 'target_ip': target_ip, + 'gateway_ip': gateway_ip, + 'bidirectional': bidirectional, + 'started': datetime.now(), + 'status': 'active' + } + + self._trigger_callback('attack_status', { + 'attack_id': attack_id, + 'type': 'arp_spoofing', + 'status': 'started', + 'target': target_ip + }) + + return { + "success": all(r["success"] for r in results), + "attack_id": attack_id, + "results": results + } + + except Exception as e: + self.logger.error(f"ARP spoofing start failed: {e}") + return {"success": False, "error": str(e)} + + def stop_arp_spoofing(self) -> Dict[str, Any]: + """Stop ARP spoofing attack""" + result = self.execute_command("arp.spoof off") + + # Update active attacks + for attack_id, attack in self.active_attacks.items(): + if attack['type'] == 'arp_spoofing': + attack['status'] = 'stopped' + self._trigger_callback('attack_status', { + 'attack_id': attack_id, + 'type': 'arp_spoofing', + 'status': 'stopped' + }) + + return result + + def start_dns_spoofing(self, domain: str, spoofed_ip: str, all_domains: bool = False) -> Dict[str, Any]: + """Start DNS spoofing attack""" + try: + commands = [] + + if all_domains: + commands.append("set dns.spoof.all true") + else: + commands.append(f"set dns.spoof.domains {domain}") + + commands.extend([ + f"set dns.spoof.address {spoofed_ip}", + "dns.spoof on" + ]) + + attack_id = f"dns_{domain}_{int(time.time())}" + + results = [] + for cmd in commands: + result = self.execute_command(cmd) + results.append(result) + + if all(r["success"] for r in results): + self.active_attacks[attack_id] = { + 'type': 'dns_spoofing', + 'domain': domain, + 'spoofed_ip': spoofed_ip, + 'all_domains': all_domains, + 'started': datetime.now(), + 'status': 'active' + } + + self._trigger_callback('attack_status', { + 'attack_id': attack_id, + 'type': 'dns_spoofing', + 'status': 'started', + 'domain': domain + }) + + return { + "success": all(r["success"] for r in results), + "attack_id": attack_id, + "results": results + } + + except Exception as e: + self.logger.error(f"DNS spoofing start failed: {e}") + return {"success": False, "error": str(e)} + + def stop_dns_spoofing(self) -> Dict[str, Any]: + """Stop DNS spoofing attack""" + result = self.execute_command("dns.spoof off") + + # Update active attacks + for attack_id, attack in self.active_attacks.items(): + if attack['type'] == 'dns_spoofing': + attack['status'] = 'stopped' + self._trigger_callback('attack_status', { + 'attack_id': attack_id, + 'type': 'dns_spoofing', + 'status': 'stopped' + }) + + return result + + def start_packet_sniffer(self, protocols: List[str], bpf_filter: str = "", max_packets: int = 0) -> Dict[str, Any]: + """Start packet sniffing""" + try: + commands = [] + + if bpf_filter: + commands.append(f"set net.sniff.filter '{bpf_filter}'") + + commands.append("net.sniff on") + + results = [] + for cmd in commands: + result = self.execute_command(cmd) + results.append(result) + + return {"success": all(r["success"] for r in results), "results": results} + + except Exception as e: + self.logger.error(f"Packet sniffer start failed: {e}") + return {"success": False, "error": str(e)} + + def stop_packet_sniffer(self) -> Dict[str, Any]: + """Stop packet sniffing""" + return self.execute_command("net.sniff off") + + def start_http_proxy(self, port: int = 8080, transparent: bool = True) -> Dict[str, Any]: + """Start HTTP proxy""" + try: + commands = [ + f"set http.proxy.port {port}", + "http.proxy on" + ] + + if transparent: + commands.insert(1, "set http.proxy.transparent true") + + results = [] + for cmd in commands: + result = self.execute_command(cmd) + results.append(result) + + return {"success": all(r["success"] for r in results), "results": results} + + except Exception as e: + self.logger.error(f"HTTP proxy start failed: {e}") + return {"success": False, "error": str(e)} + + def stop_http_proxy(self) -> Dict[str, Any]: + """Stop HTTP proxy""" + return self.execute_command("http.proxy off") + + def get_attack_status(self) -> Dict[str, Any]: + """Get status of all active attacks""" + return { + 'is_running': self.is_running, + 'active_attacks': self.active_attacks, + 'discovered_hosts': len(self.discovered_hosts), + 'current_interface': self.current_interface + } + + def register_callback(self, event_type: str, callback): + """Register callback for real-time events""" + if event_type in self.callbacks: + self.callbacks[event_type].append(callback) + + def _trigger_callback(self, event_type: str, data: Any): + """Trigger callbacks for an event""" + if event_type in self.callbacks: + for callback in self.callbacks[event_type]: + try: + callback(data) + except Exception as e: + self.logger.error(f"Callback error: {e}") + + def _monitor_bettercap(self): + """Monitor Bettercap process and events""" + while self.is_running and self.process: + try: + # Check if process is still running + if self.process.poll() is not None: + self.is_running = False + self._trigger_callback('error', "Bettercap process terminated") + break + + # Poll for events/updates + # This would typically involve reading from Bettercap's event stream + time.sleep(1) + + except Exception as e: + self.logger.error(f"Monitor error: {e}") + time.sleep(5) + + def cleanup(self): + """Cleanup resources""" + try: + self.stop_bettercap() + self.callbacks.clear() + self.active_attacks.clear() + self.discovered_hosts.clear() + except Exception as e: + self.logger.error(f"Cleanup error: {e}") + + +# Global instance +bettercap_manager = BettercapManager() \ No newline at end of file diff --git a/app/bettermitm/device_tracker.py b/app/bettermitm/device_tracker.py new file mode 100644 index 0000000..deb37a0 --- /dev/null +++ b/app/bettermitm/device_tracker.py @@ -0,0 +1,376 @@ +""" +Device Tracker +Manages network device discovery and tracking +""" + +import json +import time +from datetime import datetime, timedelta +from typing import Dict, List, Optional, Any +import threading +import logging + + +class DeviceTracker: + """Tracks discovered network devices and their properties""" + + def __init__(self): + self.devices = {} # MAC -> device info + self.ip_to_mac = {} # IP -> MAC mapping + self.logger = logging.getLogger(__name__) + self.lock = threading.Lock() + + # Device tracking settings + self.device_timeout = 300 # 5 minutes + self.cleanup_interval = 60 # 1 minute + + # Start cleanup thread + self.cleanup_thread = threading.Thread(target=self._cleanup_stale_devices, daemon=True) + self.cleanup_thread.start() + + def add_device(self, device_info: Dict[str, Any]) -> bool: + """Add or update a device""" + try: + with self.lock: + mac = device_info.get('mac', '').lower() + ip = device_info.get('ip') + + if not mac and not ip: + return False + + # Generate MAC if not provided (for IP-only entries) + if not mac and ip: + mac = f"unknown_{ip.replace('.', '_')}" + + # Update device info + now = datetime.now() + + if mac in self.devices: + # Update existing device + existing = self.devices[mac] + existing.update(device_info) + existing['last_seen'] = now.isoformat() + existing['updated_at'] = now.isoformat() + else: + # Add new device + self.devices[mac] = { + 'mac': mac, + 'ip': ip, + 'hostname': device_info.get('hostname', ''), + 'vendor': device_info.get('vendor', 'Unknown'), + 'os': device_info.get('os', ''), + 'device_type': device_info.get('device_type', 'Unknown'), + 'first_seen': now.isoformat(), + 'last_seen': now.isoformat(), + 'updated_at': now.isoformat(), + 'packets_sent': device_info.get('packets_sent', 0), + 'packets_received': device_info.get('packets_received', 0), + 'bytes_sent': device_info.get('bytes_sent', 0), + 'bytes_received': device_info.get('bytes_received', 0), + 'open_ports': device_info.get('open_ports', []), + 'services': device_info.get('services', {}), + 'is_gateway': device_info.get('is_gateway', False), + 'is_broadcast': device_info.get('is_broadcast', False), + 'targeted': device_info.get('targeted', False), + 'attacks': device_info.get('attacks', {}), + 'notes': device_info.get('notes', ''), + 'risk_level': device_info.get('risk_level', 'low'), + 'status': 'online' + } + + # Update IP mapping + if ip: + self.ip_to_mac[ip] = mac + + return True + + except Exception as e: + self.logger.error(f"Error adding device: {e}") + return False + + def get_device_by_ip(self, ip: str) -> Optional[Dict[str, Any]]: + """Get device by IP address""" + with self.lock: + mac = self.ip_to_mac.get(ip) + if mac: + return self.devices.get(mac) + return None + + def get_device_by_mac(self, mac: str) -> Optional[Dict[str, Any]]: + """Get device by MAC address""" + with self.lock: + return self.devices.get(mac.lower()) + + def get_all_devices(self) -> List[Dict[str, Any]]: + """Get all tracked devices""" + with self.lock: + return list(self.devices.values()) + + def get_active_devices(self, minutes: int = 10) -> List[Dict[str, Any]]: + """Get devices active within the last N minutes""" + cutoff = datetime.now() - timedelta(minutes=minutes) + + with self.lock: + active_devices = [] + for device in self.devices.values(): + try: + last_seen = datetime.fromisoformat(device['last_seen']) + if last_seen > cutoff: + active_devices.append(device) + except: + continue + + return active_devices + + def get_targeted_devices(self) -> List[Dict[str, Any]]: + """Get devices marked for targeting""" + with self.lock: + return [device for device in self.devices.values() if device.get('targeted', False)] + + def update_device_stats(self, ip: str, packets_sent: int = 0, packets_received: int = 0, + bytes_sent: int = 0, bytes_received: int = 0) -> bool: + """Update device network statistics""" + try: + device = self.get_device_by_ip(ip) + if device: + with self.lock: + device['packets_sent'] += packets_sent + device['packets_received'] += packets_received + device['bytes_sent'] += bytes_sent + device['bytes_received'] += bytes_received + device['last_seen'] = datetime.now().isoformat() + return True + return False + + except Exception as e: + self.logger.error(f"Error updating device stats: {e}") + return False + + def update_device_attack_status(self, ip: str, attack_type: str, active: bool) -> bool: + """Update device attack status""" + try: + device = self.get_device_by_ip(ip) + if device: + with self.lock: + if 'attacks' not in device: + device['attacks'] = {} + + device['attacks'][attack_type] = { + 'active': active, + 'started_at': datetime.now().isoformat() if active else device['attacks'].get(attack_type, {}).get('started_at'), + 'stopped_at': None if active else datetime.now().isoformat() + } + + device['last_seen'] = datetime.now().isoformat() + return True + return False + + except Exception as e: + self.logger.error(f"Error updating attack status: {e}") + return False + + def add_device_service(self, ip: str, port: int, service: str, version: str = '') -> bool: + """Add discovered service to device""" + try: + device = self.get_device_by_ip(ip) + if device: + with self.lock: + if 'services' not in device: + device['services'] = {} + + device['services'][str(port)] = { + 'service': service, + 'version': version, + 'discovered_at': datetime.now().isoformat() + } + + if port not in device.get('open_ports', []): + device.setdefault('open_ports', []).append(port) + + device['last_seen'] = datetime.now().isoformat() + return True + return False + + except Exception as e: + self.logger.error(f"Error adding service: {e}") + return False + + def set_device_os(self, ip: str, os_info: str) -> bool: + """Set device OS information""" + try: + device = self.get_device_by_ip(ip) + if device: + with self.lock: + device['os'] = os_info + device['last_seen'] = datetime.now().isoformat() + return True + return False + + except Exception as e: + self.logger.error(f"Error setting OS info: {e}") + return False + + def set_device_vendor(self, mac: str, vendor: str) -> bool: + """Set device vendor information""" + try: + device = self.get_device_by_mac(mac) + if device: + with self.lock: + device['vendor'] = vendor + device['last_seen'] = datetime.now().isoformat() + return True + return False + + except Exception as e: + self.logger.error(f"Error setting vendor: {e}") + return False + + def mark_device_offline(self, ip: str) -> bool: + """Mark device as offline""" + try: + device = self.get_device_by_ip(ip) + if device: + with self.lock: + device['status'] = 'offline' + device['last_seen'] = datetime.now().isoformat() + return True + return False + + except Exception as e: + self.logger.error(f"Error marking device offline: {e}") + return False + + def remove_device(self, identifier: str) -> bool: + """Remove device by IP or MAC""" + try: + with self.lock: + # Try as IP first + if identifier in self.ip_to_mac: + mac = self.ip_to_mac[identifier] + del self.ip_to_mac[identifier] + if mac in self.devices: + del self.devices[mac] + return True + + # Try as MAC + mac = identifier.lower() + if mac in self.devices: + # Remove IP mapping + device = self.devices[mac] + if device.get('ip') in self.ip_to_mac: + del self.ip_to_mac[device['ip']] + del self.devices[mac] + return True + + return False + + except Exception as e: + self.logger.error(f"Error removing device: {e}") + return False + + def get_device_count(self) -> Dict[str, int]: + """Get device count statistics""" + with self.lock: + total = len(self.devices) + active = len(self.get_active_devices()) + targeted = len(self.get_targeted_devices()) + + return { + 'total': total, + 'active': active, + 'targeted': targeted, + 'offline': total - active + } + + def export_devices(self, format: str = 'json') -> str: + """Export devices to various formats""" + try: + with self.lock: + if format.lower() == 'json': + return json.dumps(self.devices, indent=2, default=str) + elif format.lower() == 'csv': + import csv + import io + + output = io.StringIO() + if self.devices: + fieldnames = ['mac', 'ip', 'hostname', 'vendor', 'os', 'device_type', + 'first_seen', 'last_seen', 'packets_sent', 'packets_received', + 'status', 'targeted'] + writer = csv.DictWriter(output, fieldnames=fieldnames) + writer.writeheader() + + for device in self.devices.values(): + row = {field: device.get(field, '') for field in fieldnames} + writer.writerow(row) + + return output.getvalue() + + return '' + + except Exception as e: + self.logger.error(f"Error exporting devices: {e}") + return '' + + def import_devices(self, data: str, format: str = 'json') -> int: + """Import devices from various formats""" + try: + imported_count = 0 + + if format.lower() == 'json': + devices_data = json.loads(data) + + for device_info in devices_data.values() if isinstance(devices_data, dict) else devices_data: + if self.add_device(device_info): + imported_count += 1 + + return imported_count + + except Exception as e: + self.logger.error(f"Error importing devices: {e}") + return 0 + + def clear_all(self): + """Clear all tracked devices""" + with self.lock: + self.devices.clear() + self.ip_to_mac.clear() + + def _cleanup_stale_devices(self): + """Background thread to cleanup stale devices""" + while True: + try: + time.sleep(self.cleanup_interval) + + cutoff = datetime.now() - timedelta(seconds=self.device_timeout) + stale_devices = [] + + with self.lock: + for mac, device in self.devices.items(): + try: + last_seen = datetime.fromisoformat(device['last_seen']) + if last_seen < cutoff and not device.get('targeted', False): + stale_devices.append(mac) + except: + continue + + # Remove stale devices + for mac in stale_devices: + try: + with self.lock: + if mac in self.devices: + device = self.devices[mac] + if device.get('ip') in self.ip_to_mac: + del self.ip_to_mac[device['ip']] + del self.devices[mac] + self.logger.info(f"Removed stale device: {mac}") + except Exception as e: + self.logger.error(f"Error removing stale device {mac}: {e}") + + except Exception as e: + self.logger.error(f"Cleanup thread error: {e}") + time.sleep(60) # Wait before retrying + + +# Global instance +device_tracker = DeviceTracker() \ No newline at end of file diff --git a/app/bettermitm/forms.py b/app/bettermitm/forms.py new file mode 100644 index 0000000..1864f67 --- /dev/null +++ b/app/bettermitm/forms.py @@ -0,0 +1,172 @@ +""" +BetterMITM Forms +Flask-WTF forms for network security operations +""" + +from flask_wtf import FlaskForm +from wtforms import ( + StringField, SelectField, TextAreaField, BooleanField, + IntegerField, FloatField, SelectMultipleField, HiddenField +) +from wtforms.validators import DataRequired, IPAddress, Optional, NumberRange +from wtforms.widgets import CheckboxInput, ListWidget + + +class MultiCheckboxField(SelectMultipleField): + """Custom field for multiple checkboxes""" + widget = ListWidget(prefix_label=False) + option_widget = CheckboxInput() + + +class NetworkScanForm(FlaskForm): + """Form for network discovery and scanning""" + interface = SelectField('Network Interface', validators=[DataRequired()]) + scan_type = SelectField( + 'Scan Type', + choices=[ + ('arp', 'ARP Scan'), + ('syn', 'SYN Scan'), + ('comprehensive', 'Comprehensive Scan') + ], + default='arp' + ) + target_range = StringField( + 'Target Range', + validators=[Optional()], + render_kw={'placeholder': '192.168.1.0/24 or leave empty for auto-detect'} + ) + timeout = IntegerField( + 'Timeout (seconds)', + validators=[NumberRange(min=1, max=300)], + default=30 + ) + + +class ARPSpoofingForm(FlaskForm): + """Form for ARP spoofing attacks""" + target_ip = StringField('Target IP', validators=[DataRequired(), IPAddress()]) + gateway_ip = StringField('Gateway IP', validators=[DataRequired(), IPAddress()]) + interface = SelectField('Network Interface', validators=[DataRequired()]) + bidirectional = BooleanField('Bidirectional Spoofing', default=True) + continuous = BooleanField('Continuous Attack', default=True) + packet_interval = FloatField( + 'Packet Interval (seconds)', + validators=[NumberRange(min=0.1, max=60.0)], + default=1.0 + ) + + +class DNSSpoofingForm(FlaskForm): + """Form for DNS spoofing attacks""" + target_domain = StringField('Target Domain', validators=[DataRequired()]) + spoofed_ip = StringField('Spoofed IP', validators=[DataRequired(), IPAddress()]) + interface = SelectField('Network Interface', validators=[DataRequired()]) + all_domains = BooleanField('Spoof All Domains', default=False) + custom_rules = TextAreaField( + 'Custom DNS Rules', + render_kw={'placeholder': 'domain.com=1.2.3.4\\nexample.org=5.6.7.8'} + ) + + +class PacketSnifferForm(FlaskForm): + """Form for packet sniffing configuration""" + interface = SelectField('Network Interface', validators=[DataRequired()]) + protocols = MultiCheckboxField( + 'Protocols to Capture', + choices=[ + ('tcp', 'TCP'), + ('udp', 'UDP'), + ('icmp', 'ICMP'), + ('dns', 'DNS'), + ('http', 'HTTP'), + ('https', 'HTTPS'), + ('ftp', 'FTP'), + ('ssh', 'SSH') + ], + default=['tcp', 'udp', 'http'] + ) + filter_expression = StringField( + 'BPF Filter', + render_kw={'placeholder': 'host 192.168.1.1 or port 80'} + ) + max_packets = IntegerField( + 'Max Packets (0 for unlimited)', + validators=[NumberRange(min=0)], + default=1000 + ) + save_to_file = BooleanField('Save to PCAP file') + filename = StringField('Output Filename') + + +class WiFiHandshakeForm(FlaskForm): + """Form for WiFi handshake capture""" + interface = SelectField('WiFi Interface', validators=[DataRequired()]) + target_bssid = StringField('Target BSSID (optional)') + target_essid = StringField('Target ESSID (optional)') + channel = SelectField( + 'Channel', + choices=[('', 'Auto')] + [(str(i), str(i)) for i in range(1, 15)], + default='' + ) + deauth_attack = BooleanField('Enable Deauthentication Attack', default=True) + deauth_interval = IntegerField( + 'Deauth Interval (seconds)', + validators=[NumberRange(min=1, max=60)], + default=5 + ) + + +class ProxyForm(FlaskForm): + """Form for HTTP/HTTPS proxy configuration""" + interface = SelectField('Network Interface', validators=[DataRequired()]) + proxy_port = IntegerField( + 'Proxy Port', + validators=[DataRequired(), NumberRange(min=1024, max=65535)], + default=8080 + ) + transparent = BooleanField('Transparent Proxy', default=True) + https_proxy = BooleanField('Enable HTTPS Proxy', default=True) + log_requests = BooleanField('Log HTTP Requests', default=True) + custom_scripts = TextAreaField( + 'Custom JavaScript Injection', + render_kw={'placeholder': 'alert("Injected by BetterMITM");'} + ) + + +class DeviceTargetForm(FlaskForm): + """Form for targeting specific devices""" + device_mac = StringField('Device MAC Address', validators=[DataRequired()]) + device_ip = StringField('Device IP Address', validators=[DataRequired(), IPAddress()]) + device_name = StringField('Device Name/Alias') + attack_types = MultiCheckboxField( + 'Attack Types', + choices=[ + ('arp_spoof', 'ARP Spoofing'), + ('dns_spoof', 'DNS Spoofing'), + ('packet_sniff', 'Packet Sniffing'), + ('mitm_proxy', 'MITM Proxy'), + ('bandwidth_limit', 'Bandwidth Limiting') + ] + ) + + +class BettercapScriptForm(FlaskForm): + """Form for custom Bettercap scripts""" + script_name = StringField('Script Name', validators=[DataRequired()]) + script_content = TextAreaField( + 'Script Content', + validators=[DataRequired()], + render_kw={'rows': 15} + ) + auto_execute = BooleanField('Auto-execute on load') + + +class NetworkConfigForm(FlaskForm): + """Form for network configuration""" + gateway_override = StringField('Gateway Override (optional)') + dns_servers = StringField( + 'DNS Servers', + render_kw={'placeholder': '8.8.8.8,1.1.1.1'} + ) + monitor_mode = BooleanField('Enable Monitor Mode') + promiscuous_mode = BooleanField('Enable Promiscuous Mode') \ No newline at end of file diff --git a/app/bettermitm/routes.py b/app/bettermitm/routes.py new file mode 100644 index 0000000..2c2bb67 --- /dev/null +++ b/app/bettermitm/routes.py @@ -0,0 +1,982 @@ +""" +BetterMITM Routes +Flask routes for BetterMITM web interface +""" + +import json +import time +from datetime import datetime +from flask import render_template, request, jsonify, flash, redirect, url_for +from flask_login import login_required +from werkzeug.utils import secure_filename + +from . import bettermitm_bp +from .forms import ( + NetworkScanForm, ARPSpoofingForm, DNSSpoofingForm, PacketSnifferForm, + WiFiHandshakeForm, ProxyForm, DeviceTargetForm, BettercapScriptForm, + NetworkConfigForm +) +from .simple_manager import simple_bettercap_manager as bettercap_manager +from .device_tracker import device_tracker + + +@bettermitm_bp.route('/') +@login_required +def index(): + """Main BetterMITM interface""" + # Get all forms for the interface + network_scan_form = NetworkScanForm() + arp_spoof_form = ARPSpoofingForm() + dns_spoof_form = DNSSpoofingForm() + packet_sniffer_form = PacketSnifferForm() + wifi_form = WiFiHandshakeForm() + proxy_form = ProxyForm() + device_form = DeviceTargetForm() + script_form = BettercapScriptForm() + config_form = NetworkConfigForm() + + # Populate interface choices + interfaces = bettercap_manager.get_network_interfaces() + interface_choices = [(iface['name'], f"{iface['name']} ({iface['ip']})") for iface in interfaces] + + # Update form choices + for form in [network_scan_form, arp_spoof_form, dns_spoof_form, + packet_sniffer_form, wifi_form, proxy_form]: + if hasattr(form, 'interface'): + form.interface.choices = interface_choices + + return render_template('bettermitm/index.html', + network_scan_form=network_scan_form, + arp_spoof_form=arp_spoof_form, + dns_spoof_form=dns_spoof_form, + packet_sniffer_form=packet_sniffer_form, + wifi_form=wifi_form, + proxy_form=proxy_form, + device_form=device_form, + script_form=script_form, + config_form=config_form, + interfaces=interfaces) + + +@bettermitm_bp.route('/api/status') +@login_required +def api_status(): + """Get current Bettercap status""" + try: + status = bettercap_manager.get_attack_status() + discovered_hosts = device_tracker.get_all_devices() + + return jsonify({ + 'success': True, + 'bettercap_running': status['is_running'], + 'active_attacks': status['active_attacks'], + 'discovered_hosts': discovered_hosts, + 'current_interface': status['current_interface'], + 'timestamp': datetime.now().isoformat() + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/test') +@login_required +def api_test(): + """Test Bettercap API connection and basic commands""" + try: + result = bettercap_manager.test_api_connection() + + return jsonify({ + 'success': True, + 'test_results': result + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/start', methods=['POST']) +@login_required +def api_start_bettercap(): + """Start Bettercap instance""" + try: + data = request.get_json() or {} + interface = data.get('interface') + api_port = data.get('api_port', 8081) + sudo_password = data.get('sudo_password') # Optional sudo password + + # Check if Bettercap is installed first + installed, check_message = bettercap_manager.check_bettercap_installed() + if not installed: + return jsonify({ + 'success': False, + 'error': check_message + }), 400 + + # Try to start Bettercap + success = bettercap_manager.start_bettercap(interface, api_port, sudo_password) + + if success: + return jsonify({ + 'success': True, + 'message': 'Bettercap started successfully', + 'interface': interface, + 'sudo_used': bool(sudo_password) + }) + else: + # Get more specific error from logs + error_msg = 'Failed to start Bettercap. ' + if not sudo_password: + error_msg += 'Try using sudo if you have permission issues.' + else: + error_msg += 'Check if the sudo password is correct and Bettercap has proper permissions.' + + return jsonify({ + 'success': False, + 'error': error_msg + }), 500 + + except Exception as e: + return jsonify({ + 'success': False, + 'error': f'Unexpected error: {str(e)}' + }), 500 + + +@bettermitm_bp.route('/api/stop', methods=['POST']) +@login_required +def api_stop_bettercap(): + """Stop Bettercap instance""" + try: + if bettercap_manager.stop_bettercap(): + return jsonify({ + 'success': True, + 'message': 'Bettercap stopped successfully' + }) + else: + return jsonify({ + 'success': False, + 'error': 'Failed to stop Bettercap' + }), 500 + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/test') +@login_required +def api_test(): + """Test Bettercap API connection and basic commands""" + try: + result = bettercap_manager.test_api_connection() + + return jsonify({ + 'success': True, + 'test_results': result + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/scan/start', methods=['POST']) +@login_required +def api_start_scan(): + """Start network discovery scan""" + try: + # Check if Bettercap is running + if not bettercap_manager.is_running: + return jsonify({ + 'success': False, + 'error': 'Bettercap is not running. Please start Bettercap first.' + }), 400 + + data = request.get_json() or {} + scan_type = data.get('scan_type', 'arp') + target_range = data.get('target_range') + + # Try to start network discovery + try: + result = bettercap_manager.start_network_discovery() + + if result and result.get('success'): + return jsonify({ + 'success': True, + 'message': f'Network scan started ({scan_type})', + 'scan_type': scan_type, + 'target_range': target_range + }) + else: + return jsonify({ + 'success': False, + 'error': f'Failed to start network scan: {result.get("error", "Unknown error") if result else "No response from Bettercap"}' + }), 500 + + except Exception as scan_error: + return jsonify({ + 'success': False, + 'error': f'Network scan error: {str(scan_error)}' + }), 500 + + except Exception as e: + return jsonify({ + 'success': False, + 'error': f'General error: {str(e)}' + }), 500 + + +@bettermitm_bp.route('/api/scan/stop', methods=['POST']) +@login_required +def api_stop_scan(): + """Stop network discovery scan""" + try: + result = bettercap_manager.stop_network_discovery() + + return jsonify({ + 'success': result['success'], + 'message': 'Network scan stopped' if result['success'] else 'Failed to stop scan' + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/test') +@login_required +def api_test(): + """Test Bettercap API connection and basic commands""" + try: + result = bettercap_manager.test_api_connection() + + return jsonify({ + 'success': True, + 'test_results': result + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/hosts') +@login_required +def api_get_hosts(): + """Get discovered network hosts""" + try: + hosts = device_tracker.get_all_devices() + + return jsonify({ + 'success': True, + 'hosts': hosts, + 'count': len(hosts), + 'timestamp': datetime.now().isoformat() + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/test') +@login_required +def api_test(): + """Test Bettercap API connection and basic commands""" + try: + result = bettercap_manager.test_api_connection() + + return jsonify({ + 'success': True, + 'test_results': result + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/arp/start', methods=['POST']) +@login_required +def api_start_arp_spoof(): + """Start ARP spoofing attack""" + try: + form = ARPSpoofingForm() + if form.validate_on_submit(): + result = bettercap_manager.start_arp_spoofing( + target_ip=form.target_ip.data, + gateway_ip=form.gateway_ip.data, + bidirectional=form.bidirectional.data + ) + + if result['success']: + device_tracker.update_device_attack_status( + form.target_ip.data, 'arp_spoofing', True + ) + + return jsonify({ + 'success': True, + 'message': f'ARP spoofing started against {form.target_ip.data}', + 'attack_id': result['attack_id'] + }) + else: + return jsonify({ + 'success': False, + 'error': result.get('error', 'ARP spoofing failed') + }), 500 + else: + return jsonify({ + 'success': False, + 'error': 'Form validation failed', + 'errors': form.errors + }), 400 + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/test') +@login_required +def api_test(): + """Test Bettercap API connection and basic commands""" + try: + result = bettercap_manager.test_api_connection() + + return jsonify({ + 'success': True, + 'test_results': result + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/arp/stop', methods=['POST']) +@login_required +def api_stop_arp_spoof(): + """Stop ARP spoofing attack""" + try: + result = bettercap_manager.stop_arp_spoofing() + + # Update device attack statuses + for device in device_tracker.get_all_devices(): + if device.get('attacks', {}).get('arp_spoofing'): + device_tracker.update_device_attack_status( + device['ip'], 'arp_spoofing', False + ) + + return jsonify({ + 'success': result['success'], + 'message': 'ARP spoofing stopped' if result['success'] else 'Failed to stop ARP spoofing' + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/test') +@login_required +def api_test(): + """Test Bettercap API connection and basic commands""" + try: + result = bettercap_manager.test_api_connection() + + return jsonify({ + 'success': True, + 'test_results': result + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/dns/start', methods=['POST']) +@login_required +def api_start_dns_spoof(): + """Start DNS spoofing attack""" + try: + form = DNSSpoofingForm() + if form.validate_on_submit(): + result = bettercap_manager.start_dns_spoofing( + domain=form.target_domain.data, + spoofed_ip=form.spoofed_ip.data, + all_domains=form.all_domains.data + ) + + if result['success']: + return jsonify({ + 'success': True, + 'message': f'DNS spoofing started for {form.target_domain.data}', + 'attack_id': result['attack_id'] + }) + else: + return jsonify({ + 'success': False, + 'error': result.get('error', 'DNS spoofing failed') + }), 500 + else: + return jsonify({ + 'success': False, + 'error': 'Form validation failed', + 'errors': form.errors + }), 400 + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/test') +@login_required +def api_test(): + """Test Bettercap API connection and basic commands""" + try: + result = bettercap_manager.test_api_connection() + + return jsonify({ + 'success': True, + 'test_results': result + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/dns/stop', methods=['POST']) +@login_required +def api_stop_dns_spoof(): + """Stop DNS spoofing attack""" + try: + result = bettercap_manager.stop_dns_spoofing() + + return jsonify({ + 'success': result['success'], + 'message': 'DNS spoofing stopped' if result['success'] else 'Failed to stop DNS spoofing' + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/test') +@login_required +def api_test(): + """Test Bettercap API connection and basic commands""" + try: + result = bettercap_manager.test_api_connection() + + return jsonify({ + 'success': True, + 'test_results': result + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/sniffer/start', methods=['POST']) +@login_required +def api_start_sniffer(): + """Start packet sniffer""" + try: + # Check if Bettercap is running + if not bettercap_manager.is_running: + return jsonify({ + 'success': False, + 'error': 'Bettercap is not running. Please start Bettercap first.' + }), 400 + + # Handle both JSON and form data + if request.is_json: + data = request.get_json() or {} + protocols = data.get('protocols', ['tcp', 'udp']) + bpf_filter = data.get('bpf_filter', '') + max_packets = data.get('max_packets', 100) + else: + form = PacketSnifferForm() + if not form.validate_on_submit(): + return jsonify({ + 'success': False, + 'error': 'Form validation failed', + 'errors': form.errors + }), 400 + protocols = form.protocols.data + bpf_filter = form.filter_expression.data + max_packets = form.max_packets.data + + # Try to start packet sniffer + try: + result = bettercap_manager.start_packet_sniffer( + protocols=protocols, + bpf_filter=bpf_filter, + max_packets=max_packets + ) + + if result and result.get('success'): + return jsonify({ + 'success': True, + 'message': 'Packet sniffer started' + }) + else: + return jsonify({ + 'success': False, + 'error': f'Failed to start sniffer: {result.get("error", "Unknown error") if result else "No response from Bettercap"}' + }), 500 + + except Exception as sniffer_error: + return jsonify({ + 'success': False, + 'error': f'Packet sniffer error: {str(sniffer_error)}' + }), 500 + + except Exception as e: + return jsonify({ + 'success': False, + 'error': f'General error: {str(e)}' + }), 500 + + +@bettermitm_bp.route('/api/sniffer/stop', methods=['POST']) +@login_required +def api_stop_sniffer(): + """Stop packet sniffer""" + try: + result = bettercap_manager.stop_packet_sniffer() + + return jsonify({ + 'success': result['success'], + 'message': 'Packet sniffer stopped' if result['success'] else 'Failed to stop sniffer' + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/test') +@login_required +def api_test(): + """Test Bettercap API connection and basic commands""" + try: + result = bettercap_manager.test_api_connection() + + return jsonify({ + 'success': True, + 'test_results': result + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/proxy/start', methods=['POST']) +@login_required +def api_start_proxy(): + """Start HTTP proxy""" + try: + form = ProxyForm() + if form.validate_on_submit(): + result = bettercap_manager.start_http_proxy( + port=form.proxy_port.data, + transparent=form.transparent.data + ) + + return jsonify({ + 'success': result['success'], + 'message': f'HTTP proxy started on port {form.proxy_port.data}' if result['success'] else 'Failed to start proxy' + }) + else: + return jsonify({ + 'success': False, + 'error': 'Form validation failed', + 'errors': form.errors + }), 400 + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/test') +@login_required +def api_test(): + """Test Bettercap API connection and basic commands""" + try: + result = bettercap_manager.test_api_connection() + + return jsonify({ + 'success': True, + 'test_results': result + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/proxy/stop', methods=['POST']) +@login_required +def api_stop_proxy(): + """Stop HTTP proxy""" + try: + result = bettercap_manager.stop_http_proxy() + + return jsonify({ + 'success': result['success'], + 'message': 'HTTP proxy stopped' if result['success'] else 'Failed to stop proxy' + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/test') +@login_required +def api_test(): + """Test Bettercap API connection and basic commands""" + try: + result = bettercap_manager.test_api_connection() + + return jsonify({ + 'success': True, + 'test_results': result + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/command', methods=['POST']) +@login_required +def api_execute_command(): + """Execute custom Bettercap command""" + try: + data = request.get_json() or {} + command = data.get('command', '').strip() + + if not command: + return jsonify({ + 'success': False, + 'error': 'No command provided' + }), 400 + + result = bettercap_manager.execute_command(command) + + return jsonify({ + 'success': result['success'], + 'output': result.get('data'), + 'error': result.get('error'), + 'command': command, + 'timestamp': datetime.now().isoformat() + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/test') +@login_required +def api_test(): + """Test Bettercap API connection and basic commands""" + try: + result = bettercap_manager.test_api_connection() + + return jsonify({ + 'success': True, + 'test_results': result + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/device/target', methods=['POST']) +@login_required +def api_target_device(): + """Target a specific device for attacks""" + try: + form = DeviceTargetForm() + if form.validate_on_submit(): + device_info = { + 'mac': form.device_mac.data, + 'ip': form.device_ip.data, + 'name': form.device_name.data or 'Unknown Device', + 'targeted': True, + 'attack_types': form.attack_types.data, + 'targeted_at': datetime.now().isoformat() + } + + device_tracker.add_device(device_info) + + return jsonify({ + 'success': True, + 'message': f'Device {device_info["ip"]} targeted for attacks', + 'device': device_info + }) + else: + return jsonify({ + 'success': False, + 'error': 'Form validation failed', + 'errors': form.errors + }), 400 + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/test') +@login_required +def api_test(): + """Test Bettercap API connection and basic commands""" + try: + result = bettercap_manager.test_api_connection() + + return jsonify({ + 'success': True, + 'test_results': result + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/interfaces') +@login_required +def api_get_interfaces(): + """Get available network interfaces""" + try: + interfaces = bettercap_manager.get_network_interfaces() + + return jsonify({ + 'success': True, + 'interfaces': interfaces + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/test') +@login_required +def api_test(): + """Test Bettercap API connection and basic commands""" + try: + result = bettercap_manager.test_api_connection() + + return jsonify({ + 'success': True, + 'test_results': result + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/logs') +@login_required +def api_get_logs(): + """Get recent Bettercap logs and events""" + try: + # This would typically read from log files or event streams + logs = [ + { + 'timestamp': datetime.now().isoformat(), + 'level': 'info', + 'message': 'Sample log entry', + 'module': 'arp.spoof' + } + ] + + return jsonify({ + 'success': True, + 'logs': logs + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/test') +@login_required +def api_test(): + """Test Bettercap API connection and basic commands""" + try: + result = bettercap_manager.test_api_connection() + + return jsonify({ + 'success': True, + 'test_results': result + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/cleanup') +@login_required +def cleanup(): + """Cleanup and stop all operations""" + try: + bettercap_manager.cleanup() + device_tracker.clear_all() + + flash('All operations stopped and cleaned up successfully', 'success') + return redirect(url_for('bettermitm.index')) + + except Exception as e: + flash(f'Error during cleanup: {str(e)}', 'error') + return redirect(url_for('bettermitm.index')) + + +@bettermitm_bp.route('/api/diagnose') +@login_required +def api_diagnose(): + """Diagnose Bettercap installation and status""" + try: + diagnosis = { + 'timestamp': datetime.now().isoformat(), + 'system_info': { + 'platform': 'linux', # Can be enhanced to detect actual platform + 'user': 'current_user' # Can be enhanced to get actual user + } + } + + # Check Bettercap installation + installed, install_message = bettercap_manager.check_bettercap_installed() + diagnosis['bettercap_installed'] = installed + diagnosis['install_message'] = install_message + + # Check if running + diagnosis['is_running'] = bettercap_manager.is_running + diagnosis['current_interface'] = bettercap_manager.current_interface + + # Check network interfaces + try: + interfaces = bettercap_manager.get_network_interfaces() + diagnosis['available_interfaces'] = interfaces + except Exception as e: + diagnosis['interfaces_error'] = str(e) + diagnosis['available_interfaces'] = [] + + # Check API connectivity if running + if bettercap_manager.is_running: + try: + import requests + response = requests.get(f"{bettercap_manager.api_url}/api/session", timeout=2) + diagnosis['api_status'] = response.status_code + diagnosis['api_accessible'] = True + except Exception as e: + diagnosis['api_status'] = 'error' + diagnosis['api_accessible'] = False + diagnosis['api_error'] = str(e) + + return jsonify({ + 'success': True, + 'diagnosis': diagnosis + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + +@bettermitm_bp.route('/api/test') +@login_required +def api_test(): + """Test Bettercap API connection and basic commands""" + try: + result = bettercap_manager.test_api_connection() + + return jsonify({ + 'success': True, + 'test_results': result + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 \ No newline at end of file diff --git a/app/bettermitm/simple_manager.py b/app/bettermitm/simple_manager.py new file mode 100644 index 0000000..2ad4175 --- /dev/null +++ b/app/bettermitm/simple_manager.py @@ -0,0 +1,623 @@ +""" +Simple Bettercap Manager +A more robust and simple implementation for managing Bettercap +""" + +import subprocess +import time +import requests +import logging +from typing import Dict, List, Any, Optional + + +class SimpleBettercapManager: + """Simple and robust Bettercap manager""" + + def __init__(self): + self.api_url = "http://127.0.0.1:8081/" + self.username = "admin" + self.password = "admin123" + self.process = None + self.is_running = False + self.logger = logging.getLogger(__name__) + self.session_token = None + + # Simple state + self.discovered_hosts = [] + self.active_attacks = {} + self.current_interface = None + + def check_bettercap_installed(self) -> tuple[bool, str]: + """Check if Bettercap is installed and accessible""" + try: + # Try to run bettercap --version + result = subprocess.run( + ["bettercap", "--version"], + capture_output=True, + text=True, + timeout=5 + ) + + if result.returncode == 0: + version = result.stdout.strip() + return True, f"Bettercap installed: {version}" + else: + return False, f"Bettercap command failed: {result.stderr}" + + except FileNotFoundError: + return False, "Bettercap not found in PATH. Please install Bettercap." + except subprocess.TimeoutExpired: + return False, "Bettercap version check timed out" + except Exception as e: + return False, f"Error checking Bettercap: {e}" + + def start_bettercap(self, interface: str = None, api_port: int = 8081, sudo_password: str = None) -> bool: + """Start Bettercap with given interface""" + try: + if self.is_running: + return True + + # Check if Bettercap is installed + installed, message = self.check_bettercap_installed() + if not installed: + self.logger.error(message) + return False + else: + self.logger.info(message) + + # Build command with correct arguments + cmd = ["bettercap"] + if interface: + cmd.extend(["-iface", interface]) + + # Add REST API arguments + cmd.extend([ + "-eval", f"set api.rest.on true; set api.rest.port {api_port}; set api.rest.username {self.username}; set api.rest.password {self.password}" + ]) + + self.logger.info(f"Starting Bettercap: {' '.join(cmd)}") + + if sudo_password: + # Run with sudo + sudo_cmd = ["sudo", "-S"] + cmd + self.process = subprocess.Popen( + sudo_cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + # Send password + self.process.stdin.write(sudo_password + '\n') + self.process.stdin.flush() + else: + # Run normally + self.process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + # Wait for startup + time.sleep(5) + + # Check if it's running + if self.process.poll() is None: + # Process is running, now test API + time.sleep(2) # Give API time to start + + # Test API connection + try: + test_response = requests.get(f"{self.api_url}/api/session", timeout=3) + if test_response.status_code in [200, 401]: # 401 means API is up but not authenticated + self.is_running = True + self.current_interface = interface + self.logger.info(f"Bettercap started successfully on {interface}") + return True + except requests.RequestException: + pass + + # If we get here, API is not responding + self.logger.warning("Bettercap process started but API is not responding") + self.is_running = True # Assume it's working for now + self.current_interface = interface + return True + else: + # Process failed to start + try: + stdout, stderr = self.process.communicate(timeout=2) + error_output = stderr or stdout or "Unknown error" + except subprocess.TimeoutExpired: + error_output = "Process startup timeout" + + self.logger.error(f"Bettercap failed to start: {error_output}") + return False + + except Exception as e: + self.logger.error(f"Error starting Bettercap: {e}") + return False + + def stop_bettercap(self) -> bool: + """Stop Bettercap""" + try: + if not self.is_running or not self.process: + return True + + self.process.terminate() + time.sleep(2) + + # Force kill if still running + if self.process.poll() is None: + self.process.kill() + + self.is_running = False + self.current_interface = None + self.session_token = None + self.process = None + + self.logger.info("Bettercap stopped successfully") + return True + + except Exception as e: + self.logger.error(f"Error stopping Bettercap: {e}") + return False + + def sync_status(self) -> bool: + """Synchronize status with actual Bettercap process""" + try: + # Check if process is actually running + if self.process and self.process.poll() is None: + # Process is running, check API + try: + response = requests.get(f"{self.api_url}/api/session", timeout=2) + if response.status_code in [200, 401]: + self.is_running = True + return True + except: + pass + + # If we get here, either process is dead or API is not responding + self.is_running = False + self.current_interface = None + self.session_token = None + + if self.process: + try: + self.process.terminate() + except: + pass + self.process = None + + return False + + except Exception as e: + self.logger.error(f"Status sync error: {e}") + return False + + def get_attack_status(self) -> Dict[str, Any]: + """Get current attack status""" + # Sync status first + self.sync_status() + + return { + 'is_running': self.is_running, + 'current_interface': self.current_interface, + 'active_attacks': self.active_attacks, + 'discovered_hosts': self.discovered_hosts + } + + def get_network_interfaces(self) -> List[Dict[str, str]]: + """Get available network interfaces""" + try: + import netifaces + interfaces = [] + + for interface in netifaces.interfaces(): + try: + addrs = netifaces.ifaddresses(interface) + if netifaces.AF_INET in addrs: + ip = addrs[netifaces.AF_INET][0]['addr'] + interfaces.append({ + 'name': interface, + 'ip': ip + }) + except: + continue + + return interfaces + except ImportError: + # Fallback method using system commands + try: + import os + interfaces = [] + + # Try ip command + result = subprocess.run(['ip', '-4', 'addr', 'show'], + capture_output=True, text=True, timeout=5) + + if result.returncode == 0: + lines = result.stdout.split('\n') + current_interface = None + + for line in lines: + line = line.strip() + if ': ' in line and 'state UP' in line: + current_interface = line.split(':')[1].strip().split('@')[0] + elif 'inet ' in line and current_interface: + ip = line.split('inet ')[1].split('/')[0] + if not ip.startswith('127.'): + interfaces.append({ + 'name': current_interface, + 'ip': ip + }) + current_interface = None + + return interfaces + except: + # Ultimate fallback + return [{'name': 'eth0', 'ip': '192.168.1.100'}] + + def authenticate(self) -> bool: + """Authenticate with Bettercap API""" + try: + if not self.is_running: + self.logger.error("Cannot authenticate: Bettercap is not running") + return False + + # Check if we already have a valid session + if self.session_token: + # Test if session is still valid + try: + cookies = {"session": self.session_token} + test_response = requests.get(f"{self.api_url}/api/session", cookies=cookies, timeout=3) + if test_response.status_code == 200: + self.logger.info("Existing session is still valid") + return True + except: + self.logger.info("Existing session expired, re-authenticating") + self.session_token = None + + self.logger.info("Authenticating with Bettercap API") + + auth_data = { + "username": self.username, + "password": self.password + } + + response = requests.post( + f"{self.api_url}/api/session", + json=auth_data, + timeout=5 + ) + + self.logger.info(f"Auth response status: {response.status_code}") + + if response.status_code == 200: + self.session_token = response.cookies.get('session') + if self.session_token: + self.logger.info("Authentication successful") + return True + else: + self.logger.error("No session cookie received") + return False + else: + self.logger.error(f"Authentication failed with status: {response.status_code}") + return False + + except Exception as e: + self.logger.error(f"Authentication error: {e}") + return False + + def execute_command(self, command: str) -> Dict[str, Any]: + """Execute a command via Bettercap API""" + try: + if not self.is_running: + return {"success": False, "error": "Bettercap is not running"} + + self.logger.info(f"Executing command: {command}") + + # Ensure we have a valid session + if not self.session_token: + if not self.authenticate(): + return {"success": False, "error": "Authentication failed"} + + cookies = {"session": self.session_token} + + # Use the correct endpoint for command execution + response = requests.post( + f"{self.api_url}/api/session", + json={"cmd": command}, + cookies=cookies, + timeout=10 + ) + + self.logger.info(f"Command response status: {response.status_code}") + + if response.status_code == 200: + try: + response_data = response.json() + self.logger.info(f"Command response data: {response_data}") + return {"success": True, "data": response_data} + except ValueError as e: + # Response might not be JSON + self.logger.info(f"Command response (non-JSON): {response.text}") + return {"success": True, "data": response.text} + elif response.status_code == 401: + # Session expired, retry with new authentication + self.logger.info("Session expired, re-authenticating and retrying") + self.session_token = None + if self.authenticate(): + cookies = {"session": self.session_token} + response = requests.post( + f"{self.api_url}/api/session", + json={"cmd": command}, + cookies=cookies, + timeout=10 + ) + if response.status_code == 200: + try: + response_data = response.json() + return {"success": True, "data": response_data} + except ValueError: + return {"success": True, "data": response.text} + + return {"success": False, "error": "Authentication failed after retry"} + else: + self.logger.error(f"Command failed with status: {response.status_code}, response: {response.text}") + return {"success": False, "error": f"Command failed: {response.status_code} - {response.text}"} + + except Exception as e: + self.logger.error(f"Command execution error: {e}") + return {"success": False, "error": str(e)} + + def start_network_discovery(self) -> Dict[str, Any]: + """Start network discovery - simplified version""" + try: + if not self.is_running: + return {"success": False, "error": "Bettercap is not running"} + + self.logger.info("Starting network discovery") + + # First ensure authentication + if not self.authenticate(): + return {"success": False, "error": "Failed to authenticate with Bettercap API"} + + # Execute discovery commands one by one + commands = ["net.recon on", "net.probe on"] + failed_commands = [] + + for cmd in commands: + result = self.execute_command(cmd) + self.logger.info(f"Command '{cmd}' result: {result}") + + if not result.get("success"): + failed_commands.append(cmd) + self.logger.error(f"Command '{cmd}' failed: {result.get('error')}") + else: + self.logger.info(f"Command '{cmd}' executed successfully") + + if not failed_commands: + self.active_attacks['network_discovery'] = True + self.logger.info("Network discovery started successfully") + return {"success": True, "message": "Network discovery started"} + else: + error_msg = f"Failed commands: {', '.join(failed_commands)}" + self.logger.error(f"Network discovery failed: {error_msg}") + return {"success": False, "error": error_msg} + + except Exception as e: + self.logger.error(f"Network discovery error: {e}") + return {"success": False, "error": str(e)} + + def stop_network_discovery(self) -> Dict[str, Any]: + """Stop network discovery""" + try: + result1 = self.execute_command("net.recon off") + result2 = self.execute_command("net.probe off") + + self.active_attacks.pop('network_discovery', None) + return {"success": True, "message": "Network discovery stopped"} + + except Exception as e: + self.logger.error(f"Stop network discovery error: {e}") + return {"success": False, "error": str(e)} + + def start_packet_sniffer(self, protocols: List[str], bpf_filter: str = "", max_packets: int = 0) -> Dict[str, Any]: + """Start packet sniffer - simplified version""" + try: + if not self.is_running: + return {"success": False, "error": "Bettercap is not running"} + + commands = [] + if bpf_filter: + commands.append(f"set net.sniff.filter '{bpf_filter}'") + + commands.append("net.sniff on") + + for cmd in commands: + result = self.execute_command(cmd) + if not result.get("success"): + return {"success": False, "error": f"Command failed: {cmd}"} + + self.active_attacks['packet_sniffer'] = True + return {"success": True, "message": "Packet sniffer started"} + + except Exception as e: + self.logger.error(f"Packet sniffer error: {e}") + return {"success": False, "error": str(e)} + + def stop_packet_sniffer(self) -> Dict[str, Any]: + """Stop packet sniffer""" + try: + result = self.execute_command("net.sniff off") + self.active_attacks.pop('packet_sniffer', None) + return {"success": True, "message": "Packet sniffer stopped"} + + except Exception as e: + self.logger.error(f"Stop packet sniffer error: {e}") + return {"success": False, "error": str(e)} + + def start_arp_spoofing(self, target_ip: str, gateway_ip: str, bidirectional: bool = True) -> Dict[str, Any]: + """Start ARP spoofing - simplified version""" + try: + if not self.is_running: + return {"success": False, "error": "Bettercap is not running"} + + commands = [ + f"set arp.spoof.targets {target_ip}", + "arp.spoof on" + ] + + for cmd in commands: + result = self.execute_command(cmd) + if not result.get("success"): + return {"success": False, "error": f"Command failed: {cmd}"} + + self.active_attacks['arp_spoofing'] = {'target': target_ip, 'gateway': gateway_ip} + return {"success": True, "message": "ARP spoofing started", "attack_id": "arp_1"} + + except Exception as e: + self.logger.error(f"ARP spoofing error: {e}") + return {"success": False, "error": str(e)} + + def stop_arp_spoofing(self) -> Dict[str, Any]: + """Stop ARP spoofing""" + try: + result = self.execute_command("arp.spoof off") + self.active_attacks.pop('arp_spoofing', None) + return {"success": True, "message": "ARP spoofing stopped"} + + except Exception as e: + self.logger.error(f"Stop ARP spoofing error: {e}") + return {"success": False, "error": str(e)} + + def start_dns_spoofing(self, domain: str, spoofed_ip: str, all_domains: bool = False) -> Dict[str, Any]: + """Start DNS spoofing - simplified version""" + try: + if not self.is_running: + return {"success": False, "error": "Bettercap is not running"} + + commands = [ + f"set dns.spoof.address {spoofed_ip}", + f"set dns.spoof.domains {domain}", + "dns.spoof on" + ] + + for cmd in commands: + result = self.execute_command(cmd) + if not result.get("success"): + return {"success": False, "error": f"Command failed: {cmd}"} + + self.active_attacks['dns_spoofing'] = {'domain': domain, 'spoofed_ip': spoofed_ip} + return {"success": True, "message": "DNS spoofing started", "attack_id": "dns_1"} + + except Exception as e: + self.logger.error(f"DNS spoofing error: {e}") + return {"success": False, "error": str(e)} + + def stop_dns_spoofing(self) -> Dict[str, Any]: + """Stop DNS spoofing""" + try: + result = self.execute_command("dns.spoof off") + self.active_attacks.pop('dns_spoofing', None) + return {"success": True, "message": "DNS spoofing stopped"} + + except Exception as e: + self.logger.error(f"Stop DNS spoofing error: {e}") + return {"success": False, "error": str(e)} + + def start_http_proxy(self, port: int = 8080, transparent: bool = False) -> Dict[str, Any]: + """Start HTTP proxy - simplified version""" + try: + if not self.is_running: + return {"success": False, "error": "Bettercap is not running"} + + commands = [ + f"set http.proxy.port {port}", + "http.proxy on" + ] + + for cmd in commands: + result = self.execute_command(cmd) + if not result.get("success"): + return {"success": False, "error": f"Command failed: {cmd}"} + + self.active_attacks['http_proxy'] = {'port': port} + return {"success": True, "message": f"HTTP proxy started on port {port}"} + + except Exception as e: + self.logger.error(f"HTTP proxy error: {e}") + return {"success": False, "error": str(e)} + + def stop_http_proxy(self) -> Dict[str, Any]: + """Stop HTTP proxy""" + try: + result = self.execute_command("http.proxy off") + self.active_attacks.pop('http_proxy', None) + return {"success": True, "message": "HTTP proxy stopped"} + + except Exception as e: + self.logger.error(f"Stop HTTP proxy error: {e}") + return {"success": False, "error": str(e)} + + def test_api_connection(self) -> Dict[str, Any]: + """Test the API connection and basic commands""" + try: + if not self.is_running: + return {"success": False, "error": "Bettercap is not running"} + + # Test authentication + if not self.authenticate(): + return {"success": False, "error": "Authentication failed"} + + # Test basic commands + test_results = {} + + # Test help command + help_result = self.execute_command("help") + test_results['help_command'] = help_result.get("success", False) + + # Test status command + status_result = self.execute_command("active") + test_results['status_command'] = status_result.get("success", False) + + # Test interface info + iface_result = self.execute_command("net.show") + test_results['interface_command'] = iface_result.get("success", False) + + success_count = sum(1 for v in test_results.values() if v) + total_tests = len(test_results) + + return { + "success": success_count > 0, + "results": test_results, + "summary": f"{success_count}/{total_tests} tests passed" + } + + except Exception as e: + self.logger.error(f"API test error: {e}") + return {"success": False, "error": str(e)} + + def cleanup(self) -> bool: + """Cleanup all operations""" + try: + # Stop all attacks + self.stop_arp_spoofing() + self.stop_dns_spoofing() + self.stop_packet_sniffer() + self.stop_http_proxy() + self.stop_network_discovery() + + # Stop Bettercap + self.stop_bettercap() + + return True + + except Exception as e: + self.logger.error(f"Cleanup error: {e}") + return False + + +# Global instance +simple_bettercap_manager = SimpleBettercapManager() \ No newline at end of file diff --git a/app/static/js/bettermitm.js b/app/static/js/bettermitm.js new file mode 100644 index 0000000..6d55ff1 --- /dev/null +++ b/app/static/js/bettermitm.js @@ -0,0 +1,924 @@ +/** + * BetterMITM Web Interface JavaScript + * Provides functionality for network security testing with Bettercap + */ + +class BetterMITM { + constructor() { + console.log('BetterMITM constructor called'); + this.refreshInterval = 3000; // 3 seconds + this.statusRefreshTimer = null; + this.devicesRefreshTimer = null; + this.packetCount = 0; + this.currentDevices = {}; + this.isRunning = false; + this.csrf_token = document.querySelector('meta[name=csrf-token]')?.getAttribute('content') || ''; + console.log('BetterMITM constructed with CSRF token:', this.csrf_token); + } + + static init() { + console.log('BetterMITM.init() called'); + window.BetterMITM = new BetterMITM(); + console.log('BetterMITM instance created'); + window.BetterMITM.bindEvents(); + console.log('Events bound'); + window.BetterMITM.startAutoRefresh(); + window.BetterMITM.refreshStatus(); + console.log('BetterMITM initialization complete'); + } + + bindEvents() { + console.log('bindEvents() called'); + // Bettercap control buttons + const startBtn = document.getElementById('start-bettercap-btn'); + const stopBtn = document.getElementById('stop-bettercap-btn'); + + console.log('Start button found:', !!startBtn); + console.log('Stop button found:', !!stopBtn); + + if (startBtn) { + console.log('Adding click listener to start button'); + startBtn.addEventListener('click', () => { + console.log('Start button clicked!'); + this.showInterfaceModal(); + }); + } else { + console.error('Start Bettercap button not found!'); + } + + if (stopBtn) { + stopBtn.addEventListener('click', () => { + console.log('Stop button clicked!'); + this.stopBettercap(); + }); + } else { + console.error('Stop Bettercap button not found!'); + } + + // Form submissions + document.getElementById('network-scan-form').addEventListener('submit', (e) => { + e.preventDefault(); + this.startNetworkScan(); + }); + + document.getElementById('arp-spoof-form').addEventListener('submit', (e) => { + e.preventDefault(); + this.startARPSpoof(); + }); + + document.getElementById('dns-spoof-form').addEventListener('submit', (e) => { + e.preventDefault(); + this.startDNSSpoof(); + }); + + document.getElementById('proxy-form').addEventListener('submit', (e) => { + e.preventDefault(); + this.startProxy(); + }); + + document.getElementById('sniffer-form').addEventListener('submit', (e) => { + e.preventDefault(); + this.startSniffer(); + }); + + document.getElementById('device-target-form').addEventListener('submit', (e) => { + e.preventDefault(); + this.targetDevice(); + }); + + // Global functions + window.startNetworkScan = () => this.startNetworkScan(); + window.stopNetworkScan = () => this.stopNetworkScan(); + window.startARPSpoofAll = () => this.startARPSpoofAll(); + window.stopARPSpoof = () => this.stopARPSpoof(); + window.stopDNSSpoof = () => this.stopDNSSpoof(); + window.stopProxy = () => this.stopProxy(); + window.stopSniffer = () => this.stopSniffer(); + window.clearCapture = () => this.clearCapture(); + window.emergencyStop = () => this.emergencyStop(); + window.exportDevices = () => this.exportDevices(); + window.startPacketCapture = () => this.startPacketCapture(); + window.targetCurrentDevice = () => this.targetCurrentDevice(); + window.startBettercapClick = () => { + console.log('startBettercapClick called'); + this.showInterfaceModal(); + }; + window.stopBettercapClick = () => { + console.log('stopBettercapClick called'); + this.stopBettercap(); + }; + } + + showInterfaceModal() { + const modal = new bootstrap.Modal(document.getElementById('interfaceModal')); + modal.show(); + } + + async startBettercap(interface = null, sudoPassword = null) { + try { + const requestData = { interface: interface }; + if (sudoPassword) { + requestData.sudo_password = sudoPassword; + } + + const response = await fetch('https://github.com/bettermitm/api/start', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': this.csrf_token + }, + body: JSON.stringify(requestData) + }); + + const result = await response.json(); + + if (result.success) { + const message = result.sudo_used ? + 'Bettercap started successfully with sudo privileges' : + 'Bettercap started successfully'; + this.showNotification(message, 'success'); + this.isRunning = true; + this.updateBettercapStatus(true, interface); + this.startAutoRefresh(); + + // Clear password field for security + if (sudoPassword) { + document.getElementById('sudo-password').value = ''; + } + } else { + this.showNotification('Failed to start Bettercap: ' + result.error, 'error'); + } + } catch (error) { + this.showNotification('Error starting Bettercap: ' + error.message, 'error'); + } + } + + async stopBettercap() { + try { + const response = await fetch('https://github.com/bettermitm/api/stop', { + method: 'POST', + headers: { + 'X-CSRFToken': this.csrf_token + } + }); + + const result = await response.json(); + + if (result.success) { + this.showNotification('Bettercap stopped successfully', 'success'); + this.isRunning = false; + this.updateBettercapStatus(false); + this.stopAutoRefresh(); + } else { + this.showNotification('Failed to stop Bettercap: ' + result.error, 'error'); + } + } catch (error) { + this.showNotification('Error stopping Bettercap: ' + error.message, 'error'); + } + } + + async refreshStatus() { + try { + const response = await fetch('https://github.com/bettermitm/api/status'); + const result = await response.json(); + + if (result.success) { + this.isRunning = result.bettercap_running; + this.updateBettercapStatus(result.bettercap_running, result.current_interface); + this.updateStatistics(result); + this.updateActiveAttacks(result.active_attacks); + } + } catch (error) { + console.error('Error refreshing status:', error); + } + } + + async refreshDevices() { + try { + const response = await fetch('https://github.com/bettermitm/api/hosts'); + const result = await response.json(); + + if (result.success) { + this.currentDevices = {}; + result.hosts.forEach(device => { + this.currentDevices[device.ip || device.mac] = device; + }); + this.updateDevicesList(result.hosts); + this.updateNetworkMap(result.hosts); + } + } catch (error) { + console.error('Error refreshing devices:', error); + } + } + + async startNetworkScan() { + try { + const form = document.getElementById('network-scan-form'); + const formData = new FormData(form); + + const data = { + scan_type: formData.get('scan_type'), + target_range: formData.get('target_range') + }; + + const response = await fetch('https://github.com/bettermitm/api/scan/start', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': this.csrf_token + }, + body: JSON.stringify(data) + }); + + const result = await response.json(); + + if (result.success) { + this.showNotification('Network scan started', 'success'); + this.logToConsole('Network scan started: ' + result.scan_type); + } else { + this.showNotification('Failed to start scan: ' + result.error, 'error'); + } + } catch (error) { + this.showNotification('Error starting scan: ' + error.message, 'error'); + } + } + + async stopNetworkScan() { + try { + const response = await fetch('https://github.com/bettermitm/api/scan/stop', { + method: 'POST', + headers: { + 'X-CSRFToken': this.csrf_token + } + }); + + const result = await response.json(); + + if (result.success) { + this.showNotification('Network scan stopped', 'success'); + this.logToConsole('Network scan stopped'); + } else { + this.showNotification('Failed to stop scan: ' + result.message, 'error'); + } + } catch (error) { + this.showNotification('Error stopping scan: ' + error.message, 'error'); + } + } + + async startARPSpoof() { + try { + const form = document.getElementById('arp-spoof-form'); + const response = await fetch('https://github.com/bettermitm/api/arp/start', { + method: 'POST', + headers: { + 'X-CSRFToken': this.csrf_token + }, + body: new FormData(form) + }); + + const result = await response.json(); + + if (result.success) { + this.showNotification('ARP spoofing started', 'success'); + this.logToConsole('ARP spoofing attack started: ' + result.message); + } else { + this.showNotification('ARP spoofing failed: ' + result.error, 'error'); + } + } catch (error) { + this.showNotification('Error starting ARP spoof: ' + error.message, 'error'); + } + } + + async stopARPSpoof() { + try { + const response = await fetch('https://github.com/bettermitm/api/arp/stop', { + method: 'POST', + headers: { + 'X-CSRFToken': this.csrf_token + } + }); + + const result = await response.json(); + + if (result.success) { + this.showNotification('ARP spoofing stopped', 'success'); + this.logToConsole('ARP spoofing stopped'); + } else { + this.showNotification('Failed to stop ARP spoof', 'error'); + } + } catch (error) { + this.showNotification('Error stopping ARP spoof: ' + error.message, 'error'); + } + } + + async startDNSSpoof() { + try { + const form = document.getElementById('dns-spoof-form'); + const response = await fetch('https://github.com/bettermitm/api/dns/start', { + method: 'POST', + headers: { + 'X-CSRFToken': this.csrf_token + }, + body: new FormData(form) + }); + + const result = await response.json(); + + if (result.success) { + this.showNotification('DNS spoofing started', 'success'); + this.logToConsole('DNS spoofing attack started: ' + result.message); + } else { + this.showNotification('DNS spoofing failed: ' + result.error, 'error'); + } + } catch (error) { + this.showNotification('Error starting DNS spoof: ' + error.message, 'error'); + } + } + + async stopDNSSpoof() { + try { + const response = await fetch('https://github.com/bettermitm/api/dns/stop', { + method: 'POST', + headers: { + 'X-CSRFToken': this.csrf_token + } + }); + + const result = await response.json(); + + if (result.success) { + this.showNotification('DNS spoofing stopped', 'success'); + this.logToConsole('DNS spoofing stopped'); + } else { + this.showNotification('Failed to stop DNS spoof', 'error'); + } + } catch (error) { + this.showNotification('Error stopping DNS spoof: ' + error.message, 'error'); + } + } + + async startProxy() { + try { + const form = document.getElementById('proxy-form'); + const response = await fetch('https://github.com/bettermitm/api/proxy/start', { + method: 'POST', + headers: { + 'X-CSRFToken': this.csrf_token + }, + body: new FormData(form) + }); + + const result = await response.json(); + + if (result.success) { + this.showNotification('HTTP proxy started', 'success'); + this.logToConsole('HTTP proxy started: ' + result.message); + } else { + this.showNotification('Proxy failed: ' + result.error, 'error'); + } + } catch (error) { + this.showNotification('Error starting proxy: ' + error.message, 'error'); + } + } + + async stopProxy() { + try { + const response = await fetch('https://github.com/bettermitm/api/proxy/stop', { + method: 'POST', + headers: { + 'X-CSRFToken': this.csrf_token + } + }); + + const result = await response.json(); + + if (result.success) { + this.showNotification('HTTP proxy stopped', 'success'); + this.logToConsole('HTTP proxy stopped'); + } else { + this.showNotification('Failed to stop proxy', 'error'); + } + } catch (error) { + this.showNotification('Error stopping proxy: ' + error.message, 'error'); + } + } + + async startSniffer() { + try { + const form = document.getElementById('sniffer-form'); + const response = await fetch('https://github.com/bettermitm/api/sniffer/start', { + method: 'POST', + headers: { + 'X-CSRFToken': this.csrf_token + }, + body: new FormData(form) + }); + + const result = await response.json(); + + if (result.success) { + this.showNotification('Packet sniffer started', 'success'); + this.logToConsole('Packet capture started'); + this.clearCapture(); + } else { + this.showNotification('Sniffer failed: ' + result.error, 'error'); + } + } catch (error) { + this.showNotification('Error starting sniffer: ' + error.message, 'error'); + } + } + + async stopSniffer() { + try { + const response = await fetch('https://github.com/bettermitm/api/sniffer/stop', { + method: 'POST', + headers: { + 'X-CSRFToken': this.csrf_token + } + }); + + const result = await response.json(); + + if (result.success) { + this.showNotification('Packet sniffer stopped', 'success'); + this.logToConsole('Packet capture stopped'); + } else { + this.showNotification('Failed to stop sniffer', 'error'); + } + } catch (error) { + this.showNotification('Error stopping sniffer: ' + error.message, 'error'); + } + } + + async targetDevice() { + try { + const form = document.getElementById('device-target-form'); + const response = await fetch('https://github.com/bettermitm/api/device/target', { + method: 'POST', + headers: { + 'X-CSRFToken': this.csrf_token + }, + body: new FormData(form) + }); + + const result = await response.json(); + + if (result.success) { + this.showNotification('Device targeted successfully', 'success'); + this.refreshDevices(); + } else { + this.showNotification('Device targeting failed: ' + result.error, 'error'); + } + } catch (error) { + this.showNotification('Error targeting device: ' + error.message, 'error'); + } + } + + async executeCommand(command) { + try { + this.logToConsole('bettercap> ' + command, 'command'); + + const response = await fetch('https://github.com/bettermitm/api/command', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': this.csrf_token + }, + body: JSON.stringify({ command: command }) + }); + + const result = await response.json(); + + if (result.success) { + if (result.output) { + this.logToConsole(JSON.stringify(result.output, null, 2), 'output'); + } + } else { + this.logToConsole('Error: ' + result.error, 'error'); + } + } catch (error) { + this.logToConsole('Request failed: ' + error.message, 'error'); + } + } + + updateBettercapStatus(running, interface = null) { + const statusElement = document.getElementById('bettercap-status'); + const statusTextElement = document.getElementById('status-text'); + const interfaceElement = document.getElementById('current-interface'); + const startBtn = document.getElementById('start-bettercap-btn'); + const stopBtn = document.getElementById('stop-bettercap-btn'); + + if (running) { + statusElement.className = 'bettercap-status running'; + statusTextElement.textContent = 'Running'; + startBtn.disabled = true; + stopBtn.disabled = false; + interfaceElement.textContent = interface || 'Unknown'; + } else { + statusElement.className = 'bettercap-status stopped'; + statusTextElement.textContent = 'Stopped'; + startBtn.disabled = false; + stopBtn.disabled = true; + interfaceElement.textContent = 'None'; + } + } + + updateStatistics(data) { + document.getElementById('total-devices').textContent = data.discovered_hosts.length || 0; + document.getElementById('active-devices').textContent = + data.discovered_hosts.filter(d => d.status === 'online').length || 0; + document.getElementById('targeted-devices').textContent = + data.discovered_hosts.filter(d => d.targeted).length || 0; + document.getElementById('under-attack').textContent = + data.discovered_hosts.filter(d => Object.keys(d.attacks || {}).some(k => d.attacks[k].active)).length || 0; + } + + updateActiveAttacks(attacks) { + const attackCount = Object.keys(attacks).length; + document.getElementById('active-attacks-count').textContent = attackCount; + } + + updateDevicesList(devices) { + const container = document.getElementById('devices-list'); + + if (devices.length === 0) { + container.innerHTML = ` +
+ +

No Devices Found

+

Start a network scan to discover devices

+
+ `; + return; + } + + let html = ''; + devices.forEach(device => { + const statusClass = device.status === 'online' ? 'online' : 'offline'; + const targetedClass = device.targeted ? 'targeted' : ''; + const attacksActive = Object.values(device.attacks || {}).some(a => a.active); + + html += ` +
+
+
+
+ + ${device.hostname || device.ip} + ${device.targeted ? 'TARGETED' : ''} +
+ + ${device.ip} • ${device.mac || 'Unknown MAC'} • ${device.vendor || 'Unknown Vendor'} + + ${attacksActive ? '
Under Attack
' : ''} +
+
+ ${attacksActive ? '' : ''} + +
+
+
+ `; + }); + + container.innerHTML = html; + } + + updateNetworkMap(devices) { + const container = document.getElementById('network-map'); + + if (devices.length === 0) { + container.innerHTML = ` +
+ +

Network Map

+

Start Bettercap and begin network discovery to see devices

+
+ `; + return; + } + + // Simple network visualization + let html = '
'; + devices.forEach((device, index) => { + if (index % 4 === 0 && index > 0) html += '
'; + + const statusClass = device.status === 'online' ? 'success' : 'secondary'; + const targetedClass = device.targeted ? 'danger' : statusClass; + + html += ` +
+
+
+ +
+ ${device.hostname || device.ip} + ${device.vendor || 'Unknown'} +
+
+
+
+ `; + }); + html += '
'; + + container.innerHTML = html; + } + + getDeviceIcon(deviceType) { + const icons = { + 'router': 'router', + 'desktop': 'pc-display', + 'laptop': 'laptop', + 'mobile': 'phone', + 'server': 'server', + 'printer': 'printer', + 'camera': 'camera', + 'iot': 'thermometer' + }; + return icons[deviceType] || 'device-ssd'; + } + + logToConsole(message, type = 'info') { + const console = document.getElementById('console-output'); + const timestamp = new Date().toLocaleTimeString(); + + let cssClass = 'text-light'; + if (type === 'error') cssClass = 'text-danger'; + else if (type === 'success') cssClass = 'text-success'; + else if (type === 'command') cssClass = 'text-warning'; + else if (type === 'output') cssClass = 'text-info'; + + const logEntry = `
[${timestamp}] ${message}
`; + console.innerHTML += logEntry; + console.scrollTop = console.scrollHeight; + } + + clearConsole() { + document.getElementById('console-output').innerHTML = 'Console cleared.\n'; + } + + clearCapture() { + document.getElementById('packet-output').innerHTML = ` +
+ +

No packets captured yet. Start packet capture to see network traffic.

+
+ `; + this.packetCount = 0; + document.getElementById('packet-count').textContent = '0 packets'; + } + + startAutoRefresh() { + this.stopAutoRefresh(); + + this.statusRefreshTimer = setInterval(() => { + if (this.isRunning) { + this.refreshStatus(); + } + }, this.refreshInterval); + + this.devicesRefreshTimer = setInterval(() => { + if (this.isRunning) { + this.refreshDevices(); + } + }, this.refreshInterval * 2); + } + + stopAutoRefresh() { + if (this.statusRefreshTimer) { + clearInterval(this.statusRefreshTimer); + this.statusRefreshTimer = null; + } + + if (this.devicesRefreshTimer) { + clearInterval(this.devicesRefreshTimer); + this.devicesRefreshTimer = null; + } + } + + async emergencyStop() { + try { + // Stop all attacks and operations + await Promise.all([ + this.stopARPSpoof(), + this.stopDNSSpoof(), + this.stopProxy(), + this.stopSniffer(), + this.stopNetworkScan() + ]); + + // Stop Bettercap + await this.stopBettercap(); + + this.showNotification('Emergency stop completed', 'success'); + this.logToConsole('Emergency stop - all operations terminated', 'error'); + + } catch (error) { + this.showNotification('Emergency stop failed: ' + error.message, 'error'); + } + } + + showNotification(message, type = 'info') { + // Create toast notification + const toastHtml = ` + + `; + + // Add to toast container (create if needed) + let toastContainer = document.querySelector('.toast-container'); + if (!toastContainer) { + toastContainer = document.createElement('div'); + toastContainer.className = 'toast-container position-fixed bottom-0 end-0 p-3'; + document.body.appendChild(toastContainer); + } + + toastContainer.innerHTML = toastHtml; + const toast = new bootstrap.Toast(toastContainer.querySelector('.toast')); + toast.show(); + } + + saveConsoleLog() { + const content = document.getElementById('console-output').textContent; + const blob = new Blob([content], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'bettermitm-console-' + new Date().toISOString().slice(0, 10) + '.log'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } + + exportDevices() { + const devices = Object.values(this.currentDevices); + const content = JSON.stringify(devices, null, 2); + const blob = new Blob([content], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'bettermitm-devices-' + new Date().toISOString().slice(0, 10) + '.json'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } +} + +// Global helper functions +window.showDeviceDetails = function(ip) { + const device = window.BetterMITM.currentDevices[ip]; + if (!device) return; + + const modal = document.getElementById('deviceModal'); + const modalBody = document.getElementById('device-modal-body'); + + let attacksList = ''; + if (device.attacks) { + Object.entries(device.attacks).forEach(([type, info]) => { + const status = info.active ? 'text-danger' : 'text-muted'; + attacksList += `
  • ${type}: ${info.active ? 'Active' : 'Inactive'}
  • `; + }); + } + + modalBody.innerHTML = ` +
    +
    +
    Basic Information
    + + + + + + + +
    IP Address:${device.ip}
    MAC Address:${device.mac || 'Unknown'}
    Hostname:${device.hostname || 'Unknown'}
    Vendor:${device.vendor || 'Unknown'}
    OS:${device.os || 'Unknown'}
    Status:${device.status}
    +
    +
    +
    Network Statistics
    + + + + + + + +
    Packets Sent:${device.packets_sent || 0}
    Packets Received:${device.packets_received || 0}
    Bytes Sent:${device.bytes_sent || 0}
    Bytes Received:${device.bytes_received || 0}
    First Seen:${device.first_seen ? new Date(device.first_seen).toLocaleString() : 'Unknown'}
    Last Seen:${device.last_seen ? new Date(device.last_seen).toLocaleString() : 'Unknown'}
    + + ${attacksList ? '
    Active Attacks
    ' : ''} +
    +
    + `; + + const bsModal = new bootstrap.Modal(modal); + bsModal.show(); + + // Store current device for targeting + modal.setAttribute('data-device-ip', ip); +}; + +window.targetDeviceQuick = function(ip) { + document.getElementById('device_ip').value = ip; + document.getElementById('device-target-form').dispatchEvent(new Event('submit')); +}; + +window.targetCurrentDevice = function() { + const modal = document.getElementById('deviceModal'); + const ip = modal.getAttribute('data-device-ip'); + if (ip) { + targetDeviceQuick(ip); + bootstrap.Modal.getInstance(modal).hide(); + } +}; + +window.startARPSpoofAll = function() { + if (confirm('This will start ARP spoofing against all discovered devices. Continue?')) { + window.BetterMITM.showNotification('ARP spoofing all devices - feature not implemented yet', 'warning'); + } +}; + +window.startPacketCapture = function() { + // Quick packet capture start + document.getElementById('sniffer-form').dispatchEvent(new Event('submit')); +}; + +// Interface selection functions +window.startBettercapWithInterface = async function() { + const selectedInterface = document.getElementById('interface-select')?.value; + + if (!selectedInterface) { + window.BetterMITM.showNotification('Please select an interface', 'error'); + return; + } + + // Hide the interface modal + const interfaceModal = bootstrap.Modal.getInstance(document.getElementById('interfaceModal')); + if (interfaceModal) { + interfaceModal.hide(); + } + + // Start Bettercap without sudo + await window.BetterMITM.startBettercap(selectedInterface); +}; + +// Sudo authentication functions +window.showSudoModal = function() { + const selectedInterface = document.getElementById('interface-select')?.value; + + if (!selectedInterface) { + window.BetterMITM.showNotification('Please select an interface first', 'error'); + return; + } + + // Update sudo modal with selected interface + const interfaceDisplay = document.getElementById('selected-interface-name'); + if (interfaceDisplay) { + interfaceDisplay.textContent = selectedInterface; + } + + // Hide interface modal and show sudo modal + const interfaceModal = bootstrap.Modal.getInstance(document.getElementById('interfaceModal')); + if (interfaceModal) { + interfaceModal.hide(); + } + + const sudoModal = new bootstrap.Modal(document.getElementById('sudoModal')); + sudoModal.show(); +}; + +window.startBettercapWithSudo = async function() { + const sudoPassword = document.getElementById('sudo-password').value; + const selectedInterface = document.getElementById('interface-select')?.value; + + if (!sudoPassword.trim()) { + window.BetterMITM.showNotification('Please enter sudo password', 'error'); + return; + } + + if (!selectedInterface) { + window.BetterMITM.showNotification('Interface not selected', 'error'); + return; + } + + // Hide the sudo modal + const sudoModal = bootstrap.Modal.getInstance(document.getElementById('sudoModal')); + if (sudoModal) { + sudoModal.hide(); + } + + // Start Bettercap with sudo + await window.BetterMITM.startBettercap(selectedInterface, sudoPassword); +}; + +// Initialize when DOM is ready +document.addEventListener('DOMContentLoaded', BetterMITM.init); \ No newline at end of file diff --git a/app/static/js/bettermitm_complete.js b/app/static/js/bettermitm_complete.js new file mode 100644 index 0000000..e606e14 --- /dev/null +++ b/app/static/js/bettermitm_complete.js @@ -0,0 +1,637 @@ +/** + * BetterMITM Complete JavaScript Implementation + * Provides full functionality for network security testing with Bettercap + */ + +console.log('BetterMITM Complete script loading...'); + +// Global state management +const BetterMITMState = { + isRunning: false, + currentInterface: null, + refreshInterval: 3000, + statusTimer: null, + devicesTimer: null, + csrf_token: '' +}; + +// Initialize CSRF token +document.addEventListener('DOMContentLoaded', function() { + BetterMITMState.csrf_token = document.querySelector('meta[name=csrf-token]')?.getAttribute('content') || ''; + console.log('CSRF token initialized:', BetterMITMState.csrf_token); + + // Start auto-refresh if Bettercap is running + startAutoRefresh(); + checkStatus(); +}); + +// API Helper Functions +async function makeAPICall(endpoint, method = 'GET', data = null) { + try { + const options = { + method: method, + headers: { + 'X-CSRFToken': BetterMITMState.csrf_token, + 'X-Requested-With': 'XMLHttpRequest' + } + }; + + if (data) { + if (data instanceof FormData) { + options.body = data; + } else { + options.headers['Content-Type'] = 'application/json'; + options.body = JSON.stringify(data); + } + } + + const response = await fetch(`/bettermitm/api${endpoint}`, options); + const result = await response.json(); + + return result; + } catch (error) { + console.error('API call failed:', error); + showNotification('API call failed: ' + error.message, 'error'); + return { success: false, error: error.message }; + } +} + +function showNotification(message, type = 'info') { + console.log(`[${type.toUpperCase()}] ${message}`); + + // Create toast notification + const toastHtml = ` + + `; + + // Add to toast container (create if needed) + let toastContainer = document.querySelector('.toast-container'); + if (!toastContainer) { + toastContainer = document.createElement('div'); + toastContainer.className = 'toast-container position-fixed bottom-0 end-0 p-3'; + document.body.appendChild(toastContainer); + } + + toastContainer.innerHTML = toastHtml; + const toast = new bootstrap.Toast(toastContainer.querySelector('.toast')); + toast.show(); +} + +// Main Bettercap Control Functions +window.startBettercapClick = function() { + console.log('startBettercapClick called'); + showInterfaceModal(); +}; + +window.stopBettercapClick = async function() { + console.log('stopBettercapClick called'); + + const result = await makeAPICall('/stop', 'POST'); + + if (result.success) { + showNotification('Bettercap stopped successfully', 'success'); + BetterMITMState.isRunning = false; + updateBettercapStatus(false); + stopAutoRefresh(); + } else { + showNotification('Failed to stop Bettercap: ' + result.error, 'error'); + } +}; + +function showInterfaceModal() { + const modal = new bootstrap.Modal(document.getElementById('interfaceModal')); + modal.show(); +} + +window.startBettercapWithInterface = async function() { + console.log('startBettercapWithInterface called'); + + const interfaceSelect = document.getElementById('interface-select'); + if (!interfaceSelect || !interfaceSelect.value) { + showNotification('Please select a network interface', 'error'); + return; + } + + const selectedInterface = interfaceSelect.value; + console.log('Selected interface:', selectedInterface); + + // Hide modal + const interfaceModal = bootstrap.Modal.getInstance(document.getElementById('interfaceModal')); + if (interfaceModal) { + interfaceModal.hide(); + } + + // Start Bettercap + const result = await makeAPICall('/start', 'POST', { + interface: selectedInterface + }); + + if (result.success) { + showNotification('Bettercap started successfully on ' + selectedInterface, 'success'); + BetterMITMState.isRunning = true; + BetterMITMState.currentInterface = selectedInterface; + updateBettercapStatus(true, selectedInterface); + startAutoRefresh(); + } else { + showNotification('Failed to start Bettercap: ' + result.error, 'error'); + } +}; + +window.showSudoModal = function() { + console.log('showSudoModal called'); + + const interfaceSelect = document.getElementById('interface-select'); + if (!interfaceSelect || !interfaceSelect.value) { + showNotification('Please select an interface first', 'error'); + return; + } + + // Update sudo modal with selected interface + const interfaceDisplay = document.getElementById('selected-interface-name'); + if (interfaceDisplay) { + interfaceDisplay.textContent = interfaceSelect.value; + } + + // Hide interface modal and show sudo modal + const interfaceModal = bootstrap.Modal.getInstance(document.getElementById('interfaceModal')); + if (interfaceModal) { + interfaceModal.hide(); + } + + const sudoModal = new bootstrap.Modal(document.getElementById('sudoModal')); + sudoModal.show(); +}; + +window.startBettercapWithSudo = async function() { + console.log('startBettercapWithSudo called'); + + const sudoPassword = document.getElementById('sudo-password').value; + const interfaceSelect = document.getElementById('interface-select'); + + if (!sudoPassword.trim()) { + showNotification('Please enter sudo password', 'error'); + return; + } + + if (!interfaceSelect || !interfaceSelect.value) { + showNotification('Interface not selected', 'error'); + return; + } + + const selectedInterface = interfaceSelect.value; + + // Hide sudo modal + const sudoModal = bootstrap.Modal.getInstance(document.getElementById('sudoModal')); + if (sudoModal) { + sudoModal.hide(); + } + + // Start Bettercap with sudo + const result = await makeAPICall('/start', 'POST', { + interface: selectedInterface, + sudo_password: sudoPassword + }); + + // Clear password field + document.getElementById('sudo-password').value = ''; + + if (result.success) { + showNotification('Bettercap started successfully with sudo privileges on ' + selectedInterface, 'success'); + BetterMITMState.isRunning = true; + BetterMITMState.currentInterface = selectedInterface; + updateBettercapStatus(true, selectedInterface); + startAutoRefresh(); + } else { + showNotification('Failed to start Bettercap with sudo: ' + result.error, 'error'); + } +}; + +// Network Scanning Functions +window.startNetworkScan = async function() { + console.log('startNetworkScan called'); + + // Check if Bettercap is running + if (!BetterMITMState.isRunning) { + showNotification('Please start Bettercap first before scanning', 'error'); + return; + } + + const result = await makeAPICall('/scan/start', 'POST', { + scan_type: 'arp', + target_range: null + }); + + if (result.success) { + showNotification('Network scan started', 'success'); + } else { + showNotification('Failed to start scan: ' + result.error, 'error'); + } +}; + +window.stopNetworkScan = async function() { + console.log('stopNetworkScan called'); + + const result = await makeAPICall('/scan/stop', 'POST'); + + if (result.success) { + showNotification('Network scan stopped', 'success'); + } else { + showNotification('Failed to stop scan', 'error'); + } +}; + +// Attack Functions +window.startARPSpoofAll = function() { + console.log('startARPSpoofAll called'); + if (confirm('This will start ARP spoofing against all discovered devices. Continue?')) { + showNotification('ARP spoofing all devices - feature under development', 'warning'); + } +}; + +window.stopARPSpoof = async function() { + console.log('stopARPSpoof called'); + + const result = await makeAPICall('/arp/stop', 'POST'); + + if (result.success) { + showNotification('ARP spoofing stopped', 'success'); + } else { + showNotification('Failed to stop ARP spoofing', 'error'); + } +}; + +window.stopDNSSpoof = async function() { + console.log('stopDNSSpoof called'); + + const result = await makeAPICall('/dns/stop', 'POST'); + + if (result.success) { + showNotification('DNS spoofing stopped', 'success'); + } else { + showNotification('Failed to stop DNS spoofing', 'error'); + } +}; + +window.stopProxy = async function() { + console.log('stopProxy called'); + + const result = await makeAPICall('/proxy/stop', 'POST'); + + if (result.success) { + showNotification('HTTP proxy stopped', 'success'); + } else { + showNotification('Failed to stop proxy', 'error'); + } +}; + +window.stopSniffer = async function() { + console.log('stopSniffer called'); + + const result = await makeAPICall('/sniffer/stop', 'POST'); + + if (result.success) { + showNotification('Packet sniffer stopped', 'success'); + } else { + showNotification('Failed to stop sniffer', 'error'); + } +}; + +window.startPacketCapture = function() { + console.log('startPacketCapture called'); + showNotification('Starting packet capture...', 'info'); + + // Submit the sniffer form + const form = document.getElementById('sniffer-form'); + if (form) { + form.dispatchEvent(new Event('submit')); + } +}; + +window.clearCapture = function() { + console.log('clearCapture called'); + document.getElementById('packet-output').innerHTML = ` +
    + +

    No packets captured yet. Start packet capture to see network traffic.

    +
    + `; +}; + +window.emergencyStop = async function() { + console.log('emergencyStop called'); + + if (!confirm('This will stop all active attacks and operations. Continue?')) { + return; + } + + showNotification('Emergency stop initiated...', 'warning'); + + try { + // Stop all attacks in parallel + await Promise.all([ + makeAPICall('/arp/stop', 'POST'), + makeAPICall('/dns/stop', 'POST'), + makeAPICall('/proxy/stop', 'POST'), + makeAPICall('/sniffer/stop', 'POST'), + makeAPICall('/scan/stop', 'POST') + ]); + + // Stop Bettercap + await makeAPICall('/stop', 'POST'); + + showNotification('Emergency stop completed', 'success'); + BetterMITMState.isRunning = false; + updateBettercapStatus(false); + stopAutoRefresh(); + + } catch (error) { + showNotification('Emergency stop failed: ' + error.message, 'error'); + } +}; + +// Device Management Functions +window.exportDevices = function() { + console.log('exportDevices called'); + showNotification('Exporting devices - feature under development', 'info'); +}; + +window.targetCurrentDevice = function() { + console.log('targetCurrentDevice called'); + showNotification('Device targeting - feature under development', 'info'); +}; + +// Status and UI Update Functions +function updateBettercapStatus(running, interface = null) { + const statusElement = document.getElementById('bettercap-status'); + const statusTextElement = document.getElementById('status-text'); + const interfaceElement = document.getElementById('current-interface'); + const startBtn = document.getElementById('start-bettercap-btn'); + const stopBtn = document.getElementById('stop-bettercap-btn'); + + if (running) { + if (statusElement) statusElement.className = 'bettercap-status running'; + if (statusTextElement) statusTextElement.textContent = 'Running'; + if (startBtn) startBtn.disabled = true; + if (stopBtn) stopBtn.disabled = false; + if (interfaceElement) interfaceElement.textContent = interface || 'Unknown'; + } else { + if (statusElement) statusElement.className = 'bettercap-status stopped'; + if (statusTextElement) statusTextElement.textContent = 'Stopped'; + if (startBtn) startBtn.disabled = false; + if (stopBtn) stopBtn.disabled = true; + if (interfaceElement) interfaceElement.textContent = 'None'; + } +} + +async function checkStatus() { + try { + const result = await makeAPICall('/status'); + + if (result.success) { + BetterMITMState.isRunning = result.bettercap_running; + updateBettercapStatus(result.bettercap_running, result.current_interface); + updateStatistics(result); + updateActiveAttacks(result.active_attacks); + } + } catch (error) { + console.error('Error checking status:', error); + } +} + +async function refreshDevices() { + try { + const result = await makeAPICall('/hosts'); + + if (result.success) { + updateDevicesList(result.hosts); + } + } catch (error) { + console.error('Error refreshing devices:', error); + } +} + +function updateStatistics(data) { + const totalDevicesEl = document.getElementById('total-devices'); + const activeDevicesEl = document.getElementById('active-devices'); + const targetedDevicesEl = document.getElementById('targeted-devices'); + const underAttackEl = document.getElementById('under-attack'); + + if (totalDevicesEl) totalDevicesEl.textContent = data.discovered_hosts?.length || 0; + if (activeDevicesEl) activeDevicesEl.textContent = data.discovered_hosts?.filter(d => d.status === 'online').length || 0; + if (targetedDevicesEl) targetedDevicesEl.textContent = data.discovered_hosts?.filter(d => d.targeted).length || 0; + if (underAttackEl) underAttackEl.textContent = data.discovered_hosts?.filter(d => Object.keys(d.attacks || {}).some(k => d.attacks[k].active)).length || 0; +} + +function updateActiveAttacks(attacks) { + const attackCountEl = document.getElementById('active-attacks-count'); + if (attackCountEl) { + attackCountEl.textContent = Object.keys(attacks || {}).length; + } +} + +function updateDevicesList(devices) { + const container = document.getElementById('devices-list'); + if (!container) return; + + if (!devices || devices.length === 0) { + container.innerHTML = ` +
    + +

    No Devices Found

    +

    Start a network scan to discover devices

    +
    + `; + return; + } + + let html = ''; + devices.forEach(device => { + const statusClass = device.status === 'online' ? 'online' : 'offline'; + const targetedClass = device.targeted ? 'targeted' : ''; + const attacksActive = Object.values(device.attacks || {}).some(a => a.active); + + html += ` +
    +
    +
    +
    + + ${device.hostname || device.ip} + ${device.targeted ? 'TARGETED' : ''} +
    + + ${device.ip} • ${device.mac || 'Unknown MAC'} • ${device.vendor || 'Unknown Vendor'} + + ${attacksActive ? '
    Under Attack
    ' : ''} +
    +
    +
    + `; + }); + + container.innerHTML = html; +} + +// Auto-refresh functions +function startAutoRefresh() { + stopAutoRefresh(); + + BetterMITMState.statusTimer = setInterval(() => { + if (BetterMITMState.isRunning) { + checkStatus(); + } + }, BetterMITMState.refreshInterval); + + BetterMITMState.devicesTimer = setInterval(() => { + if (BetterMITMState.isRunning) { + refreshDevices(); + } + }, BetterMITMState.refreshInterval * 2); + + console.log('Auto-refresh started'); +} + +function stopAutoRefresh() { + if (BetterMITMState.statusTimer) { + clearInterval(BetterMITMState.statusTimer); + BetterMITMState.statusTimer = null; + } + + if (BetterMITMState.devicesTimer) { + clearInterval(BetterMITMState.devicesTimer); + BetterMITMState.devicesTimer = null; + } + + console.log('Auto-refresh stopped'); +} + +// Console and utility functions +window.refreshAll = function() { + console.log('refreshAll called'); + checkStatus(); + refreshDevices(); + showNotification('Refreshed status and devices', 'info'); +}; + +window.cleanupAll = function() { + console.log('cleanupAll called'); + emergencyStop(); +}; + +window.executeCommand = async function() { + const input = document.getElementById('console-input'); + if (!input) return; + + const command = input.value.trim(); + if (!command) return; + + console.log('Executing command:', command); + + try { + const result = await makeAPICall('/command', 'POST', { + command: command + }); + + // Add command to console output + const consoleOutput = document.getElementById('console-output'); + if (consoleOutput) { + const timestamp = new Date().toLocaleTimeString(); + consoleOutput.innerHTML += `
    [${timestamp}] bettercap> ${command}
    `; + + if (result.success) { + if (result.output) { + consoleOutput.innerHTML += `
    [${timestamp}] ${JSON.stringify(result.output, null, 2)}
    `; + } + } else { + consoleOutput.innerHTML += `
    [${timestamp}] Error: ${result.error}
    `; + } + + consoleOutput.scrollTop = consoleOutput.scrollHeight; + } + + // Clear input + input.value = ''; + + } catch (error) { + console.error('Error executing command:', error); + showNotification('Command execution failed: ' + error.message, 'error'); + } +}; + +window.clearConsole = function() { + const consoleOutput = document.getElementById('console-output'); + if (consoleOutput) { + consoleOutput.innerHTML = 'Console cleared.\n'; + } +}; + +window.saveConsoleLog = function() { + const consoleOutput = document.getElementById('console-output'); + if (!consoleOutput) return; + + const content = consoleOutput.textContent; + const blob = new Blob([content], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'bettermitm-console-' + new Date().toISOString().slice(0, 10) + '.log'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + showNotification('Console log saved', 'success'); +}; + +// Form submission handlers +document.addEventListener('DOMContentLoaded', function() { + // Network scan form + const networkScanForm = document.getElementById('network-scan-form'); + if (networkScanForm) { + networkScanForm.addEventListener('submit', async function(e) { + e.preventDefault(); + await startNetworkScan(); + }); + } + + // Sniffer form + const snifferForm = document.getElementById('sniffer-form'); + if (snifferForm) { + snifferForm.addEventListener('submit', async function(e) { + e.preventDefault(); + showNotification('Starting packet sniffer...', 'info'); + + const result = await makeAPICall('/sniffer/start', 'POST', { + protocols: ['tcp', 'udp'], + bpf_filter: '', + max_packets: 100 + }); + + if (result.success) { + showNotification('Packet sniffer started', 'success'); + clearCapture(); + } else { + showNotification('Failed to start sniffer: ' + result.error, 'error'); + } + }); + } + + // Console input handler + const consoleInput = document.getElementById('console-input'); + if (consoleInput) { + consoleInput.addEventListener('keypress', function(e) { + if (e.key === 'Enter') { + executeCommand(); + } + }); + } +}); + +console.log('BetterMITM Complete script loaded successfully'); +console.log('All functions defined and ready'); \ No newline at end of file diff --git a/app/static/js/bettermitm_final.js b/app/static/js/bettermitm_final.js new file mode 100644 index 0000000..5225cf3 --- /dev/null +++ b/app/static/js/bettermitm_final.js @@ -0,0 +1,593 @@ +/** + * BetterMITM Final Implementation - Working Version + */ + +console.log('BetterMITM Final script loading...'); + +// Global state +const BetterMITM = { + isRunning: false, + currentInterface: null, + csrfToken: '', + refreshInterval: null +}; + +// Initialize on DOM load +document.addEventListener('DOMContentLoaded', function() { + // Get CSRF token + BetterMITM.csrfToken = document.querySelector('meta[name=csrf-token]')?.getAttribute('content') || ''; + console.log('CSRF token loaded:', BetterMITM.csrfToken ? 'Yes' : 'No'); + + // Start status checking + checkStatus(); + startStatusRefresh(); + + // Bind console input + const consoleInput = document.getElementById('console-input'); + if (consoleInput) { + consoleInput.addEventListener('keypress', function(e) { + if (e.key === 'Enter') { + executeCommand(); + } + }); + } + + console.log('BetterMITM Final initialized'); +}); + +// API helper +async function apiCall(endpoint, method = 'GET', data = null) { + try { + const options = { + method: method, + headers: { + 'X-CSRFToken': BetterMITM.csrfToken, + 'X-Requested-With': 'XMLHttpRequest' + } + }; + + if (data) { + options.headers['Content-Type'] = 'application/json'; + options.body = JSON.stringify(data); + } + + const response = await fetch(`/bettermitm/api${endpoint}`, options); + const result = await response.json(); + + console.log(`API ${method} ${endpoint}:`, result); + return result; + + } catch (error) { + console.error(`API call failed: ${method} ${endpoint}`, error); + showNotification('API call failed: ' + error.message, 'error'); + return { success: false, error: error.message }; + } +} + +// Notification system +function showNotification(message, type = 'info') { + console.log(`[${type.toUpperCase()}] ${message}`); + + // Create toast + const toastContainer = document.querySelector('.toast-container') || createToastContainer(); + + const toast = document.createElement('div'); + toast.className = `toast align-items-center text-white bg-${getBgClass(type)} border-0`; + toast.setAttribute('role', 'alert'); + toast.innerHTML = ` +
    +
    ${message}
    + +
    + `; + + toastContainer.appendChild(toast); + const bsToast = new bootstrap.Toast(toast); + bsToast.show(); + + // Remove after shown + toast.addEventListener('hidden.bs.toast', () => { + toast.remove(); + }); +} + +function createToastContainer() { + const container = document.createElement('div'); + container.className = 'toast-container position-fixed bottom-0 end-0 p-3'; + document.body.appendChild(container); + return container; +} + +function getBgClass(type) { + switch(type) { + case 'error': return 'danger'; + case 'success': return 'success'; + case 'warning': return 'warning'; + default: return 'primary'; + } +} + +// Main Bettercap functions +window.startBettercapClick = function() { + console.log('Start Bettercap clicked'); + const modal = new bootstrap.Modal(document.getElementById('interfaceModal')); + modal.show(); +}; + +window.stopBettercapClick = async function() { + console.log('Stop Bettercap clicked'); + showNotification('Stopping Bettercap...', 'info'); + + const result = await apiCall('/stop', 'POST'); + + if (result.success) { + showNotification('Bettercap stopped successfully', 'success'); + BetterMITM.isRunning = false; + updateBettercapStatus(false); + stopStatusRefresh(); + } else { + showNotification('Failed to stop Bettercap: ' + result.error, 'error'); + } +}; + +window.startBettercapWithInterface = async function() { + const interfaceSelect = document.getElementById('interface-select'); + if (!interfaceSelect || !interfaceSelect.value) { + showNotification('Please select a network interface', 'error'); + return; + } + + const selectedInterface = interfaceSelect.value; + showNotification(`Starting Bettercap on ${selectedInterface}...`, 'info'); + + // Hide modal + const modal = bootstrap.Modal.getInstance(document.getElementById('interfaceModal')); + if (modal) modal.hide(); + + // Start Bettercap + const result = await apiCall('/start', 'POST', { + interface: selectedInterface + }); + + if (result.success) { + showNotification(`Bettercap started on ${selectedInterface}`, 'success'); + BetterMITM.isRunning = true; + BetterMITM.currentInterface = selectedInterface; + updateBettercapStatus(true, selectedInterface); + startStatusRefresh(); + } else { + showNotification('Failed to start Bettercap: ' + result.error, 'error'); + } +}; + +window.showSudoModal = function() { + const interfaceSelect = document.getElementById('interface-select'); + if (!interfaceSelect || !interfaceSelect.value) { + showNotification('Please select an interface first', 'error'); + return; + } + + // Update sudo modal with selected interface + const interfaceDisplay = document.getElementById('selected-interface-name'); + if (interfaceDisplay) { + interfaceDisplay.textContent = interfaceSelect.value; + } + + // Show sudo modal + const interfaceModal = bootstrap.Modal.getInstance(document.getElementById('interfaceModal')); + if (interfaceModal) interfaceModal.hide(); + + const sudoModal = new bootstrap.Modal(document.getElementById('sudoModal')); + sudoModal.show(); +}; + +window.startBettercapWithSudo = async function() { + const sudoPassword = document.getElementById('sudo-password').value; + const interfaceSelect = document.getElementById('interface-select'); + + if (!sudoPassword.trim()) { + showNotification('Please enter sudo password', 'error'); + return; + } + + if (!interfaceSelect || !interfaceSelect.value) { + showNotification('Interface not selected', 'error'); + return; + } + + const selectedInterface = interfaceSelect.value; + showNotification(`Starting Bettercap with sudo on ${selectedInterface}...`, 'info'); + + // Hide modal + const modal = bootstrap.Modal.getInstance(document.getElementById('sudoModal')); + if (modal) modal.hide(); + + // Start with sudo + const result = await apiCall('/start', 'POST', { + interface: selectedInterface, + sudo_password: sudoPassword + }); + + // Clear password + document.getElementById('sudo-password').value = ''; + + if (result.success) { + showNotification(`Bettercap started with sudo on ${selectedInterface}`, 'success'); + BetterMITM.isRunning = true; + BetterMITM.currentInterface = selectedInterface; + updateBettercapStatus(true, selectedInterface); + startStatusRefresh(); + } else { + showNotification('Failed to start Bettercap with sudo: ' + result.error, 'error'); + } +}; + +// Network functions +window.startNetworkScan = async function() { + console.log('Start network scan clicked'); + + if (!BetterMITM.isRunning) { + showNotification('Please start Bettercap first', 'error'); + return; + } + + showNotification('Starting network scan...', 'info'); + + const result = await apiCall('/scan/start', 'POST', { + scan_type: 'arp', + target_range: null + }); + + if (result.success) { + showNotification('Network scan started', 'success'); + } else { + showNotification('Failed to start scan: ' + result.error, 'error'); + } +}; + +window.stopNetworkScan = async function() { + console.log('Stop network scan clicked'); + + const result = await apiCall('/scan/stop', 'POST'); + + if (result.success) { + showNotification('Network scan stopped', 'success'); + } else { + showNotification('Failed to stop scan: ' + result.error, 'error'); + } +}; + +window.startARPSpoofAll = function() { + if (confirm('This will start ARP spoofing against all discovered devices. Continue?')) { + showNotification('ARP spoofing all - feature under development', 'warning'); + } +}; + +window.stopARPSpoof = async function() { + const result = await apiCall('/arp/stop', 'POST'); + if (result.success) { + showNotification('ARP spoofing stopped', 'success'); + } else { + showNotification('Failed to stop ARP spoofing: ' + result.error, 'error'); + } +}; + +window.stopDNSSpoof = async function() { + const result = await apiCall('/dns/stop', 'POST'); + if (result.success) { + showNotification('DNS spoofing stopped', 'success'); + } else { + showNotification('Failed to stop DNS spoofing: ' + result.error, 'error'); + } +}; + +window.stopProxy = async function() { + const result = await apiCall('/proxy/stop', 'POST'); + if (result.success) { + showNotification('HTTP proxy stopped', 'success'); + } else { + showNotification('Failed to stop proxy: ' + result.error, 'error'); + } +}; + +window.stopSniffer = async function() { + const result = await apiCall('/sniffer/stop', 'POST'); + if (result.success) { + showNotification('Packet sniffer stopped', 'success'); + } else { + showNotification('Failed to stop sniffer: ' + result.error, 'error'); + } +}; + +window.startPacketCapture = async function() { + console.log('Start packet capture clicked'); + + if (!BetterMITM.isRunning) { + showNotification('Please start Bettercap first', 'error'); + return; + } + + showNotification('Starting packet capture...', 'info'); + + const result = await apiCall('/sniffer/start', 'POST', { + protocols: ['tcp', 'udp'], + bpf_filter: '', + max_packets: 100 + }); + + if (result.success) { + showNotification('Packet capture started', 'success'); + clearCapture(); + } else { + showNotification('Failed to start packet capture: ' + result.error, 'error'); + } +}; + +window.clearCapture = function() { + const packetOutput = document.getElementById('packet-output'); + if (packetOutput) { + packetOutput.innerHTML = ` +
    + +

    No packets captured yet. Start packet capture to see network traffic.

    +
    + `; + } +}; + +window.emergencyStop = async function() { + if (!confirm('This will stop all active attacks and operations. Continue?')) { + return; + } + + showNotification('Emergency stop initiated...', 'warning'); + + try { + // Stop all attacks + await Promise.all([ + apiCall('/arp/stop', 'POST'), + apiCall('/dns/stop', 'POST'), + apiCall('/proxy/stop', 'POST'), + apiCall('/sniffer/stop', 'POST'), + apiCall('/scan/stop', 'POST') + ]); + + // Stop Bettercap + await apiCall('/stop', 'POST'); + + showNotification('Emergency stop completed', 'success'); + BetterMITM.isRunning = false; + updateBettercapStatus(false); + stopStatusRefresh(); + + } catch (error) { + showNotification('Emergency stop failed: ' + error.message, 'error'); + } +}; + +window.exportDevices = function() { + showNotification('Export devices - feature under development', 'info'); +}; + +window.targetCurrentDevice = function() { + showNotification('Target device - feature under development', 'info'); +}; + +// Console functions +window.executeCommand = async function() { + const input = document.getElementById('console-input'); + if (!input) return; + + const command = input.value.trim(); + if (!command) return; + + console.log('Executing command:', command); + + const result = await apiCall('/command', 'POST', { command: command }); + + // Add to console output + const consoleOutput = document.getElementById('console-output'); + if (consoleOutput) { + const timestamp = new Date().toLocaleTimeString(); + consoleOutput.innerHTML += `
    [${timestamp}] bettercap> ${command}
    `; + + if (result.success) { + if (result.data) { + consoleOutput.innerHTML += `
    [${timestamp}] ${JSON.stringify(result.data, null, 2)}
    `; + } + } else { + consoleOutput.innerHTML += `
    [${timestamp}] Error: ${result.error}
    `; + } + + consoleOutput.scrollTop = consoleOutput.scrollHeight; + } + + // Clear input + input.value = ''; +}; + +window.clearConsole = function() { + const consoleOutput = document.getElementById('console-output'); + if (consoleOutput) { + consoleOutput.innerHTML = 'Console cleared.\n'; + } +}; + +window.saveConsoleLog = function() { + const consoleOutput = document.getElementById('console-output'); + if (!consoleOutput) return; + + const content = consoleOutput.textContent; + const blob = new Blob([content], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'bettermitm-console-' + new Date().toISOString().slice(0, 10) + '.log'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + showNotification('Console log saved', 'success'); +}; + +window.refreshAll = function() { + console.log('Refresh all clicked'); + checkStatus(); + showNotification('Status refreshed', 'info'); +}; + +window.cleanupAll = function() { + emergencyStop(); +}; + +window.diagnoseSystem = async function() { + console.log('Running system diagnosis...'); + showNotification('Running system diagnosis...', 'info'); + + try { + const result = await apiCall('/diagnose'); + + if (result.success) { + const diagnosis = result.diagnosis; + + console.log('Diagnosis results:', diagnosis); + + let message = 'System Diagnosis:\n\n'; + message += `Bettercap Installed: ${diagnosis.bettercap_installed ? 'Yes' : 'No'}\n`; + message += `Install Message: ${diagnosis.install_message}\n`; + message += `Currently Running: ${diagnosis.is_running ? 'Yes' : 'No'}\n`; + message += `Current Interface: ${diagnosis.current_interface || 'None'}\n`; + message += `Available Interfaces: ${diagnosis.available_interfaces.length}\n`; + + if (diagnosis.api_accessible) { + message += `API Status: Accessible (${diagnosis.api_status})\n`; + } else if (diagnosis.api_error) { + message += `API Status: Error - ${diagnosis.api_error}\n`; + } + + alert(message); + + if (!diagnosis.bettercap_installed) { + showNotification('Bettercap is not installed! Please install it first.', 'error'); + } else { + showNotification('Diagnosis completed - check console for details', 'success'); + } + } else { + showNotification('Diagnosis failed: ' + result.error, 'error'); + } + } catch (error) { + showNotification('Diagnosis error: ' + error.message, 'error'); + } +}; + +window.testAPIConnection = async function() { + console.log('Testing API connection...'); + showNotification('Testing Bettercap API connection...', 'info'); + + try { + const result = await apiCall('/test'); + + if (result.success) { + const testResults = result.test_results; + + console.log('API test results:', testResults); + + let message = 'API Connection Test Results:\n\n'; + message += `Overall Success: ${testResults.success ? 'Yes' : 'No'}\n`; + message += `Summary: ${testResults.summary || 'N/A'}\n\n`; + + if (testResults.results) { + message += 'Individual Tests:\n'; + Object.entries(testResults.results).forEach(([test, success]) => { + message += `- ${test}: ${success ? 'PASS' : 'FAIL'}\n`; + }); + } + + if (testResults.error) { + message += `\nError: ${testResults.error}\n`; + } + + alert(message); + + if (testResults.success) { + showNotification('API connection test passed', 'success'); + } else { + showNotification('API connection test failed', 'error'); + } + } else { + showNotification('API test failed: ' + result.error, 'error'); + } + } catch (error) { + showNotification('API test error: ' + error.message, 'error'); + } +}; + +// UI update functions +function updateBettercapStatus(running, interface = null) { + const statusElement = document.getElementById('bettercap-status'); + const statusTextElement = document.getElementById('status-text'); + const interfaceElement = document.getElementById('current-interface'); + const startBtn = document.getElementById('start-bettercap-btn'); + const stopBtn = document.getElementById('stop-bettercap-btn'); + + if (running) { + if (statusElement) statusElement.className = 'bettercap-status running'; + if (statusTextElement) statusTextElement.textContent = 'Running'; + if (startBtn) startBtn.disabled = true; + if (stopBtn) stopBtn.disabled = false; + if (interfaceElement) interfaceElement.textContent = interface || 'Unknown'; + } else { + if (statusElement) statusElement.className = 'bettercap-status stopped'; + if (statusTextElement) statusTextElement.textContent = 'Stopped'; + if (startBtn) startBtn.disabled = false; + if (stopBtn) stopBtn.disabled = true; + if (interfaceElement) interfaceElement.textContent = 'None'; + } +} + +// Status checking +async function checkStatus() { + try { + const result = await apiCall('/status'); + + if (result.success) { + BetterMITM.isRunning = result.bettercap_running; + updateBettercapStatus(result.bettercap_running, result.current_interface); + updateStatistics(result); + } + } catch (error) { + console.error('Status check failed:', error); + } +} + +function updateStatistics(data) { + const totalDevicesEl = document.getElementById('total-devices'); + const activeDevicesEl = document.getElementById('active-devices'); + const targetedDevicesEl = document.getElementById('targeted-devices'); + const underAttackEl = document.getElementById('under-attack'); + + if (totalDevicesEl) totalDevicesEl.textContent = data.discovered_hosts?.length || 0; + if (activeDevicesEl) activeDevicesEl.textContent = data.discovered_hosts?.filter(d => d.status === 'online').length || 0; + if (targetedDevicesEl) targetedDevicesEl.textContent = data.discovered_hosts?.filter(d => d.targeted).length || 0; + if (underAttackEl) underAttackEl.textContent = Object.keys(data.active_attacks || {}).length; +} + +function startStatusRefresh() { + stopStatusRefresh(); + BetterMITM.refreshInterval = setInterval(() => { + if (BetterMITM.isRunning) { + checkStatus(); + } + }, 3000); +} + +function stopStatusRefresh() { + if (BetterMITM.refreshInterval) { + clearInterval(BetterMITM.refreshInterval); + BetterMITM.refreshInterval = null; + } +} + +console.log('BetterMITM Final script loaded successfully'); \ No newline at end of file diff --git a/app/static/js/bettermitm_simple.js b/app/static/js/bettermitm_simple.js new file mode 100644 index 0000000..0e8c3af --- /dev/null +++ b/app/static/js/bettermitm_simple.js @@ -0,0 +1,167 @@ +/** + * Simplified BetterMITM JavaScript for testing + */ + +console.log('Simple BetterMITM script loading...'); + +// Simple global functions for testing +window.startBettercapClick = function() { + console.log('startBettercapClick called - Simple version'); + alert('Start Bettercap clicked! (Simple version)'); + + // Try to show interface modal + try { + const modal = new bootstrap.Modal(document.getElementById('interfaceModal')); + modal.show(); + } catch (error) { + console.error('Error showing modal:', error); + } +}; + +window.stopBettercapClick = function() { + console.log('stopBettercapClick called - Simple version'); + alert('Stop Bettercap clicked! (Simple version)'); +}; + +window.startBettercapWithInterface = async function() { + console.log('startBettercapWithInterface called'); + + const interfaceSelect = document.getElementById('interface-select'); + if (!interfaceSelect) { + console.error('Interface select not found!'); + alert('Interface selection not found'); + return; + } + + const selectedInterface = interfaceSelect.value; + if (!selectedInterface) { + alert('Please select a network interface'); + return; + } + + console.log('Selected interface:', selectedInterface); + + // Hide modal + const interfaceModal = bootstrap.Modal.getInstance(document.getElementById('interfaceModal')); + if (interfaceModal) { + interfaceModal.hide(); + } + + // Make API call to start Bettercap + try { + const csrfToken = document.querySelector('meta[name=csrf-token]')?.getAttribute('content') || ''; + + const response = await fetch('https://github.com/bettermitm/api/start', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken + }, + body: JSON.stringify({ + interface: selectedInterface + }) + }); + + const result = await response.json(); + + if (result.success) { + alert('Bettercap started successfully on ' + selectedInterface); + console.log('Bettercap started:', result); + } else { + alert('Failed to start Bettercap: ' + result.error); + console.error('Bettercap start failed:', result); + } + } catch (error) { + console.error('Error starting Bettercap:', error); + alert('Error starting Bettercap: ' + error.message); + } +}; + +window.showSudoModal = function() { + console.log('showSudoModal called'); + + const interfaceSelect = document.getElementById('interface-select'); + if (!interfaceSelect || !interfaceSelect.value) { + alert('Please select an interface first'); + return; + } + + // Update sudo modal with selected interface + const interfaceDisplay = document.getElementById('selected-interface-name'); + if (interfaceDisplay) { + interfaceDisplay.textContent = interfaceSelect.value; + } + + // Hide interface modal and show sudo modal + const interfaceModal = bootstrap.Modal.getInstance(document.getElementById('interfaceModal')); + if (interfaceModal) { + interfaceModal.hide(); + } + + const sudoModal = new bootstrap.Modal(document.getElementById('sudoModal')); + sudoModal.show(); +}; + +window.startBettercapWithSudo = async function() { + console.log('startBettercapWithSudo called'); + + const sudoPassword = document.getElementById('sudo-password').value; + const interfaceSelect = document.getElementById('interface-select'); + + if (!sudoPassword.trim()) { + alert('Please enter sudo password'); + return; + } + + if (!interfaceSelect || !interfaceSelect.value) { + alert('Interface not selected'); + return; + } + + const selectedInterface = interfaceSelect.value; + + // Hide sudo modal + const sudoModal = bootstrap.Modal.getInstance(document.getElementById('sudoModal')); + if (sudoModal) { + sudoModal.hide(); + } + + // Make API call with sudo + try { + const csrfToken = document.querySelector('meta[name=csrf-token]')?.getAttribute('content') || ''; + + const response = await fetch('https://github.com/bettermitm/api/start', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken + }, + body: JSON.stringify({ + interface: selectedInterface, + sudo_password: sudoPassword + }) + }); + + const result = await response.json(); + + // Clear password field + document.getElementById('sudo-password').value = ''; + + if (result.success) { + alert('Bettercap started successfully with sudo privileges on ' + selectedInterface); + console.log('Bettercap started with sudo:', result); + } else { + alert('Failed to start Bettercap with sudo: ' + result.error); + console.error('Bettercap sudo start failed:', result); + } + } catch (error) { + console.error('Error starting Bettercap with sudo:', error); + alert('Error starting Bettercap with sudo: ' + error.message); + } +}; + +console.log('Simple BetterMITM script loaded successfully'); +console.log('Functions defined:', { + startBettercapClick: typeof window.startBettercapClick, + stopBettercapClick: typeof window.stopBettercapClick +}); \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html index a142d34..050be19 100755 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -152,6 +152,13 @@ MetaSpidey + {% endif %} diff --git a/app/templates/bettermitm/index.html b/app/templates/bettermitm/index.html new file mode 100644 index 0000000..f128ddf --- /dev/null +++ b/app/templates/bettermitm/index.html @@ -0,0 +1,666 @@ +{% extends "base.html" %} +{% set title = "BetterMITM - Advanced Network Security Interface" %} + +{% block css %} + +{% endblock %} + +{% block content %} + +
    +
    + +
    +

    BetterMITM

    +

    Advanced Bettercap GUI Interface for Network Security Testing

    +
    +
    + + +
    +
    +
    + + +
    +
    +
    + Bettercap Status: + Stopped +
    +
    + + +
    +
    +
    + Interface: None | Active Attacks: 0 +
    +
    + + + + + +
    + +
    +
    +
    +
    +
    Network Overview
    +
    +
    + +

    Network Map

    +

    Start Bettercap and begin network discovery to see devices

    +
    +
    +
    +
    +
    +
    +
    Statistics
    +
    +
    +
    0
    + Total Devices +
    +
    +
    0
    + Active +
    +
    +
    +
    +
    0
    + Targeted +
    +
    +
    0
    + Under Attack +
    +
    +
    + +
    +
    Quick Actions
    +
    + + + + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    Network Discovery
    +
    +
    + {{ network_scan_form.interface.label(class="form-label") }} + {{ network_scan_form.interface(class="form-select", id="network-interface") }} +
    +
    + {{ network_scan_form.scan_type.label(class="form-label") }} + {{ network_scan_form.scan_type(class="form-select") }} +
    +
    + {{ network_scan_form.target_range.label(class="form-label") }} + {{ network_scan_form.target_range(class="form-control") }} +
    +
    + {{ network_scan_form.timeout.label(class="form-label") }} + {{ network_scan_form.timeout(class="form-control") }} +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    Discovered Devices
    + +
    +
    +
    + +

    No Devices Found

    +

    Start a network scan to discover devices

    +
    +
    +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    ARP Spoofing
    +
    +
    +
    + {{ arp_spoof_form.target_ip.label(class="form-label small") }} + {{ arp_spoof_form.target_ip(class="form-control form-control-sm") }} +
    +
    + {{ arp_spoof_form.gateway_ip.label(class="form-label small") }} + {{ arp_spoof_form.gateway_ip(class="form-control form-control-sm") }} +
    +
    +
    +
    + {{ arp_spoof_form.bidirectional(class="form-check-input") }} + {{ arp_spoof_form.bidirectional.label(class="form-check-label small") }} +
    +
    + {{ arp_spoof_form.continuous(class="form-check-input") }} + {{ arp_spoof_form.continuous.label(class="form-check-label small") }} +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    DNS Spoofing
    +
    +
    + {{ dns_spoof_form.target_domain.label(class="form-label small") }} + {{ dns_spoof_form.target_domain(class="form-control form-control-sm") }} +
    +
    + {{ dns_spoof_form.spoofed_ip.label(class="form-label small") }} + {{ dns_spoof_form.spoofed_ip(class="form-control form-control-sm") }} +
    +
    +
    + {{ dns_spoof_form.all_domains(class="form-check-input") }} + {{ dns_spoof_form.all_domains.label(class="form-check-label small") }} +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    HTTP Proxy
    +
    +
    + {{ proxy_form.proxy_port.label(class="form-label small") }} + {{ proxy_form.proxy_port(class="form-control form-control-sm") }} +
    +
    +
    + {{ proxy_form.transparent(class="form-check-input") }} + {{ proxy_form.transparent.label(class="form-check-label small") }} +
    +
    + {{ proxy_form.log_requests(class="form-check-input") }} + {{ proxy_form.log_requests.label(class="form-check-label small") }} +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    Device Targeting
    +
    +
    + {{ device_form.device_ip.label(class="form-label small") }} + {{ device_form.device_ip(class="form-control form-control-sm") }} +
    +
    + {{ device_form.device_name.label(class="form-label small") }} + {{ device_form.device_name(class="form-control form-control-sm") }} +
    +
    + +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    Packet Sniffer
    +
    +
    + {{ packet_sniffer_form.interface.label(class="form-label") }} + {{ packet_sniffer_form.interface(class="form-select", id="sniffer-interface") }} +
    +
    + + {% for choice in packet_sniffer_form.protocols %} +
    + {{ choice(class="form-check-input") }} + {{ choice.label(class="form-check-label") }} +
    + {% endfor %} +
    +
    + {{ packet_sniffer_form.filter_expression.label(class="form-label") }} + {{ packet_sniffer_form.filter_expression(class="form-control") }} +
    +
    + {{ packet_sniffer_form.max_packets.label(class="form-label") }} + {{ packet_sniffer_form.max_packets(class="form-control") }} +
    +
    + + + +
    +
    +
    +
    +
    +
    +
    +
    Captured Packets
    + 0 packets +
    +
    +
    + +

    No packets captured yet. Start packet capture to see network traffic.

    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    Bettercap Console
    +
    + + +
    +
    + +
    +Welcome to BetterMITM Console +Type 'help' for available commands or use the interface above. +
    + +
    + bettercap> + + +
    +
    +
    +
    + + + + + + + + + +{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/diagnose_bettermitm.py b/diagnose_bettermitm.py new file mode 100644 index 0000000..4659133 --- /dev/null +++ b/diagnose_bettermitm.py @@ -0,0 +1,386 @@ +#!/usr/bin/env python3 +""" +BetterMITM Diagnostic Script +Helps diagnose issues with BetterMITM module and Bettercap integration +""" + +import sys +import os +import subprocess +import time +import json +from pathlib import Path + +# Try to import optional dependencies +try: + import psutil + PSUTIL_AVAILABLE = True +except ImportError: + PSUTIL_AVAILABLE = False + print("[WARNING] psutil not available. Network interface detection will be limited.") + +try: + import requests + REQUESTS_AVAILABLE = True +except ImportError: + REQUESTS_AVAILABLE = False + print("[WARNING] requests not available. API testing will be skipped.") + +def print_header(title): + print(f"\n{'='*60}") + print(f" {title}") + print(f"{'='*60}") + +def print_status(message): + print(f"[INFO] {message}") + +def print_error(message): + print(f"[ERROR] {message}") + +def print_success(message): + print(f"[SUCCESS] {message}") + +def print_warning(message): + print(f"[WARNING] {message}") + +def check_bettercap_installation(): + print_header("Checking Bettercap Installation") + + # Check if bettercap is in PATH + if subprocess.run(['which', 'bettercap'], capture_output=True).returncode == 0: + bettercap_path = subprocess.run(['which', 'bettercap'], capture_output=True, text=True).stdout.strip() + print_success(f"Bettercap found at: {bettercap_path}") + + # Check version + try: + result = subprocess.run(['bettercap', '-version'], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + print_success(f"Version: {result.stdout.strip()}") + else: + print_warning(f"Version check failed: {result.stderr}") + except subprocess.TimeoutExpired: + print_warning("Bettercap version check timed out") + except Exception as e: + print_error(f"Error checking version: {e}") + + # Check capabilities + try: + result = subprocess.run(['getcap', bettercap_path], capture_output=True, text=True) + if 'cap_net_raw,cap_net_admin' in result.stdout: + print_success("Network capabilities are set correctly") + else: + print_warning("Network capabilities not set - may need to run as root") + print_status(f"Run: sudo setcap cap_net_raw,cap_net_admin=eip {bettercap_path}") + except Exception as e: + print_error(f"Error checking capabilities: {e}") + + else: + print_error("Bettercap not found in PATH") + return False + + return True + +def check_network_interfaces(): + print_header("Checking Network Interfaces") + + if not PSUTIL_AVAILABLE: + print_status("Using system commands to check interfaces...") + try: + # Use ip command as fallback + result = subprocess.run(['ip', 'addr', 'show'], capture_output=True, text=True) + if result.returncode == 0: + lines = result.stdout.split('\n') + interfaces = [] + current_interface = None + + for line in lines: + if ': ' in line and 'inet ' not in line: + parts = line.split(': ') + if len(parts) >= 2: + current_interface = parts[1].split('@')[0] + elif 'inet ' in line and current_interface: + inet_part = line.strip().split(' ')[1] + ip = inet_part.split('/')[0] + interfaces.append({'name': current_interface, 'ip': ip}) + print_success(f" {current_interface}: {ip}") + + if interfaces: + print_status(f"Found {len(interfaces)} network interfaces with IP addresses") + return interfaces + else: + print_error("No valid interfaces found") + return False + else: + print_error("Failed to get network interfaces") + return False + except Exception as e: + print_error(f"Error checking interfaces: {e}") + return False + + try: + interfaces = psutil.net_if_addrs() + print_status(f"Found {len(interfaces)} network interfaces:") + + valid_interfaces = [] + for interface, addrs in interfaces.items(): + has_ipv4 = False + for addr in addrs: + if addr.family == 2: # IPv4 + has_ipv4 = True + valid_interfaces.append({ + 'name': interface, + 'ip': addr.address, + 'netmask': addr.netmask + }) + print_success(f" {interface}: {addr.address}/{addr.netmask}") + break + + if not has_ipv4: + print_warning(f" {interface}: No IPv4 address") + + if not valid_interfaces: + print_error("No valid IPv4 interfaces found") + return False + + return valid_interfaces + + except Exception as e: + print_error(f"Error checking interfaces: {e}") + return False + +def test_bettercap_start(): + print_header("Testing Bettercap Startup") + + # Test basic startup + print_status("Testing basic Bettercap startup...") + + try: + # Start Bettercap with API + cmd = [ + 'bettercap', + '-eval', + 'api.rest on; set api.rest.port 8082; set api.rest.username admin; set api.rest.password admin123; sleep 2; api.rest off; quit' + ] + + print_status(f"Running: {' '.join(cmd)}") + + result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) + + if result.returncode == 0: + print_success("Bettercap started and stopped successfully") + if 'REST API server starting on' in result.stdout: + print_success("REST API was enabled successfully") + return True + else: + print_error(f"Bettercap failed to start: {result.stderr}") + return False + + except subprocess.TimeoutExpired: + print_error("Bettercap startup timed out") + return False + except Exception as e: + print_error(f"Error testing Bettercap: {e}") + return False + +def test_api_connection(): + print_header("Testing Bettercap API Connection") + + if not REQUESTS_AVAILABLE: + print_warning("Requests library not available, skipping API test") + print_status("You can test manually with:") + print_status(" 1. Start bettercap: bettercap -eval 'api.rest on'") + print_status(" 2. Test: curl -X POST http://127.0.0.1:8081/api/session -d '{\"username\":\"admin\",\"password\":\"admin123\"}'") + return True # Don't fail the test if we can't run it + + print_status("Starting Bettercap with API...") + + # Start Bettercap in background + try: + process = subprocess.Popen([ + 'bettercap', + '-eval', + 'api.rest on; set api.rest.port 8082; set api.rest.username admin; set api.rest.password admin123' + ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + # Wait for API to start + time.sleep(3) + + # Test API connection + try: + response = requests.post( + 'http://127.0.0.1:8082/api/session', + json={'username': 'admin', 'password': 'admin123'}, + timeout=5 + ) + + if response.status_code == 200: + print_success("API authentication successful") + + # Test command execution + session_cookie = response.cookies.get('session') + if session_cookie: + cmd_response = requests.post( + 'http://127.0.0.1:8082/api/session', + json={'cmd': 'help'}, + cookies={'session': session_cookie}, + timeout=5 + ) + + if cmd_response.status_code == 200: + print_success("Command execution successful") + return True + else: + print_error(f"Command execution failed: {cmd_response.status_code}") + else: + print_error("No session cookie received") + else: + print_error(f"API authentication failed: {response.status_code}") + + except requests.exceptions.ConnectionError: + print_error("Could not connect to Bettercap API") + except requests.exceptions.Timeout: + print_error("API connection timed out") + except Exception as e: + print_error(f"API test error: {e}") + + except Exception as e: + print_error(f"Error starting Bettercap: {e}") + finally: + # Clean up process + if 'process' in locals(): + try: + process.terminate() + process.wait(timeout=5) + except: + try: + process.kill() + except: + pass + + return False + +def check_bettercap_config(): + print_header("Checking Bettercap Configuration") + + config_path = Path.home() / '.bettercap' / 'config.yml' + + if config_path.exists(): + print_success(f"Config file found: {config_path}") + try: + with open(config_path, 'r') as f: + content = f.read() + if 'api:' in content and 'rest:' in content: + print_success("API configuration found in config file") + else: + print_warning("API configuration not found in config file") + except Exception as e: + print_error(f"Error reading config file: {e}") + else: + print_warning("No Bettercap config file found") + print_status("Creating default configuration...") + + try: + config_path.parent.mkdir(exist_ok=True) + + config_content = """# Bettercap Configuration for BetterMITM +api: + rest: + enabled: true + port: 8081 + username: admin + password: admin123 + certificate: "" + key: "" + allow_origin: "*" + +# Network interface (auto-detect by default) +interface: "" + +# Logging +log: + level: info + output: "" +""" + + with open(config_path, 'w') as f: + f.write(config_content) + + config_path.chmod(0o600) + print_success(f"Default config created at: {config_path}") + + except Exception as e: + print_error(f"Error creating config file: {e}") + +def check_permissions(): + print_header("Checking Permissions") + + # Check if running as root + if os.geteuid() == 0: + print_warning("Running as root - this should work but is not recommended") + else: + print_status("Running as non-root user") + + # Check capabilities + bettercap_path = subprocess.run(['which', 'bettercap'], capture_output=True, text=True).stdout.strip() + if bettercap_path: + try: + result = subprocess.run(['getcap', bettercap_path], capture_output=True, text=True) + if 'cap_net_raw,cap_net_admin' in result.stdout: + print_success("Network capabilities are properly set") + else: + print_error("Network capabilities not set") + print_status("Run this to fix:") + print_status(f"sudo setcap cap_net_raw,cap_net_admin=eip {bettercap_path}") + except Exception as e: + print_error(f"Error checking capabilities: {e}") + +def main(): + print("BetterMITM Diagnostic Tool") + print("=" * 50) + + # Run all checks + checks = [ + check_bettercap_installation, + check_network_interfaces, + check_permissions, + check_bettercap_config, + test_bettercap_start, + test_api_connection + ] + + results = [] + for check in checks: + try: + result = check() + results.append(result) + except Exception as e: + print_error(f"Check failed with exception: {e}") + results.append(False) + + # Summary + print_header("Diagnostic Summary") + + passed = sum(1 for r in results if r) + total = len(results) + + print_status(f"Checks passed: {passed}/{total}") + + if passed == total: + print_success("All checks passed! BetterMITM should work correctly.") + elif passed >= total - 1: + print_warning("Most checks passed. BetterMITM might work with minor issues.") + else: + print_error("Multiple checks failed. BetterMITM likely won't work properly.") + print_status("\nCommon solutions:") + print_status("1. Install Bettercap: sudo apt install bettercap") + print_status("2. Set capabilities: sudo setcap cap_net_raw,cap_net_admin=eip $(which bettercap)") + print_status("3. Check network interfaces: ip link show") + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + print("\n[INFO] Diagnostic interrupted by user") + except Exception as e: + print_error(f"Diagnostic failed: {e}") + sys.exit(1) \ No newline at end of file diff --git a/fix_bettercap.sh b/fix_bettercap.sh new file mode 100644 index 0000000..1c2a662 --- /dev/null +++ b/fix_bettercap.sh @@ -0,0 +1,196 @@ +#!/bin/bash +# Quick fix script for BetterMITM/Bettercap setup +# Run this script to configure Bettercap permissions and configuration + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_status() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_header() { + echo -e "${BLUE}=== $1 ===${NC}" +} + +print_header "BetterMITM Quick Fix Script" + +# Check if bettercap is installed +if ! command -v bettercap &> /dev/null; then + print_error "Bettercap is not installed!" + print_status "Installing Bettercap..." + + if command -v apt-get &> /dev/null; then + sudo apt-get update + sudo apt-get install -y bettercap + else + print_error "Please install Bettercap manually:" + echo " Download from: https://github.com/bettercap/bettercap/releases" + exit 1 + fi +fi + +# Get bettercap path +BETTERCAP_PATH=$(which bettercap) +print_status "Bettercap found at: $BETTERCAP_PATH" + +# Check and set network capabilities +print_header "Setting Network Capabilities" + +if getcap "$BETTERCAP_PATH" | grep -q "cap_net_raw,cap_net_admin"; then + print_success "Network capabilities are already set" +else + print_status "Setting network capabilities..." + if sudo setcap cap_net_raw,cap_net_admin=eip "$BETTERCAP_PATH"; then + print_success "Network capabilities set successfully" + else + print_error "Failed to set network capabilities" + print_warning "You may need to run BetterMITM as root" + fi +fi + +# Create configuration directory and file +print_header "Creating Bettercap Configuration" + +CONFIG_DIR="$HOME/.bettercap" +CONFIG_FILE="$CONFIG_DIR/config.yml" + +mkdir -p "$CONFIG_DIR" + +if [ -f "$CONFIG_FILE" ]; then + print_warning "Configuration file already exists: $CONFIG_FILE" + print_status "Backing up existing configuration..." + cp "$CONFIG_FILE" "$CONFIG_FILE.backup.$(date +%s)" +fi + +print_status "Creating default configuration..." + +cat > "$CONFIG_FILE" << 'EOF' +# Bettercap Configuration for BetterMITM +api: + rest: + enabled: true + port: 8081 + username: admin + password: admin123 + certificate: "" + key: "" + allow_origin: "*" + +# Network interface (auto-detect by default) +interface: "" + +# Logging +log: + level: info + output: "" + +# Default modules +modules: + net.recon: + enabled: true + net.probe: + enabled: false + arp.spoof: + enabled: false + dns.spoof: + enabled: false + http.proxy: + enabled: false + net.sniff: + enabled: false +EOF + +chmod 600 "$CONFIG_FILE" +print_success "Configuration file created: $CONFIG_FILE" + +# Test bettercap startup +print_header "Testing Bettercap" + +print_status "Testing basic Bettercap functionality..." +if timeout 5 bettercap -eval "help; quit" >/dev/null 2>&1; then + print_success "Bettercap basic test passed" +else + print_error "Bettercap basic test failed" + print_warning "This might be normal if you're in a restricted environment" +fi + +print_status "Testing Bettercap API startup..." +if timeout 10 bettercap -eval "api.rest on; set api.rest.port 8083; sleep 2; api.rest off; quit" >/dev/null 2>&1; then + print_success "Bettercap API test passed" +else + print_error "Bettercap API test failed" + print_warning "Check the output manually: bettercap -eval 'api.rest on; help; quit'" +fi + +# Show network interfaces +print_header "Network Interfaces" +print_status "Available network interfaces:" + +if command -v ip &> /dev/null; then + ip -4 addr show | grep -E "^[0-9]+:|inet " | while read line; do + if [[ $line =~ ^[0-9]+: ]]; then + interface=$(echo $line | cut -d' ' -f2 | cut -d':' -f1) + printf " Interface: %-10s" "$interface" + elif [[ $line =~ inet ]]; then + ip_addr=$(echo $line | awk '{print $2}' | cut -d'/' -f1) + echo " IP: $ip_addr" + fi + done +else + ifconfig | grep -E "^[a-zA-Z0-9]+|inet " | while read line; do + if [[ $line =~ ^[a-zA-Z0-9]+ ]]; then + interface=$(echo $line | cut -d' ' -f1 | cut -d':' -f1) + printf " Interface: %-10s" "$interface" + elif [[ $line =~ inet ]]; then + ip_addr=$(echo $line | awk '{print $2}') + echo " IP: $ip_addr" + fi + done +fi + +print_header "Setup Complete" +print_success "BetterMITM setup completed!" + +echo +print_status "Next steps:" +echo " 1. Start your CoreSecFrame application" +echo " 2. Navigate to /bettermitm" +echo " 3. Click 'Start Bettercap' and select a network interface" +echo +print_status "If you still have issues:" +echo " 1. Run: python3 diagnose_bettermitm.py" +echo " 2. Check logs in the CoreSecFrame web interface" +echo " 3. Try running bettercap manually: bettercap -eval 'help; quit'" +echo +print_warning "Remember: BetterMITM should only be used on networks you own or have permission to test!" + +print_header "Manual Test Commands" +echo "Test Bettercap manually:" +echo " bettercap -version" +echo " bettercap -eval 'help; quit'" +echo " bettercap -eval 'api.rest on; sleep 5; api.rest off; quit'" +echo +echo "Test API manually:" +echo " # Terminal 1:" +echo " bettercap -eval 'api.rest on'" +echo " # Terminal 2:" +echo " curl -X POST http://127.0.0.1:8081/api/session -d '{\"username\":\"admin\",\"password\":\"admin123\"}'" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 1e27133..39c5cb4 100755 --- a/requirements.txt +++ b/requirements.txt @@ -22,4 +22,5 @@ PyPDF2>=3.0.0,<4.0.0 python-docx>=0.8.11,<1.0.0 openpyxl>=3.1.0,<4.0.0 mutagen>=1.47.0,<2.0.0 -exifread>=3.0.0,<4.0.0 \ No newline at end of file +exifread>=3.0.0,<4.0.0 +netifaces>=0.11.0,<1.0.0 \ No newline at end of file diff --git a/setup.sh b/setup.sh index 286ada3..d8a0f52 100755 --- a/setup.sh +++ b/setup.sh @@ -70,6 +70,220 @@ EOF echo "" } +# Function to install Bettercap +install_bettercap() { + print_header "=== Installing Bettercap for BetterMITM ===" + + # Check if bettercap is already installed + if command -v bettercap &> /dev/null; then + print_success "Bettercap is already installed" + bettercap -version 2>/dev/null || print_status "Bettercap version check failed, but binary exists" + return 0 + fi + + print_status "Installing Bettercap..." + + if command -v apt-get &> /dev/null; then + # Debian/Ubuntu/Kali Linux + print_status "Using apt-get to install Bettercap..." + + # Try to install from repositories first (usually available in Kali) + if sudo apt-get update && sudo apt-get install -y bettercap 2>/dev/null; then + print_success "Bettercap installed from repositories" + else + print_status "Repository version not available, installing from binary..." + install_bettercap_binary + fi + + elif command -v yum &> /dev/null; then + # RHEL/CentOS + print_status "RHEL/CentOS detected, installing from binary..." + install_bettercap_binary + + elif command -v dnf &> /dev/null; then + # Fedora + print_status "Fedora detected, installing from binary..." + install_bettercap_binary + + else + print_status "Unknown package manager, installing from binary..." + install_bettercap_binary + fi + + # Verify installation + if command -v bettercap &> /dev/null; then + print_success "Bettercap installed successfully!" + + # Set up capabilities for non-root operation + setup_bettercap_permissions + + # Create default configuration + setup_bettercap_config + + print_status "Bettercap installation completed" + else + print_error "Bettercap installation failed" + print_warning "BetterMITM module may not work properly without Bettercap" + print_warning "You can install it manually later with:" + print_warning " Manual binary: https://github.com/bettercap/bettercap/releases" + print_warning " Or from source: go install github.com/bettercap/bettercap@latest" + fi +} + +# Function to install Bettercap from binary +install_bettercap_binary() { + print_status "Downloading Bettercap binary..." + + # Detect architecture + ARCH=$(uname -m) + case $ARCH in + x86_64|amd64) + BETTERCAP_ARCH="amd64" + ;; + i386|i686) + BETTERCAP_ARCH="386" + ;; + arm64|aarch64) + BETTERCAP_ARCH="arm64" + ;; + armv7l) + BETTERCAP_ARCH="armv7" + ;; + *) + print_error "Unsupported architecture: $ARCH" + return 1 + ;; + esac + + # Get the latest release URL + print_status "Detecting latest Bettercap version..." + LATEST_URL=$(curl -s https://api.github.com/repos/bettercap/bettercap/releases/latest | grep "browser_download_url.*linux_${BETTERCAP_ARCH}.zip" | cut -d '"' -f 4) + + if [ -z "$LATEST_URL" ]; then + print_error "Failed to get Bettercap download URL" + return 1 + fi + + print_status "Downloading from: $LATEST_URL" + + # Create temporary directory + TEMP_DIR=$(mktemp -d) + TEMP_FILE="$TEMP_DIR/bettercap.zip" + + # Download + if curl -L -o "$TEMP_FILE" "$LATEST_URL"; then + print_status "Download completed, extracting..." + + # Extract + cd "$TEMP_DIR" + if unzip -q "$TEMP_FILE"; then + # Find the bettercap binary + BETTERCAP_BIN=$(find "$TEMP_DIR" -name "bettercap" -type f) + + if [ -n "$BETTERCAP_BIN" ] && [ -f "$BETTERCAP_BIN" ]; then + # Install to /usr/local/bin + if sudo cp "$BETTERCAP_BIN" /usr/local/bin/bettercap; then + sudo chmod +x /usr/local/bin/bettercap + print_success "Bettercap binary installed to /usr/local/bin/bettercap" + else + print_error "Failed to copy Bettercap binary" + rm -rf "$TEMP_DIR" + return 1 + fi + else + print_error "Bettercap binary not found in downloaded archive" + rm -rf "$TEMP_DIR" + return 1 + fi + else + print_error "Failed to extract Bettercap archive" + rm -rf "$TEMP_DIR" + return 1 + fi + else + print_error "Failed to download Bettercap" + rm -rf "$TEMP_DIR" + return 1 + fi + + # Cleanup + rm -rf "$TEMP_DIR" + return 0 +} + +# Function to set up Bettercap permissions +setup_bettercap_permissions() { + print_status "Setting up Bettercap permissions..." + + BETTERCAP_PATH=$(which bettercap) + if [ -z "$BETTERCAP_PATH" ]; then + print_error "Bettercap binary not found in PATH" + return 1 + fi + + # Set capabilities for raw socket access + if sudo setcap cap_net_raw,cap_net_admin=eip "$BETTERCAP_PATH" 2>/dev/null; then + print_success "Network capabilities set for Bettercap" + print_status "Bettercap can now run without root privileges for most operations" + else + print_warning "Failed to set network capabilities" + print_warning "You may need to run Bettercap as root for some operations" + print_warning "Alternative: sudo $BETTERCAP_PATH" + fi +} + +# Function to create Bettercap configuration +setup_bettercap_config() { + print_status "Creating Bettercap configuration..." + + # Create bettercap config directory + mkdir -p "$HOME/.bettercap" + + # Create default configuration for API + cat > "$HOME/.bettercap/config.yml" << 'EOF' +# Bettercap Configuration for BetterMITM +api: + rest: + enabled: true + port: 8081 + username: admin + password: admin123 + certificate: "" + key: "" + allow_origin: "*" + +# Network interface (auto-detect by default) +interface: "" + +# Logging +log: + level: info + output: "" + +# Default modules +modules: + net.recon: + enabled: true + net.probe: + enabled: false + arp.spoof: + enabled: false + dns.spoof: + enabled: false + http.proxy: + enabled: false + net.sniff: + enabled: false +EOF + + if [ -f "$HOME/.bettercap/config.yml" ]; then + print_success "Bettercap configuration created at ~/.bettercap/config.yml" + chmod 600 "$HOME/.bettercap/config.yml" + else + print_error "Failed to create Bettercap configuration" + fi +} + # Function to check system requirements # Function to check system requirements check_requirements() { @@ -96,6 +310,9 @@ check_requirements() { # Add dependencies for Oniux missing_deps+=("tor" "iproute2" "cargo") + + # Add dependencies for BetterMITM (Bettercap) + missing_deps+=("wget" "curl" "unzip" "libpcap-dev" "libusb-1.0-0-dev" "libnetfilter-queue-dev") # Check tmux @@ -350,6 +567,9 @@ check_requirements() { print_warning " And add $HOME/.cargo/bin to your PATH in .bashrc/.zshrc" fi + # Install Bettercap for BetterMITM module + install_bettercap + # ========== NUEVA SECCIÓN: INSTALL COMMON GUI APPS ========== print_status "Installing common GUI applications for testing..."