Feb 10, 2026·6 min read·19 visits
FUXA tried to stop directory traversal by deleting '../' strings, but they only did it once. By sending '....//', attackers can bypass the filter, write a malicious JavaScript file to the auto-loading 'runtime/scripts' folder, and achieve immediate RCE on the SCADA server.
A critical path traversal vulnerability in the FUXA SCADA/HMI web interface allows authenticated administrators to escape the filesystem sandbox using nested directory sequences. Because FUXA dynamically reloads scripts found in its runtime directory, this filesystem write primitive automatically escalates to Remote Code Execution (RCE), potentially giving attackers control over industrial control systems.
In the world of Industrial Control Systems (ICS), the line between "air-gapped safe haven" and "accessible web interface" is blurring faster than a centrifuge spinning out of control. Enter FUXA, a modern, open-source SCADA (Supervisory Control and Data Acquisition) and HMI (Human-Machine Interface) solution. It’s built with Node.js, uses WebSockets, and offers a slick interface for managing PLCs (Programmable Logic Controllers). It’s the kind of tool that makes an OT (Operational Technology) engineer's life easier and a security researcher's heart race.
Here is the reality of modern SCADA: we are moving away from proprietary, binary protocols and toward standard web technologies. While this improves interoperability, it also imports the entire history of web vulnerabilities into the factory floor. FUXA allows administrators to upload project files, manage resources, and script logic. It’s a powerful feature set.
But power requires control. CVE-2026-25951 is a classic example of what happens when a developer tries to implement security controls manually rather than using established libraries. It’s a vulnerability that transforms a simple file management feature into a weapon that can override industrial processes.
The root cause of this vulnerability is a lesson taught in every 'Intro to AppSec' class: Do not implement your own sanitization using simple string replacement. The developers of FUXA wanted to prevent users from accessing files outside of the intended directory. They knew that ../ is the universal signal for "let me out," so they wrote a filter to destroy it.
The logic (roughly) looked like this:
// The "Security" Filter
const cleanPath = userInput.replace(new RegExp('../', 'g'), '');This is a blacklist approach, and it suffers from a fatal logical error known as non-recursive sanitization. The regex engine sweeps through the string once. It finds every instance of ../ and deletes it. That sounds fine, until you think like a hacker.
If you provide the string ....//, the regex engine sees the ../ sitting in the middle. It deletes those three characters. What is left? The two dots at the beginning and the slash at the end merge together to form a new ../. The application then happily passes this newly formed directory traversal sequence to the filesystem APIs. It is like locking your front door but leaving a key under the mat, and writing "Key Under Mat" on the doorframe.
Let's look at the actual code changes to understand the gravity of the mistake and the correctness of the solution. The patch, applied in commit f7a9f04b2ab97ab5421e4ec4e711c51e9f4b65c8, completely rips out the regex logic in favor of path canonicalization.
The Vulnerable Pattern:
// OLD: Vulnerable to nested sequences
var fileName = req.query.name.replace(/\.\.\//g, '');
fs.writeFile(path.join(baseDir, fileName), ...);The Fix (The Jail Pattern):
The developers implemented a new utility, resolveWithin, which forces the path to be absolute and checks if it still resides within the intended directory.
// NEW: server/api/path-helper.js
function resolveWithin(baseDir, targetPath) {
// 1. Normalize the input (removes redundant slashes, resolves '..')
const normalized = normalizeRelativePath(targetPath);
if (!normalized) return null;
// 2. Resolve absolute paths
const resolvedBase = path.resolve(baseDir);
const resolvedTarget = path.resolve(resolvedBase, normalized);
// 3. Calculate relative path between Base and Target
const relative = path.relative(resolvedBase, resolvedTarget);
// 4. If the relative path starts with '..', it tried to escape!
if (relative.startsWith('..') || path.isAbsolute(relative)) {
return null;
}
return { resolvedTarget, normalized };
}This is the correct way to handle file paths. It doesn't guess what the attacker might send; it resolves the final destination and checks if that destination is legally allowed. If the math says the file is outside the jail, the request is dropped.
So we have a path traversal. Usually, this means we can read /etc/passwd or C:\Windows\win.ini. That's bad, but in FUXA's case, it's catastrophic. The system allows writing files, and critically, it monitors the runtime/scripts directory for changes.
Here is the kill chain:
pwn.js, containing a reverse shell or a command to dump database credentials.POST /api/resources?name=....//....//runtime/scripts/pwn.js HTTP/1.1
...
[Malicious JavaScript Content]../, leaving ../../runtime/scripts/pwn.js. The file is written to the disk.CVSS scores (8.6 in this case) often fail to capture the visceral reality of an OT compromise. If this were a blog engine, RCE would mean defacement. In FUXA, RCE means the attacker controls the HMI.
From the HMI, an attacker can:
This vulnerability bridges the gap between the IT network (where the web interface lives) and the OT network (where the physical machinery lives). It is a direct tunnel from a web browser to physical destruction.
The remediation is straightforward but urgent. The vulnerability is patched in FUXA version 1.2.11. If you are running an older version, you are exposed.
Immediate Steps:
v1.2.11.runtime/scripts directory. If you see files you didn't put there, assume compromise and initiate incident response.For developers reading this: this is your reminder to never trust user input, especially when it deals with filesystems. Use the standard library's path resolution tools. Regex is not a firewall.
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
FUXA frangoteam | < 1.2.11 | 1.2.11 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 (Path Traversal) |
| CVSS v4.0 | 8.6 (High) |
| Attack Vector | Network |
| Privileges Required | High (Admin) |
| Impact | Remote Code Execution (RCE) |
| Status | Patched (v1.2.11) |
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')