GHSA-VC8F-X9PP-WF5P

Vulnerability from github – Published: 2026-03-27 17:58 – Updated: 2026-03-30 20:15
VLAI?
Summary
Locutus Prototype Pollution due to incomplete fix for CVE-2026-25521
Details

Summary

A prototype pollution vulnerability exists in the parse_str function of the npm package locutus. An attacker can pollute Object.prototype by overriding RegExp.prototype.test and then passing a crafted query string to parse_str, bypassing the prototype pollution guard.

This vulnerability stems from an incomplete fix for CVE-2026-25521. The CVE-2026-25521 patch replaced the String.prototype.includes()-based guard with a RegExp.prototype.test()-based guard. However, RegExp.prototype.test is itself a writable prototype method that can be overridden, making the new guard bypassable in the same way as the original — trading one hijackable built-in for another.

Package

locutus (npm)

Affected versions

= 2.0.39, <= 3.0.24

Tested and confirmed vulnerable on 2.0.39 and 3.0.24 (latest). Version 2.0.38 (pre-fix) uses a different guard (String.prototype.includes) and is not affected by this specific bypass.


Description

Details

The vulnerability resides in parse_str.js where the RegExp.prototype.test() function is used to check whether user-provided input contains forbidden keys:

if (/__proto__|constructor|prototype/.test(key)) {
  break
}

The previous guard (fixed in CVE-2026-25521) used String.prototype.includes():

if (key.includes('__proto__')) {
  break
}

The CVE-2026-25521 fix correctly identified that String.prototype.includes can be hijacked. However, the replacement guard using RegExp.prototype.test() suffers from the same class of weakness — RegExp.prototype.test is a writable method on the prototype chain and can be overridden to always return false, completely disabling the guard.

The robust fix is to use direct string comparison operators (===) in native control flow (for/if) instead of prototype methods like RegExp.prototype.test(), since === is a language-level operator that cannot be overridden.

PoC

Steps to reproduce

  1. Install locutus using npm install locutus
  2. Run the following code snippet:
const parse_str = require('locutus/php/strings/parse_str');

// Hijack RegExp.prototype.test (simulates a prior prototype pollution gadget)
const original = RegExp.prototype.test;
RegExp.prototype.test = function () { return false; };

// Payload
const result = {};
parse_str('__proto__[polluted]=yes', result);

// Check
RegExp.prototype.test = original;
console.log(({}).polluted); // 'yes' — prototype is polluted

Expected behavior

Prototype pollution should be prevented and ({}).polluted should print undefined.

undefined

Actual behavior

Object.prototype is polluted. This is printed on the console:

yes

Impact

This is a prototype pollution vulnerability with the same impact as CVE-2026-25521. The attack requires a chaining scenario — an attacker needs a separate prototype pollution gadget (e.g., from another npm package in the same application) to override RegExp.prototype.test before exploiting parse_str. This is realistic in Node.js applications that use multiple npm packages, where one package's vulnerability can disable another package's defenses.

Any application that processes attacker-controlled input using locutus/php/strings/parse_str may be affected. It could potentially lead to:

  1. Authentication bypass
  2. Denial of service
  3. Remote code execution (if polluted property is passed to sinks like eval or child_process)

Resources

  • Original advisory: https://github.com/locutusjs/locutus/security/advisories/GHSA-rxrv-835q-v5mh
  • Fix commit (incomplete): https://github.com/locutusjs/locutus/commit/042af9ca7fde2ff599120783e720a17f335bb01c
  • Vulnerable file: https://github.com/locutusjs/locutus/blob/main/src/php/strings/parse_str.js#L77

Maintainer response

Thank you for the follow-up report. This issue was reproduced locally against locutus@3.0.24, confirming that the earlier parse_str guard was incomplete: if RegExp.prototype.test was already compromised, the guard could be bypassed and parse_str('__proto__[polluted]=yes', result) could still pollute Object.prototype.

This is now fixed on main and released in locutus@3.0.25.

Fix Shipped In

