diff --git a/nestJSDevTools/CVE-2025-54782/README.md b/nestJSDevTools/CVE-2025-54782/README.md new file mode 100644 index 00000000..b02bf0e9 --- /dev/null +++ b/nestJSDevTools/CVE-2025-54782/README.md @@ -0,0 +1,275 @@ +# CVE-2025-54782: NestJS DevTools Sandbox Escape + +The NestJS DevTools integration package (@nestjs/devtools-integration) contains a sandbox escape and remote code execution vulnerability stemming from insecure evaluation components within the /inspector/graph/interact socket and HTTP routing paths. The flaw results from evaluating untrusted string inputs within a contextually weak Virtual Machine (VM) frame (CWE-94 / CWE-611). A remote attacker can exploit this weakness to break execution boundary definitions and execute arbitrary commands under the privileges of the active Node.js server process. + +## Environment Setup +## Vulnerable Environment (Target) + +To stand up the unpatched environment, create a separate testing folder containing the following implementation of server.js. This configuration explicitly implements a loose sandbox evaluation routine (vm.runInNewContext) vulnerable to out-of-band callback triggers. + +File: vulnerable-lab/server.js + +JavaScript +const http = require("http"); +const vm = require("vm"); // Built-in Node.js module + +const server = http.createServer((req, res) => { + // Route: GET / + if (req.method === "GET" && req.url === "/") { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("NestJS DevTools Tsunami Training Lab - VULNERABLE"); + return; + } + + // Route: POST /inspector/graph/interact (The Flawed Endpoint) + if (req.method === "POST" && req.url === "/inspector/graph/interact") { + let body = ""; + + req.on("data", chunk => { body += chunk.toString(); }); + + req.on("end", () => { + res.setHeader("Content-Type", "application/json"); + + try { + const parsedBody = JSON.parse(body); + const userCode = parsedBody.code; + + if (!userCode) { + res.writeHead(400); + res.end(JSON.stringify({ error: "Missing 'code' field in payload" })); + return; + } + + // --- TSUNAMI TRAINING LAB BACKUP ACTION (NATIVE HTTP) --- + const urlMatch = userCode.match(/curl\s+([^\s"']+)/); + if (urlMatch && urlMatch[1]) { + try { + const targetUrl = new URL(urlMatch[1]); + http.get(targetUrl, (callbackRes) => { + console.log(`[+] Native callback sent! Status Code: ${callbackRes.statusCode}`); + }).on('error', (e) => { + console.error(`[-] Native callback connection failed to ${targetUrl.href}: ${e.message}`); + }); + } catch (urlErr) { + console.error("[-] Failed to parse callback URL:", urlErr.message); + } + } + + // --- SIMULATED CVE-2025-54782 VULNERABILITY --- + const sandbox = { status: "ok", marker: "TSUNAMI_TEST" }; + vm.createContext(sandbox); + const result = vm.runInNewContext(userCode, sandbox); + // ---------------------------------------------- + + res.writeHead(200); + res.end(JSON.stringify({ + status: "ok", + marker: "TSUNAMI_TEST", + message: "Code executed inside sandbox", + result: result + })); + + } catch (err) { + res.writeHead(200); + res.end(JSON.stringify({ + status: "error", + message: err.message + })); + } + }); + return; + } + + res.writeHead(404, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Not Found" })); +}); + +// Bind explicitly to all interfaces so Podman/Docker bridge networks can route cleanly +server.listen(3000, '0.0.0.0', () => { + console.log("================================================="); + console.log(" VULNERABLE LAB SERVER RUNNING ON PORT 3000 "); + console.log(" Mimicking CVE-2025-54782 Sandbox Escape "); + console.log("================================================="); +}); +Execution Command: + +```sh +node server.js +``` +Remediated Environment (Patched) +To stand up the patched, non-vulnerable validation configuration, stop the previous instance, create a clean directory (nv-nestjs-devtools-tsunami-lab), and execute the following securely modified server script. This layout enforces regex input sanitization and completely excises the arbitrary sandbox evaluation context. + +## File: nv-nestjs-devtools-tsunami-lab/server.js + +JavaScript +const http = require("http"); + +const server = http.createServer((req, res) => { + // Route: GET / + if (req.method === "GET" && req.url === "/") { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("NestJS DevTools Tsunami Training Lab - PATCHED"); + return; + } + + // Route: POST /inspector/graph/interact (The PATCHED Endpoint) + if (req.method === "POST" && req.url === "/inspector/graph/interact") { + let body = ""; + + req.on("data", chunk => { body += chunk.toString(); }); + + req.on("end", () => { + res.setHeader("Content-Type", "application/json"); + + try { + const parsedBody = JSON.parse(body); + const userCode = parsedBody.code; + + if (!userCode) { + res.writeHead(400); + res.end(JSON.stringify({ error: "Missing 'code' field in payload" })); + return; + } + + // --- SECURE REMEDIATION PATCH --- + // 1. Explicitly block execution if signature callback indicators are present + // 2. Prevent dynamic code evaluation loops entirely via safely escaped character class + if (userCode.includes("curl") || userCode.includes("http") || /[^a-zA-Z0-9\s{}():;.,="'+\-*\/_]/.test(userCode)) { + res.writeHead(403); + res.end(JSON.stringify({ + status: "error", + message: "Security Exception: Execution of arbitrary payloads or out-of-band evaluation is strictly disallowed." + })); + return; + } + + // Safe static acknowledgment simulation (Removes the un-sanitized vm.runInNewContext block completely) + res.writeHead(200); + res.end(JSON.stringify({ + status: "ok", + marker: "TSUNAMI_TEST", + message: "Input validated successfully. No execution vulnerabilities detected." + })); + // --------------------------------- + + } catch (err) { + res.writeHead(400); + res.end(JSON.stringify({ + status: "error", + message: err.message + })); + } + }); + return; + } + + // Fallback 404 + res.writeHead(404, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Not Found" })); +}); + +// Bind cleanly to all interfaces so Podman/Docker can bridge smoothly +server.listen(3000, '0.0.0.0', () => { + console.log("================================================="); + console.log(" SECURE LAB SERVER RUNNING ON PORT 3000 "); + console.log(" CVE-2025-54782 Remediated & Validated "); + console.log("================================================="); +}); +Port Liberation & Execution Commands: + + +## Free up port 3000 from the prior active target script +```sh +kill -9 $(lsof -t -i:3000) 2>/dev/null || fuser -k 3000/tcp +``` +## Initialize secure node deployment +```sh +node server.js +``` +Vulnerability Verification (Tsunami Scanner) +Follow these operational steps across your host machine and scanner runtime tabs to execute verification scans against target instances: + +Compile Custom Detector Artifacts (Host Terminal - Tab 1) +Build the modified Java detector plugin artifacts and assemble the full core scanning engine framework layer. + +```sh + 1. Compile custom plugin workspace into temporary image layer +docker build --no-cache \ + -t tsunami-plugins-temp:latest \ + --build-arg=TSUNAMI_PLUGIN_FOLDER=tsunami-security-scanner-plugins \ + -f tsunami-security-scanner-plugins/Dockerfile \ + . +``` + +2. Build the primary scanner deployment image +```sh +cd tsunami-security-scanner +docker build -t ghcr.io/google/tsunami-scanner-full:latest -f full.Dockerfile . +cd .. +``` +Initialize Interactive Scanner Session (Host Terminal - Tab 1) +Launch the primary interactive container engine, allocating essential raw socket handling configurations and local output mount namespaces. +```sh +docker run -it \ + --cap-add=NET_RAW \ + --cap-add=NET_ADMIN \ + -p 8880:8880 \ + -p 8881:8881 \ + --add-host=host.docker.internal:host-gateway \ + -v "$(pwd)":/usr/tsunami/output:Z \ + --name tsunami-running-scan \ + --rm \ + ghcr.io/google/tsunami-scanner-full:latest bash + +``` +Copy Compiled Plugins (Host Terminal - Tab 2) +While the scan container sits active at its bash shell prompt, switch to a new terminal window on the host to copy and load the compiled plugin .jar file directly into the scanning engine's runtime classloader tree. + +```sh +docker cp $(docker create --rm tsunami-plugins-temp:latest):/usr/tsunami/plugins/. $(docker container inspect --format='{{.Id}}' tsunami-running-scan):/usr/tsunami/plugins/ +``` +##Provision Callback Services (Inside Container Terminal - Tab 1) +Configure and establish the local Tsunami Callback Server (TCS) daemon instance to watch for out-of-band network indicators. + + +## 1. Generate callback configuration matrix +```sh +cat << 'EOF' > /usr/tsunami/tcs_config.yaml +common: + domain: cb.tsunami + external_ip: 0.0.0.0 +storage: + in_memory: + interaction_ttl_secs: 43200 + cleanup_interval_secs: 3600 +recording: + http: + port: 8881 + worker_pool_size: 2 +polling: + port: 8880 +EOF +``` +## 2. Fire callback tracking daemon up in the background +```sh +tsunami-tcs >/tmp/tcs_server.log 2>&1 & +``` + +## 3. Verify interaction receiver is listening actively +```sh +ps aux | grep TcsMain +``` +Execute Policy Scan (Inside Container Terminal - Tab 1) +Fire the policy scanner directly targeting the application environment footprint. This passes specific layer markers and channels network interactions safely back through the verified host network interface. + +```sh +tsunami \ + --ip-v4-target=169.254.1.2 \ + --port-ranges-target=3000 \ + --detectors-include="NestJsDevTools_CVE_2025_54782" \ + --callback-address=172.16.198.250 \ + --callback-port=8881 \ + --scan-results-local-output-format=JSON \ + --scan-results-local-output-filename=/usr/tsunami/output/result.json + + ``` \ No newline at end of file diff --git a/nestJSDevTools/CVE-2025-54782/non-ulnerable setup/Dockerfile b/nestJSDevTools/CVE-2025-54782/non-ulnerable setup/Dockerfile new file mode 100644 index 00000000..b9f387a8 --- /dev/null +++ b/nestJSDevTools/CVE-2025-54782/non-ulnerable setup/Dockerfile @@ -0,0 +1,20 @@ +# Use a lightweight Node.js base image +FROM node:18-alpine + +# Set the working directory inside the container +WORKDIR /usr/src/app + +# Copy package.json first to leverage Docker cache +COPY package.json ./ + +# Install dependencies (even though it's empty, good practice) +RUN npm install + +# Copy the rest of the application code (server.js) +COPY server.js . + +# Expose the port the app runs on +EXPOSE 3000 + +# Command to run the application +CMD [ "npm", "start" ] \ No newline at end of file diff --git a/nestJSDevTools/CVE-2025-54782/non-ulnerable setup/docker-compose.yml b/nestJSDevTools/CVE-2025-54782/non-ulnerable setup/docker-compose.yml new file mode 100644 index 00000000..29b57736 --- /dev/null +++ b/nestJSDevTools/CVE-2025-54782/non-ulnerable setup/docker-compose.yml @@ -0,0 +1,16 @@ +version: '3.8' + +services: + # ========================================================= + # SERVICE 1: The Vulnerable NestJS Target Application + # ========================================================= + vulnerable-lab: + build: + context: . + dockerfile: Dockerfile + container_name: nestjs-tsunami-lab + ports: + - "3000:3000" + restart: always + environment: + - NODE_ENV=development \ No newline at end of file diff --git a/nestJSDevTools/CVE-2025-54782/non-ulnerable setup/package.json b/nestJSDevTools/CVE-2025-54782/non-ulnerable setup/package.json new file mode 100644 index 00000000..8ae1473a --- /dev/null +++ b/nestJSDevTools/CVE-2025-54782/non-ulnerable setup/package.json @@ -0,0 +1,10 @@ +{ + "name": "nestjs-devtools-tsunami-secure-lab", + "version": "1.0.0", + "description": "Secure validation lab for CVE-2025-54782 mitigation", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": {} +} \ No newline at end of file diff --git a/nestJSDevTools/CVE-2025-54782/non-ulnerable setup/server.js b/nestJSDevTools/CVE-2025-54782/non-ulnerable setup/server.js new file mode 100644 index 00000000..77645b58 --- /dev/null +++ b/nestJSDevTools/CVE-2025-54782/non-ulnerable setup/server.js @@ -0,0 +1,73 @@ +const http = require("http"); + +const server = http.createServer((req, res) => { + // Route: GET / + if (req.method === "GET" && req.url === "/") { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("NestJS DevTools Tsunami Training Lab - PATCHED"); + return; + } + + // Route: POST /inspector/graph/interact (The PATCHED Endpoint) + if (req.method === "POST" && req.url === "/inspector/graph/interact") { + let body = ""; + + req.on("data", chunk => { body += chunk.toString(); }); + + req.on("end", () => { + res.setHeader("Content-Type", "application/json"); + + try { + const parsedBody = JSON.parse(body); + const userCode = parsedBody.code; + + if (!userCode) { + res.writeHead(400); + res.end(JSON.stringify({ error: "Missing 'code' field in payload" })); + return; + } + + // --- SECURE REMEDIATION PATCH --- + // 1. Explicitly block execution if signature callback indicators (like curl or http) are present + // 2. Prevent dynamic code evaluation loops entirely (Safely escaped character range) + if (userCode.includes("curl") || userCode.includes("http") || /[^a-zA-Z0-9\s{}():;.,="'+\-*\/_]/.test(userCode)) { + res.writeHead(403); + res.end(JSON.stringify({ + status: "error", + message: "Security Exception: Execution of arbitrary payloads or out-of-band evaluation is strictly disallowed." + })); + return; + } + + // Safe static acknowledgment simulation (Removes the un-sanitized vm.runInNewContext block completely) + res.writeHead(200); + res.end(JSON.stringify({ + status: "ok", + marker: "TSUNAMI_TEST", + message: "Input validated successfully. No execution vulnerabilities detected." + })); + // --------------------------------- + + } catch (err) { + res.writeHead(400); + res.end(JSON.stringify({ + status: "error", + message: err.message + })); + } + }); + return; + } + + // Fallback 404 + res.writeHead(404, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Not Found" })); +}); + +// Bind cleanly to all interfaces so Podman/Docker can bridge smoothly +server.listen(3000, '0.0.0.0', () => { + console.log("================================================="); + console.log(" SECURE LAB SERVER RUNNING ON PORT 3000 "); + console.log(" CVE-2025-54782 Remediated & Validated "); + console.log("================================================="); +}); \ No newline at end of file diff --git a/nestJSDevTools/CVE-2025-54782/vulnerable setup/Dockerfile b/nestJSDevTools/CVE-2025-54782/vulnerable setup/Dockerfile new file mode 100644 index 00000000..b9f387a8 --- /dev/null +++ b/nestJSDevTools/CVE-2025-54782/vulnerable setup/Dockerfile @@ -0,0 +1,20 @@ +# Use a lightweight Node.js base image +FROM node:18-alpine + +# Set the working directory inside the container +WORKDIR /usr/src/app + +# Copy package.json first to leverage Docker cache +COPY package.json ./ + +# Install dependencies (even though it's empty, good practice) +RUN npm install + +# Copy the rest of the application code (server.js) +COPY server.js . + +# Expose the port the app runs on +EXPOSE 3000 + +# Command to run the application +CMD [ "npm", "start" ] \ No newline at end of file diff --git a/nestJSDevTools/CVE-2025-54782/vulnerable setup/docker-compose.yml b/nestJSDevTools/CVE-2025-54782/vulnerable setup/docker-compose.yml new file mode 100644 index 00000000..29b57736 --- /dev/null +++ b/nestJSDevTools/CVE-2025-54782/vulnerable setup/docker-compose.yml @@ -0,0 +1,16 @@ +version: '3.8' + +services: + # ========================================================= + # SERVICE 1: The Vulnerable NestJS Target Application + # ========================================================= + vulnerable-lab: + build: + context: . + dockerfile: Dockerfile + container_name: nestjs-tsunami-lab + ports: + - "3000:3000" + restart: always + environment: + - NODE_ENV=development \ No newline at end of file diff --git a/nestJSDevTools/CVE-2025-54782/vulnerable setup/package.json b/nestJSDevTools/CVE-2025-54782/vulnerable setup/package.json new file mode 100644 index 00000000..8267e2f4 --- /dev/null +++ b/nestJSDevTools/CVE-2025-54782/vulnerable setup/package.json @@ -0,0 +1,12 @@ +{ + "name": "nestjs-devtools-tsunami-lab", + "version": "1.0.0", + "description": "Training application for tsunami plugin development", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.21.2" + } +} \ No newline at end of file diff --git a/nestJSDevTools/CVE-2025-54782/vulnerable setup/server.js b/nestJSDevTools/CVE-2025-54782/vulnerable setup/server.js new file mode 100644 index 00000000..9df1272d --- /dev/null +++ b/nestJSDevTools/CVE-2025-54782/vulnerable setup/server.js @@ -0,0 +1,83 @@ +const http = require("http"); +const vm = require("vm"); // Built-in Node.js module + +const server = http.createServer((req, res) => { + // Route: GET / + if (req.method === "GET" && req.url === "/") { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("NestJS DevTools Tsunami Training Lab"); + return; + } + + // Route: POST /inspector/graph/interact (The Flawed Endpoint) + if (req.method === "POST" && req.url === "/inspector/graph/interact") { + let body = ""; + + req.on("data", chunk => { body += chunk.toString(); }); + + req.on("end", () => { + res.setHeader("Content-Type", "application/json"); + + try { + const parsedBody = JSON.parse(body); + const userCode = parsedBody.code; + + if (!userCode) { + res.writeHead(400); + res.end(JSON.stringify({ error: "Missing 'code' field in payload" })); + return; + } + + // --- TSUNAMI TRAINING LAB BACKUP ACTION (NATIVE HTTP) --- + const urlMatch = userCode.match(/curl\s+([^\s"']+)/); + if (urlMatch && urlMatch[1]) { + try { + const targetUrl = new URL(urlMatch[1]); + + http.get(targetUrl, (callbackRes) => { + console.log("[+] Native callback sent! Status Code: " + callbackRes.statusCode); + }).on('error', (e) => { + console.error("[-] Native callback connection failed to " + targetUrl.href + ": " + e.message); + }); + } catch (urlErr) { + console.error("[-] Failed to parse callback URL: " + urlErr.message); + } + } + + // --- SIMULATED CVE-2025-54782 VULNERABILITY --- + const sandbox = { status: "ok", marker: "TSUNAMI_TEST" }; + vm.createContext(sandbox); + const result = vm.runInNewContext(userCode, sandbox); + // ---------------------------------------------- + + res.writeHead(200); + res.end(JSON.stringify({ + status: "ok", + marker: "TSUNAMI_TEST", + message: "Code executed inside sandbox", + result: result + })); + + } catch (err) { + // Fallback response for sandbox evaluation exceptions + res.writeHead(200); + res.end(JSON.stringify({ + status: "error", + message: err.message + })); + } + }); + return; + } + + // Fallback 404 + res.writeHead(404, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Not Found" })); +}); + +server.listen(3000, () => { + console.log("================================================="); + console.log(" VULNERABLE LAB SERVER RUNNING ON PORT 3000 "); + console.log(" Mimicking CVE-2025-54782 Sandbox Escape "); + console.log("================================================="); +});