Remote command execution In locutus
Description
locutus call_user_func_array vulnerable to Remote Code Execution (RCE) due to Code Injection
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" ); ...
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 {...
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
Mitigation
Update Impact
Minimal update. May introduce new vulnerabilities or breaking changes.
Ecosystem | Component | Affected version | Patched versions |
|---|---|---|---|
npm | 3.0.0 |
Aliases
References