What the Fix Does

The new fix no longer relies on a regex-prototype guard for safety. Instead, src/php/strings/parse_str.ts now rejects dangerous key paths during parsed-segment assignment, so the sink itself is hardened even if RegExp.prototype.test has been tampered with beforehand.

Tested Repro Before the Fix

  • Override RegExp.prototype.test to always return false
  • Call parse_str('__proto__[polluted]=yes', result)
  • Observe ({}).polluted === 'yes'

Tested State After the Fix in 3.0.25

  • Dangerous key paths are skipped during assignment
  • The same chained repro no longer pollutes Object.prototype
  • The regression is covered by test/custom/parse_str-prototype-pollution.vitest.ts

The locutus team is treating this as a real package vulnerability with patched version 3.0.25. The vulnerable range should end at < 3.0.25.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "locutus"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.0.39"
            },
            {
              "fixed": "3.0.25"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-33994"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1321"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-27T17:58:48Z",
    "nvd_published_at": "2026-03-27T23:17:14Z",
    "severity": "MODERATE"
  },
  "details": "## Summary\n\nA prototype pollution vulnerability exists in the `parse_str` function of the npm package locutus. An attacker can pollute `Object.prototype` by overriding `RegExp.prototype.test` and then passing a crafted query string to `parse_str`, bypassing the prototype pollution guard.\n\nThis vulnerability stems from an incomplete fix for [CVE-2026-25521](https://github.com/locutusjs/locutus/security/advisories/GHSA-rxrv-835q-v5mh). The CVE-2026-25521 patch replaced the `String.prototype.includes()`-based guard with a `RegExp.prototype.test()`-based guard. However, `RegExp.prototype.test` is itself a writable prototype method that can be overridden, making the new guard bypassable in the same way as the original \u2014 trading one hijackable built-in for another.\n\n## Package\n\nlocutus (npm)\n\n## Affected versions\n\n\u003e= 2.0.39, \u003c= 3.0.24\n\nTested and confirmed vulnerable on **2.0.39** and **3.0.24** (latest). Version 2.0.38 (pre-fix) uses a different guard (`String.prototype.includes`) and is not affected by this specific bypass.\n\n---\n\n## Description\n\n### Details\n\nThe vulnerability resides in `parse_str.js` where the `RegExp.prototype.test()` function is used to check whether user-provided input contains forbidden keys:\n\n```javascript\nif (/__proto__|constructor|prototype/.test(key)) {\n  break\n}\n```\n\nThe previous guard (fixed in CVE-2026-25521) used `String.prototype.includes()`:\n\n```javascript\nif (key.includes(\u0027__proto__\u0027)) {\n  break\n}\n```\n\nThe CVE-2026-25521 fix correctly identified that `String.prototype.includes` can be hijacked. However, the replacement guard using `RegExp.prototype.test()` suffers from the same class of weakness \u2014 `RegExp.prototype.test` is a writable method on the prototype chain and can be overridden to always return `false`, completely disabling the guard.\n\nThe robust fix is to use direct string comparison operators (`===`) in native control flow (`for`/`if`) instead of prototype methods like `RegExp.prototype.test()`, since `===` is a language-level operator that cannot be overridden.\n\n### PoC\n\n#### Steps to reproduce\n\n1. Install locutus using `npm install locutus`\n2. Run the following code snippet:\n\n```javascript\nconst parse_str = require(\u0027locutus/php/strings/parse_str\u0027);\n\n// Hijack RegExp.prototype.test (simulates a prior prototype pollution gadget)\nconst original = RegExp.prototype.test;\nRegExp.prototype.test = function () { return false; };\n\n// Payload\nconst result = {};\nparse_str(\u0027__proto__[polluted]=yes\u0027, result);\n\n// Check\nRegExp.prototype.test = original;\nconsole.log(({}).polluted); // \u0027yes\u0027 \u2014 prototype is polluted\n```\n\n#### Expected behavior\n\nPrototype pollution should be prevented and `({}).polluted` should print `undefined`.\n\n```\nundefined\n```\n\n#### Actual behavior\n\n`Object.prototype` is polluted. This is printed on the console:\n\n```\nyes\n```\n\n### Impact\n\nThis is a prototype pollution vulnerability with the same impact as CVE-2026-25521. The attack requires a chaining scenario \u2014 an attacker needs a separate prototype pollution gadget (e.g., from another npm package in the same application) to override `RegExp.prototype.test` before exploiting `parse_str`. This is realistic in Node.js applications that use multiple npm packages, where one package\u0027s vulnerability can disable another package\u0027s defenses.\n\nAny application that processes attacker-controlled input using `locutus/php/strings/parse_str` may be affected. It could potentially lead to:\n\n1. Authentication bypass\n2. Denial of service\n3. Remote code execution (if polluted property is passed to sinks like `eval` or `child_process`)\n\n### Resources\n\n- Original advisory: https://github.com/locutusjs/locutus/security/advisories/GHSA-rxrv-835q-v5mh\n- Fix commit (incomplete): https://github.com/locutusjs/locutus/commit/042af9ca7fde2ff599120783e720a17f335bb01c\n- Vulnerable file: https://github.com/locutusjs/locutus/blob/main/src/php/strings/parse_str.js#L77\n\n## Maintainer response\n\nThank you for the follow-up report. This issue was reproduced locally against `locutus@3.0.24`, confirming that the earlier `parse_str` guard was incomplete: if `RegExp.prototype.test` was already compromised, the guard could be bypassed and `parse_str(\u0027__proto__[polluted]=yes\u0027, result)` could still pollute `Object.prototype`.\n\nThis is now fixed on `main` and released in `locutus@3.0.25`.\n\n## Fix Shipped In\n\n- **PR:** [locutusjs/locutus#597](https://github.com/locutusjs/locutus/pull/597)\n- **Merge commit on `main`:** `345a6211e1e6f939f96a7090bfeff642c9fcf9e4`\n- **Release:** [v3.0.25](https://github.com/locutusjs/locutus/releases/tag/v3.0.25)\n\n## What the Fix Does\n\nThe new fix no longer relies on a regex-prototype guard for safety. Instead, `src/php/strings/parse_str.ts` now rejects dangerous key paths during parsed-segment assignment, so the sink itself is hardened even if `RegExp.prototype.test` has been tampered with beforehand.\n\n## Tested Repro Before the Fix\n\n- Override `RegExp.prototype.test` to always return `false`\n- Call `parse_str(\u0027__proto__[polluted]=yes\u0027, result)`\n- Observe `({}).polluted === \u0027yes\u0027`\n\n## Tested State After the Fix in `3.0.25`\n\n- Dangerous key paths are skipped during assignment\n- The same chained repro no longer pollutes `Object.prototype`\n- The regression is covered by `test/custom/parse_str-prototype-pollution.vitest.ts`\n\n---\n\nThe locutus team is treating this as a real package vulnerability with patched version `3.0.25`. The vulnerable range should end at `\u003c 3.0.25`.",
  "id": "GHSA-vc8f-x9pp-wf5p",
  "modified": "2026-03-30T20:15:19Z",
  "published": "2026-03-27T17:58:48Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/locutusjs/locutus/security/advisories/GHSA-rxrv-835q-v5mh"
    },
    {
      "type": "WEB",
      "url": "https://github.com/locutusjs/locutus/security/advisories/GHSA-vc8f-x9pp-wf5p"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33994"
    },
    {
      "type": "WEB",
      "url": "https://github.com/locutusjs/locutus/pull/597"
    },
    {
      "type": "WEB",
      "url": "https://github.com/locutusjs/locutus/commit/345a6211e1e6f939f96a7090bfeff642c9fcf9e4"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/locutusjs/locutus"
    },
    {
      "type": "WEB",
      "url": "https://github.com/locutusjs/locutus/releases/tag/v3.0.25"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:L/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Locutus Prototype Pollution due to incomplete fix for CVE-2026-25521"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…