Summary
I reported and coordinated CVE-2026-25893, an unauthenticated remote code execution vulnerability in FUXA. This is the first of 7 critical vulnerabilities I published while exploring the authentication boundaries of the application.
Many might recognize FUXA from OffSec. The Offsec Proving Grounds has a “FUXA” lab featuring the HMI, where the intended solution was CVE-2023-33831, an unauthenticated RCE that has long been patched. Being part of Offsec’s curriculum, prospective and professional hackers have studied this application for years. So what did thousands of hackers miss?
Severity: Critical - 10.0 (CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H)
FUXA is a web-based Process Visualization (SCADA/HMI/Dashboard) software. Prior to 1.2.10, an authentication bypass vulnerability in FUXA allows an unauthenticated, remote attacker to gain administrative access via the heartbeat refresh API and execute arbitrary code on the server. This issue has been patched in FUXA version 1.2.10.
Technical Analysis
FUXA, developed by frangoteam, is an open-source, web-based HMI software used for creating SCADA and IoT dashboards to monitor and control industrial processes in real-time. A commercial version is also available, and it is shipped as an integrated HMI in a few PLCs and IoT solutions.
Remote Code Execution as a Service (RCEaaS)
In FUXA, as with many HMIs, remote code execution is an intended feature for administrators to implement custom automation that exceeds standard configuration options. When threat modeling the application, I specifically excluded bugs requiring administrative privileges. Vulnerabilities like path traversal or command injection, when reachable only by administrators, do not cross a meaningful security boundary. Patching them provides no tangible security benefit since administrators already possess equivalent capabilities by design.
With the an Administrator token, a user can access the /api/runscript endpoint. This endpoint is protected by secureFnc (authentication) and checkGroupsFnc (authorization). The endpoint passes the user-supplied script directly to the script manager, which executes it in the context of the running process.
// server/api/scripts/index.js
scriptsApp.post("/api/runscript", secureFnc, function (req, res, next) {
...
const permission = checkGroupsFnc(req);
...
runtime.scriptsMgr.runScript(req.body.params.script, req.body.params.toLogEvent)
.then(function (result) {
res.json(result);
});
});
This is the exact feature that was previously unauthenticated, resulting in CVE-2023-33831, and the intended solution for the Offsec FUXA lab. So any vulnerability that grants an unauthenticated or low-privileged user administrator permissions results in RCE.
Securing the Default Configuration
Also like many HMIs, FUXA is insecure by default, relying on network isolation for protection. It does allow administrators to configure authentication to prevent unauthenticated RCE. However, the way FUXA handled JWTs was particularly dangerous. I typically wouldn’t report an unsafe default configuration for a CVE, but for the sake of completeness, I also reported this as CVE-2026-25894, distinct from CVE-2026-25893.
An insecure default configuration in FUXA allows an unauthenticated, remote attacker to gain administrative access and execute arbitrary code on the server. This affects FUXA through version 1.2.9 when authentication is enabled, but the administrator JWT secret is not configured. This issue has been patched in FUXA version 1.2.10.
The vulnerability resides in api/jwt-helper.js, where the JWT signing key is initialized with a default value. FUXA uses a shared secret to sign and verify JWTs. If this value is a static, known string, an attacker can create a valid JWT offline and present it to the server as an x-access-token.
// server/api/jwt-helper.js
var secretCode = 'frangoteam751';
function init(_secureEnabled, _secretCode, _tokenExpires) {
secureEnabled = _secureEnabled;
if (_secretCode) {
secretCode = _secretCode;
}
...
}
The official documentation for enabling authentication instructs configuring secretCode and provides the same static value as the default.
secureEnabled: true,
secretCode: 'frangoteam751',
tokenExpiresIn: '1h'
This design results in a fail-open security posture, where the application can report or appear to be operating in secureEnabled mode while still accepting tokens signed with a publicly known key. While other security settings, like enabling authentication and administrator credentials, are configurable within the UI, the JWT secret is not.
To prevent this, the application should avoid hardcoded secrets entirely and instead generate a random key when one is not explicitly configured, ensuring the system remains secure even if the administrator overlooks this setting.
Breaking the Most Secure Configuration
When authentication is enabled by setting secureEnabled and a secure secretCode is configured, CVE-2026-25893 still provides an authentication bypass to achieve unauthenticated remote code execution. The vulnerability arises from a chain of three logical failures.
The vulnerability begins in the verifyToken middleware within the same api/jwt-helper.js. The logic flow fails to terminate the request when jwt.verify() encounters an error (such as an invalid or malformed token). Instead, it sets the user context to “guest” and calls next(). An exploit requires supplying an invalid x-access-token to reach the jwt.verify() error path as omitting the token triggers issuance of a valid guest token and prevents token refresh.
// server/api/jwt-helper.js
function verifyToken (req, res, next) {
let token = req.headers['x-access-token'];
...
if (token) {
jwt.verify(token, secretCode, (err, decoded) => {
if (err) {
req.userId = "guest";
req.userGroups = ["guest"];
} else {
req.userId = decoded.id;
req.userGroups = decoded.groups;
if (req.headers['x-auth-user']) {
...
}
}
next();
});
}
...
}
The unblocked request reaches the /api/heartbeat endpoint in api/index.js. This endpoint serves as a gadget to mint new tokens. It trusts the upstream middleware to have validated the user and proceeds to generate a new token if req.body.params is present.
// server/api/index.js
apiApp.post('/api/heartbeat', authMiddleware, function (req, res) {
...
} else if (req.body.params) {
const token = authJwt.getNewToken(req.headers)
if (token) {
res.status(200).json({
message: 'tokenRefresh',
token: token
});
}
}
...
});
The getNewToken function in api/jwt-helper.js is responsible for creating the new JWT. It contains a critical vulnerability where it parses the x-auth-user header, which is fully controlled by the attacker, to determine the identity of the new token. It then signs this user-supplied data with the server’s private secretCode.
// server/api/jwt-helper.js
function getNewToken(headers) {
const authUser = (headers['x-auth-user']) ? JSON.parse(headers['x-auth-user']) : null;
if (authUser) {
return jwt.sign({
id: authUser.user,
groups: authUser.groups
},
secretCode, {
expiresIn: tokenExpiresIn
});
}
return null;
}
To satisfy admin-only checks implemented via haveAdminPermission(), the attacker must mint a token whose groups claim is a scalar integer, because haveAdminPermission() performs adminGroups.indexOf(permission) against numeric constants.
// server/api/jwt-helper.js
function haveAdminPermission(permission) {
if (adminGroups.indexOf(permission) !== -1) {
return true;
}
return false;
}
This allows an unauthenticated attacker to mint administrator tokens to bypass authentication and authorization controls and execute arbitrary code via the intended runscript feature.
"x-access-token": "invalid_junk_token",
"x-auth-user": '{"user": "admin", "groups": -1}'
Impact
This vulnerability impacts FUXA deployments in their most secure configuration, including when secureEnabled is true and a secure secretCode is configured.
Exploitation allows an unauthenticated, remote attacker to bypass all authentication mechanisms and obtain administrative access to the FUXA instance by minting administrator JWTs via the heartbeat refresh endpoint. With these elevated privileges, the attacker can interact with administrative APIs, including intended features designed for automation and scripting, to execute arbitrary code in the context of the FUXA service. Depending on deployment configuration and permissions, this may lead to full system compromise.
In an ICS/SCADA environment, the HMI is often one of the few assets permitted to communicate across the IT/OT DMZ. The impact of RCE potentially exposes connected ICS/SCADA environments to follow-on actions.
Patches
This issue has been patched in FUXA version 1.2.10 by addressing the failure that enabled an attacker to reach the refresh endpoint and modify the x-auth-user header.
Notably, the patch maintained the guest fallback feature. I brought this up with the maintainer as a secondary weakness, allowing read access to many APIs. This is currently an intended feature of the application, but we agreed that this guest read access should be configurable in a future release.
Timeline
| Date | Event |
|---|---|
| 2026-01-14 | Initial Report via GitHub Private Vulnerability Report |
| 2026-01-16 | Maintainer Acknowledgement |
| 2026-01-16 | Patch Released |
| 2026-02-04 | Maintainer Releases GHSA-vwcg-c828-9822 |
| 2026-02-04 | Published to GitHub Advisory Database |
| 2026-02-05 | GitHub Staff Mistakenly Assigns Duplicate CVE |
| 2026-02-05 | Request GitHub Staff Review |
| 2026-02-09 | GitHub Staff Assigns CVE-2026-25893 |
| 2026-02-09 | Published to National Vulnerability Database |
| 2026-02-09 | Proof of Concept (PoC) published by third party |
| 2026-02-17 | Published to websmite.com |