From 16b3c64dbaee7112f3783d6ae07e18d484b034f2 Mon Sep 17 00:00:00 2001 From: Vinh Hoa La Date: Fri, 29 May 2026 17:40:30 +0200 Subject: [PATCH 1/3] feat: integrate Montimage Attack Generator (MAG) Add mmt-attacker CLI (pip install mmt-attacker) as 7th tool with 14 attack variants across 5 categories: - Network layer: SYN/UDP/ICMP flood, Ping of Death, ARP spoof, DHCP starvation - Application layer: HTTP DoS, HTTP flood, Slowloris - Credentials: SSH and FTP brute force - Amplification: DNS (50-100x) and NTP (500x) - Protocol/replay: PCAP replay Adds tools/mag/setup.sh (auto-installs mag if missing) and tools/mag/run_mag.sh (thin forwarding wrapper). CategorySelector updated to reflect 7 tools / 3 frameworks. Co-Authored-By: Claude Sonnet 4.6 --- .../features/AttackPanel/CategorySelector.tsx | 4 +- frontend/src/constants/ai4cyberTools.ts | 52 ++++ frontend/src/constants/toolMapping.ts | 1 + frontend/src/constants/tools.ts | 231 ++++++++++++++++++ tools/mag/run_mag.sh | 18 ++ tools/mag/setup.sh | 37 +++ 6 files changed, 341 insertions(+), 2 deletions(-) create mode 100755 tools/mag/run_mag.sh create mode 100755 tools/mag/setup.sh diff --git a/frontend/src/components/features/AttackPanel/CategorySelector.tsx b/frontend/src/components/features/AttackPanel/CategorySelector.tsx index 37e977d1a..e307d3897 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 a96131419..8708bf2c7 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 cf580c108..b216bebad 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 e2e79c7ab..a0544cb14 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 000000000..c1e6487f8 --- /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 000000000..f3cd4ec3e --- /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 From 4dd52d4f6c24062cecc235586e32a106b63bf98f Mon Sep 17 00:00:00 2001 From: Vinh Hoa La Date: Fri, 19 Jun 2026 21:56:43 +0200 Subject: [PATCH 2/3] fix: enable scenario attack execution and correct scenario status Three fixes required to make Scenario-based attack execution work end-to-end (verified live on mmt-pentester.montimage.eu): - ProcessManager: allow nmap/hydra/dirb in the command allowlist so recon and the attack labs can run. - ProcessManager: for network CLI tools (nmap, hydra, dirb, masscan, netcat, gobuster, sqlmap, nikto) use the command built by buildAttackCommand as-is instead of appending every parameter as --key=value (which produced invalid flags and made the tools exit immediately). - ScenarioService.finalizeScenario: keep the scenario RUNNING while any attack is still running, instead of defaulting to FAILED. checkScenarioCompletion then finalizes the real status on process exit. Previously every scenario showed "failed" even when its attack completed. Co-Authored-By: Claude Fable 5 --- backend/src/services/ProcessManager.ts | 6 +++--- backend/src/services/ScenarioService.ts | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/src/services/ProcessManager.ts b/backend/src/services/ProcessManager.ts index ce4a68919..4e1d75b21 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 a866058e3..fd5f5c224 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 From c1ba8e47dcdc721e0cee1477c4edcbe14b3f9713 Mon Sep 17 00:00:00 2001 From: Vinh Hoa La Date: Thu, 2 Jul 2026 15:25:06 +0200 Subject: [PATCH 3/3] fix(frontend): add .env.production so builds target the hosted origin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vite loads .env in every mode, so the committed dev values (VITE_API_URL=http://localhost:3000, VITE_WS_URL=ws://localhost:3000) were baked into production builds — the hosted UI then failed every API/WebSocket call from the browser (ERR_CONNECTION_REFUSED to localhost:3000), so data views (projects, scenarios, reports) and live output never loaded. Add frontend/.env.production pointing at https://mmt-pentester.montimage.eu (wss for WebSocket). `npm run build` now targets the hosted origin while `npm run dev` keeps using .env (localhost). Co-Authored-By: Claude Fable 5 --- frontend/.env.production | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 frontend/.env.production diff --git a/frontend/.env.production b/frontend/.env.production new file mode 100644 index 000000000..3be19e14e --- /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