diff --git a/backend/src/services/ProcessManager.ts b/backend/src/services/ProcessManager.ts index ce4a6891..4e1d75b2 100644 --- a/backend/src/services/ProcessManager.ts +++ b/backend/src/services/ProcessManager.ts @@ -30,7 +30,7 @@ interface PublicProcessInfo { export class ProcessManager { private static instance: ProcessManager; - private allowedCommands = new Set(["caldera", "start_maip.sh", "start_maip_iframe.sh", "docker", "python3", "bash", "sh", "./start-client.sh", "cmd"]); + private allowedCommands = new Set(["caldera", "start_maip.sh", "start_maip_iframe.sh", "docker", "python3", "bash", "sh", "./start-client.sh", "cmd", "nmap", "hydra", "dirb"]); private processes: Map = new Map(); private isWindows: boolean; private terminalManager: TerminalManager; @@ -394,8 +394,8 @@ export class ProcessManager { if (useShell) { // Pour les commandes shell (comme cd), utiliser les arguments tels quels allArgs = args; - } else if (program === 'docker') { - // Pour Docker, ne pas ajouter les paramètres automatiquement + } else if (program === 'docker' || ['nmap','hydra','dirb','masscan','netcat','nc','gobuster','sqlmap','nikto'].includes(program)) { + // Docker + network CLI tools: buildAttackCommand already produced the full command; do NOT append params allArgs = args; } else if (command.includes('shennina_standalone.py')) { // Pour Shennina, vérifier si la commande contient déjà tous les paramètres diff --git a/backend/src/services/ScenarioService.ts b/backend/src/services/ScenarioService.ts index a866058e..fd5f5c22 100644 --- a/backend/src/services/ScenarioService.ts +++ b/backend/src/services/ScenarioService.ts @@ -790,8 +790,12 @@ export class ScenarioService { // Determine final status based on attack results const hasFailedAttacks = scenario.attacks.some(attack => attack.status === AttackStatus.FAILED); const hasCompletedAttacks = scenario.attacks.some(attack => attack.status === AttackStatus.COMPLETED); + const allAttacksTerminal = scenario.attacks.every(attack => attack.status === AttackStatus.COMPLETED || attack.status === AttackStatus.FAILED); - if (hasCompletedAttacks && !hasFailedAttacks) { + if (!allAttacksTerminal) { + // Attacks still running asynchronously; keep RUNNING so checkScenarioCompletion finalizes on process exit + scenario.status = AttackStatus.RUNNING; + } else if (hasCompletedAttacks && !hasFailedAttacks) { scenario.status = AttackStatus.COMPLETED; } else if (hasCompletedAttacks && hasFailedAttacks) { scenario.status = AttackStatus.FAILED; // Partial success treated as failed diff --git a/frontend/.env.production b/frontend/.env.production new file mode 100644 index 00000000..3be19e14 --- /dev/null +++ b/frontend/.env.production @@ -0,0 +1,6 @@ +# Production build config (used by `npm run build`; dev uses .env). +# Must point at the hosted origin — Vite loads .env in all modes, so without +# this override the dev localhost:3000 values leak into the production build +# and the browser cannot reach the API/WebSocket. +VITE_API_URL=https://mmt-pentester.montimage.eu +VITE_WS_URL=wss://mmt-pentester.montimage.eu diff --git a/frontend/src/components/features/AttackPanel/CategorySelector.tsx b/frontend/src/components/features/AttackPanel/CategorySelector.tsx index 37e977d1..e307d389 100644 --- a/frontend/src/components/features/AttackPanel/CategorySelector.tsx +++ b/frontend/src/components/features/AttackPanel/CategorySelector.tsx @@ -8,7 +8,7 @@ const CATEGORIES = [ name: 'All Tools', description: 'View all available tools', Icon: WrenchIcon, - count: 6 + count: 7 }, { id: 'FUZZING', @@ -29,7 +29,7 @@ const CATEGORIES = [ name: 'Frameworks', description: 'Complete attack frameworks', Icon: CommandLineIcon, - count: 2 + count: 3 }, { id: 'Other', diff --git a/frontend/src/constants/ai4cyberTools.ts b/frontend/src/constants/ai4cyberTools.ts index a9613141..8708bf2c 100644 --- a/frontend/src/constants/ai4cyberTools.ts +++ b/frontend/src/constants/ai4cyberTools.ts @@ -112,6 +112,58 @@ export const AI4CYBER_TOOLS: AI4CyberTool[] = [ tags: ['fuzzing', 'ai', 'knx', 'iot'] }, + // MAG — Montimage Attack Generator + { + id: 'mag', + name: 'mag', + displayName: 'MAG (Montimage Attack Generator)', + description: '26 network, application-layer, credential and amplification attacks via the mmt-attacker CLI (pip install mmt-attacker)', + category: 'framework', + status: 'implemented', + command: (params) => { + const attack = params['attack'] || 'syn-flood'; + const targetIp = params['target-ip'] || '192.168.1.1'; + const targetPort = params['target-port'] ? ` --target-port ${params['target-port']}` : ''; + const extra = params['extra-args'] ? ` ${params['extra-args']}` : ''; + return `bash tools/mag/run_mag.sh ${attack} --target-ip ${targetIp}${targetPort}${extra}`; + }, + parameters: { + 'attack': { + label: 'Attack Type', + type: 'select', + options: [ + 'syn-flood', 'udp-flood', 'icmp-flood', 'ping-of-death', + 'arp-spoof', 'dhcp-starvation', + 'http-dos', 'http-flood', 'slowloris', + 'ssh-brute-force', 'ftp-brute-force', + 'dns-amplification', 'ntp-amplification', + 'pcap-replay' + ], + default: 'syn-flood', + required: true, + description: 'MAG attack to execute' + }, + 'target-ip': { + label: 'Target IP', + type: 'target', + required: true, + description: 'Target from scenario targets' + }, + 'target-port': { + label: 'Target Port', + type: 'number', + description: 'Target port (attack-dependent)' + }, + 'extra-args': { + label: 'Extra Arguments', + type: 'string', + description: 'Additional CLI flags (e.g. --count 500 --threads 10)' + } + }, + estimatedDuration: '1-10 minutes', + tags: ['framework', 'network', 'dos', 'brute-force', 'amplification', 'mitm', 'montimage'] + }, + // FRAMEWORK { id: 'sheninna', diff --git a/frontend/src/constants/toolMapping.ts b/frontend/src/constants/toolMapping.ts index cf580c10..b216beba 100644 --- a/frontend/src/constants/toolMapping.ts +++ b/frontend/src/constants/toolMapping.ts @@ -70,6 +70,7 @@ export function getAttackDisplayNameFromParams( // Short display names for known attacks/tools const shortNames: Record = { shennina: 'Shennina Scan', + mag: 'MAG Attack', gan_fuzzer: 'GAN Fuzzing', nmap: 'Nmap Scan', sqlmap: 'SQL Injection Test', diff --git a/frontend/src/constants/tools.ts b/frontend/src/constants/tools.ts index e2e79c7a..a0544cb1 100644 --- a/frontend/src/constants/tools.ts +++ b/frontend/src/constants/tools.ts @@ -500,6 +500,237 @@ export const TOOLS: Tool[] = [ ] } }, + { + id: "mag", + name: "MAG (Montimage Attack Generator)", + description: "26 network, application-layer, credential and amplification attacks via the mmt-attacker CLI", + type: "FRAMEWORK", + status: "implemented", + attacks: [ + // ── Setup ──────────────────────────────────────────────────────────── + { + id: "mag-setup", + name: "Setup / List Attacks", + description: "Install mmt-attacker (if needed) and list all 26 available attack types", + command: (_paramValues: Record) => + "bash tools/mag/setup.sh", + parameters: {} + }, + // ── Network Layer ───────────────────────────────────────────────────── + { + id: "mag-syn-flood", + name: "SYN Flood", + description: "TCP SYN flood — exhausts server connection tables to cause denial of service", + command: (paramValues: Record) => { + const ip = paramValues["target-ip"] || "192.168.1.1"; + const port = paramValues["target-port"] || "80"; + const count = paramValues["count"] || "500"; + return `bash tools/mag/run_mag.sh syn-flood --target-ip ${ip} --target-port ${port} --count ${count}`; + }, + parameters: { + "target-ip": { label: "Target IP", type: "target", default: "192.168.1.1", required: true, description: "IP of the target host" }, + "target-port": { label: "Target Port", type: "number", default: "80", required: false, description: "TCP port (default: 80)" }, + "count": { label: "Packet Count",type: "number", default: "500", required: false, description: "Number of SYN packets to send" } + } + }, + { + id: "mag-udp-flood", + name: "UDP Flood", + description: "High-volume UDP flood targeting a specific port", + command: (paramValues: Record) => { + const ip = paramValues["target-ip"] || "192.168.1.1"; + const port = paramValues["target-port"] || "53"; + const count = paramValues["count"] || "500"; + return `bash tools/mag/run_mag.sh udp-flood --target-ip ${ip} --target-port ${port} --count ${count}`; + }, + parameters: { + "target-ip": { label: "Target IP", type: "target", default: "192.168.1.1", required: true, description: "IP of the target host" }, + "target-port": { label: "Target Port", type: "number", default: "53", required: false, description: "UDP port (default: 53)" }, + "count": { label: "Packet Count",type: "number", default: "500", required: false, description: "Number of UDP packets to send" } + } + }, + { + id: "mag-icmp-flood", + name: "ICMP Flood", + description: "ICMP echo-request flood (Ping flood) to saturate bandwidth", + command: (paramValues: Record) => { + const ip = paramValues["target-ip"] || "192.168.1.1"; + const count = paramValues["count"] || "500"; + return `bash tools/mag/run_mag.sh icmp-flood --target-ip ${ip} --count ${count}`; + }, + parameters: { + "target-ip": { label: "Target IP", type: "target", default: "192.168.1.1", required: true, description: "IP of the target host" }, + "count": { label: "Packet Count",type: "number", default: "500", required: false, description: "Number of ICMP packets to send" } + } + }, + { + id: "mag-ping-of-death", + name: "Ping of Death", + description: "Oversized ICMP packet attack that can crash or freeze vulnerable hosts", + command: (paramValues: Record) => { + const ip = paramValues["target-ip"] || "192.168.1.1"; + const count = paramValues["count"] || "10"; + return `bash tools/mag/run_mag.sh ping-of-death --target-ip ${ip} --count ${count}`; + }, + parameters: { + "target-ip": { label: "Target IP", type: "target", default: "192.168.1.1", required: true, description: "IP of the target host" }, + "count": { label: "Packet Count",type: "number", default: "10", required: false, description: "Number of oversized ICMP packets" } + } + }, + { + id: "mag-arp-spoof", + name: "ARP Spoofing (MITM)", + description: "Poisons ARP caches to redirect traffic through the attacker (man-in-the-middle)", + command: (paramValues: Record) => { + const ip = paramValues["target-ip"] || "192.168.1.100"; + const gw = paramValues["gateway-ip"] || "192.168.1.1"; + return `bash tools/mag/run_mag.sh arp-spoof --target-ip ${ip} --gateway-ip ${gw}`; + }, + parameters: { + "target-ip": { label: "Target IP", type: "target", default: "192.168.1.100", required: true, description: "Victim IP to ARP-poison" }, + "gateway-ip": { label: "Gateway IP", type: "string", default: "192.168.1.1", required: true, description: "Default gateway IP" } + } + }, + { + id: "mag-dhcp-starvation", + name: "DHCP Starvation", + description: "Exhausts the DHCP pool with forged DISCOVER requests so new clients cannot get an IP", + command: (paramValues: Record) => { + const iface = paramValues["interface"] || "eth0"; + const count = paramValues["count"] || "200"; + return `bash tools/mag/run_mag.sh dhcp-starvation --interface ${iface} --count ${count}`; + }, + parameters: { + "interface": { label: "Network Interface", type: "string", default: "eth0", required: true, description: "Network interface to send forged DHCP requests on" }, + "count": { label: "Request Count", type: "number", default: "200", required: false, description: "Number of fake DHCP DISCOVER packets" } + } + }, + // ── Application Layer ───────────────────────────────────────────────── + { + id: "mag-http-dos", + name: "HTTP DoS", + description: "Multi-threaded HTTP request flood targeting a web endpoint", + command: (paramValues: Record) => { + const url = paramValues["target-url"] || "http://192.168.1.1"; + const threads = paramValues["threads"] || "10"; + return `bash tools/mag/run_mag.sh http-dos --target-url ${url} --threads ${threads}`; + }, + parameters: { + "target-url": { label: "Target URL", type: "string", default: "http://192.168.1.1", required: true, description: "Full URL of the target endpoint" }, + "threads": { label: "Threads", type: "number", default: "10", required: false, description: "Number of concurrent threads" } + } + }, + { + id: "mag-http-flood", + name: "HTTP Flood", + description: "Layer-7 HTTP flood sending a large number of GET/POST requests", + command: (paramValues: Record) => { + const url = paramValues["target-url"] || "http://192.168.1.1"; + const threads = paramValues["threads"] || "10"; + const count = paramValues["count"] || "1000"; + return `bash tools/mag/run_mag.sh http-flood --target-url ${url} --threads ${threads} --count ${count}`; + }, + parameters: { + "target-url": { label: "Target URL", type: "string", default: "http://192.168.1.1", required: true, description: "Full URL of the target endpoint" }, + "threads": { label: "Threads", type: "number", default: "10", required: false, description: "Number of concurrent threads" }, + "count": { label: "Request Count",type: "number", default: "1000", required: false, description: "Total number of HTTP requests" } + } + }, + { + id: "mag-slowloris", + name: "Slowloris", + description: "Keeps many HTTP connections open using partial requests, exhausting the web server's thread pool", + command: (paramValues: Record) => { + const ip = paramValues["target-ip"] || "192.168.1.1"; + const port = paramValues["target-port"] || "80"; + return `bash tools/mag/run_mag.sh slowloris --target-ip ${ip} --target-port ${port}`; + }, + parameters: { + "target-ip": { label: "Target IP", type: "target", default: "192.168.1.1", required: true, description: "IP of the web server" }, + "target-port": { label: "Target Port", type: "number", default: "80", required: false, description: "HTTP port (80 or 443)" } + } + }, + // ── Credential Attacks ──────────────────────────────────────────────── + { + id: "mag-ssh-brute-force", + name: "SSH Brute Force", + description: "Dictionary-based brute-force against an SSH service", + command: (paramValues: Record) => { + const ip = paramValues["target-ip"] || "192.168.1.1"; + const port = paramValues["target-port"] || "22"; + const username = paramValues["username"] || "root"; + return `bash tools/mag/run_mag.sh ssh-brute-force --target-ip ${ip} --target-port ${port} --username ${username}`; + }, + parameters: { + "target-ip": { label: "Target IP", type: "target", default: "192.168.1.1", required: true, description: "IP of the SSH server" }, + "target-port": { label: "SSH Port", type: "number", default: "22", required: false, description: "SSH port (default: 22)" }, + "username": { label: "Username", type: "string", default: "root", required: false, description: "Username to brute-force" } + } + }, + { + id: "mag-ftp-brute-force", + name: "FTP Brute Force", + description: "Dictionary-based brute-force against an FTP service", + command: (paramValues: Record) => { + const ip = paramValues["target-ip"] || "192.168.1.1"; + const port = paramValues["target-port"] || "21"; + const username = paramValues["username"] || "anonymous"; + return `bash tools/mag/run_mag.sh ftp-brute-force --target-ip ${ip} --target-port ${port} --username ${username}`; + }, + parameters: { + "target-ip": { label: "Target IP", type: "target", default: "192.168.1.1", required: true, description: "IP of the FTP server" }, + "target-port": { label: "FTP Port", type: "number", default: "21", required: false, description: "FTP port (default: 21)" }, + "username": { label: "Username", type: "string", default: "anonymous", required: false, description: "Username to brute-force" } + } + }, + // ── Amplification Attacks ───────────────────────────────────────────── + { + id: "mag-dns-amplification", + name: "DNS Amplification", + description: "Exploits open DNS resolvers to amplify traffic 50-100× toward the target", + command: (paramValues: Record) => { + const ip = paramValues["target-ip"] || "192.168.1.1"; + return `bash tools/mag/run_mag.sh dns-amplification --target-ip ${ip}`; + }, + parameters: { + "target-ip": { label: "Target IP", type: "target", default: "192.168.1.1", required: true, description: "Spoofed victim IP — amplified responses will be sent here" } + } + }, + { + id: "mag-ntp-amplification", + name: "NTP Amplification", + description: "Exploits NTP monlist to amplify traffic up to 500× toward the target", + command: (paramValues: Record) => { + const ip = paramValues["target-ip"] || "192.168.1.1"; + return `bash tools/mag/run_mag.sh ntp-amplification --target-ip ${ip}`; + }, + parameters: { + "target-ip": { label: "Target IP", type: "target", default: "192.168.1.1", required: true, description: "Spoofed victim IP — amplified NTP responses will be sent here" } + } + }, + // ── Protocol / Replay ───────────────────────────────────────────────── + { + id: "mag-pcap-replay", + name: "PCAP Replay", + description: "Reproduce previously captured network traffic at configurable speed", + command: (paramValues: Record) => { + const file = paramValues["pcap-file"] || "capture.pcap"; + const iface = paramValues["interface"] || "eth0"; + const speed = paramValues["speed"] || "1.0"; + return `bash tools/mag/run_mag.sh pcap-replay --pcap-file ${file} --interface ${iface} --speed ${speed}`; + }, + parameters: { + "pcap-file": { label: "PCAP File", type: "string", default: "capture.pcap", required: true, description: "Path to the .pcap file to replay" }, + "interface": { label: "Interface", type: "string", default: "eth0", required: true, description: "Network interface to replay on" }, + "speed": { label: "Playback Speed", type: "string", default: "1.0", required: false, description: "Speed multiplier (e.g. 2.0 = twice original speed)" } + } + } + ], + command: function(paramValues: Record) { + return this.attacks[0].command(paramValues); + }, + parameters: {} + }, { id: "caldera", name: "Caldera", diff --git a/tools/mag/run_mag.sh b/tools/mag/run_mag.sh new file mode 100755 index 00000000..c1e6487f --- /dev/null +++ b/tools/mag/run_mag.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# MAG (Montimage Attack Generator) — generic runner wrapper +# Usage: run_mag.sh [mag-options...] +# +# Verifies that the 'mag' CLI is available, then forwards all arguments. +# Raw-socket attacks (flooding, spoofing, amplification) require root/sudo. + +set -euo pipefail + +if ! command -v mag &>/dev/null; then + echo "[MAG ERROR] 'mag' command not found." + echo "Install with: pip3 install mmt-attacker" + echo "Then re-run: bash tools/mag/setup.sh" + exit 1 +fi + +# Forward all arguments to mag +exec mag "$@" diff --git a/tools/mag/setup.sh b/tools/mag/setup.sh new file mode 100755 index 00000000..f3cd4ec3 --- /dev/null +++ b/tools/mag/setup.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# MAG (Montimage Attack Generator) — setup & installation checker +# Installs mmt-attacker via pip if the `mag` CLI is not already on PATH. + +set -euo pipefail + +echo "=== Montimage Attack Generator (MAG) ===" +echo "" + +# Try to locate the mag binary +if command -v mag &>/dev/null; then + MAG_VERSION=$(mag --version 2>/dev/null || echo "unknown") + echo "[OK] mag is already installed: $MAG_VERSION" +else + echo "[INFO] mag not found — installing mmt-attacker from PyPI..." + if command -v pip3 &>/dev/null; then + pip3 install mmt-attacker + elif command -v pip &>/dev/null; then + pip install mmt-attacker + else + echo "[ERROR] pip/pip3 not found. Please install Python 3 and pip first." + echo " Then run: pip3 install mmt-attacker" + echo " Docs: https://mag.montimage.eu/" + exit 1 + fi + + if ! command -v mag &>/dev/null; then + echo "[ERROR] Installation succeeded but 'mag' is not on PATH." + echo " Try: export PATH=\$PATH:\$(python3 -m site --user-base)/bin" + exit 1 + fi + echo "[OK] mmt-attacker installed successfully." +fi + +echo "" +echo "=== Available attacks ===" +mag list