FUXA's Scheduler API Bypass: When One Missing `isAdmin` Gate Opens the Whole Plant
A critical missing authorization check in FUXA's Scheduler API allowed any authenticated operator to create or modify scheduled device actions—escalating to full admin control over SCADA operations. Breakdown of the attack, detection, and fix.
A few days ago, a GitHub Security Advisory dropped for FUXA—the open-source web-based SCADA/HMI platform. It wasn't a buffer overflow in some obscure protocol parser. It wasn't an authentication bypass that let you waltz in without credentials. This was something simpler and, in my experience, far more common in real-world industrial systems: an authorization check that someone just forgot to write.
The advisory—GHSA-8ghr-w65f-j3qr—says it plainly. "An authorization issue in the Scheduler API allowed authenticated non-admin users to create or modify scheduled actions that should be restricted to administrators."
That's it. One missing role check. One API endpoint that trusts the caller because they're already logged in. And in a SCADA environment, that's not a theoretical, "well maybe someone could..." kind of bug. It's a runaway conveyor belt, a stopped pump, a popped circuit breaker. The stuff that makes safety engineers lose sleep.
Let's walk through what actually happens, why it's so easy to miss, how an attacker would exploit it, and—most importantly—what you can do about it if you're running FUXA or any similar OT web application.
The anatomy of the bug
FUXA, like many modern SCADA web interfaces, has a concept of scheduled actions. You want to turn on a pump at 06:00 and off at 18:00. You want to toggle a valve every Tuesday at midnight. Those scheduled tasks get stored in a database, and a background scheduler kicks them off at the right time.
Typically, the UI presents a form that only administrators can see. Create a schedule, pick the device, the command, the cron expression. The frontend does its little dance, the backend validates, and the task gets queued.
The vulnerability here is that the backend API—the one that actually creates the schedule—had no authorization check. It likely looked something like this:
// routes/schedule.js (simplified hypothetical example)
router.post('/api/schedule', authenticateMiddleware, async (req, res) => {
const { deviceId, action, cron } = req.body;
// ... validation of fields ...
await scheduler.create({ deviceId, action, cron, createdBy: req.user.id });
res.json({ success: true });
});Notice anything missing? There's an authenticateMiddleware that makes sure the user's session token is valid. But there's nothing that checks req.user.role. No if (req.user.role !== 'admin') return res.status(403). Nothing.
And here's the thing. The UI probably hid the "Scheduler" page from operators. The menu items wouldn't appear. Maybe the template engine conditionally rendered the buttons. But the API itself? Wide open to anyone holding a valid session cookie.
That's the whole bug. A classic Broken Function Level Authorization—one of the OWASP Top 10 for APIs—sitting right in an industrial control webapp.
The exploitation path
Once you understand the oversight, the attack is straightforward.
An operator logs in with their legitimate credentials. They have limited permissions—maybe they can view dashboards, acknowledge alarms, start/stop certain devices manually. They open the browser's developer tools, look at the network tab, and notice requests hitting /api/schedule when an admin creates a task. The payload is a simple JSON object:
POST /api/schedule HTTP/1.1
Host: fuxa.local
Content-Type: application/json
Cookie: session=eyJhbGciOi...
{
"deviceId": 42,
"action": "start",
"cron": "0 3 * * 1"
}They can't see that in the UI. But the operator can fire off the same request with curl, PowerShell, or—more dangerously—a custom automation script:
curl -X POST https://fuxa.local/api/schedule \
-H "Content-Type: application/json" \
-H "Cookie: session=$OPERATOR_SESSION" \
-d '{"deviceId":42,"action":"stop","cron":"*/2 * * * *"}'If the backend doesn't check the role, it happily creates the schedule. The operator just told the system to stop device 42 every two minutes. Forever.
No exploit framework needed. No memory corruption. Just a plain HTTP request with a session token the attacker already owns.
And depending on how the action field works, things can get much worse. Many SCADA platforms let scheduled actions call system commands, execute scripts, or trigger REST calls to other services. If the action field accepts arbitrary shell commands or references to Lua/JavaScript routines, the operator can schedule a reverse shell or a data exfiltration script to run with the privileges of the FUXA process. Scheduled for 3 AM. Nobody watching.
Sequencing the attack
Here's what the privilege escalation flow looks like in practice:
The most insidious part? No alarm. No audit log showing "suspicious operator action." As far as the system is concerned, a scheduled task was created. The createdBy field might even retain the operator's user ID—but that's normal if you have legitimate operator-schedulable tasks. Without role-based anomaly detection, you'd never notice until the pumps start acting strangely.
Why this keeps happening in OT
I've pentested a fair number of industrial web applications, and this pattern—frontend hiding things while the backend trusts the client—isn't unique to FUXA. It's practically the default when developers rush to add features.
There's a mental model that "if the UI doesn't show it, the user can't do it." But that model collapses the moment someone opens curl or a headless browser. In OT environments, that someone could be a disgruntled operator, a contractor with just-enough access to be dangerous, or an external attacker who phished an operator's credentials.
Another reason: OT teams often treat the web interface as an afterthought. The focus is on PLC logic, RTU firmware, network segmentation. The webapp gets built by a small team—sometimes a single developer—without security code review. They're adding features like scheduling, reporting, remote control. Authentication gets added. Authorization? That's "implemented on the frontend."
And authorization bugs are brutal to spot in manual QA. The tester logs in as admin, sees everything works. Logs in as operator, sees the button is gone. Checks the box "authorization verified." Meanwhile the API endpoint sits there responding 200 to anyone.
Detection: finding this in your own stack
If you're running FUXA, patch it. The advisory links to a fix commit. But if you're maintaining a similar SCADA webapp—or if you're a researcher auditing one—here's how you'd hunt for these broken function-level authorization bugs.
First, map every API endpoint that mutates state: POST, PUT, DELETE, PATCH. In FUXA's case, it's /api/schedule. In your own application, it might be /api/actions, /api/jobs, /api/workflows, /api/config.
Then, for each endpoint, make the same request with a low-privilege session. If you get 200 OK and the action succeeds, you've found a problem.
A quick one-liner with curl and jq to capture the difference:
# Capture response status for each endpoint
while read endpoint; do
code=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST "$endpoint" \
-H "Content-Type: application/json" \
-H "Cookie: session=$LOW_PRIV_SESSION" \
-d '{"deviceId":1,"action":"ping"}')
if [ "$code" = "200" ]; then
echo "Possible authorization bypass: $endpoint returned $code"
fi
done < endpoints.txtYou can also monitor logs for anomalies. Look for requests to scheduler-related endpoints where the authenticated user's role is not admin. If your application logs include the user role in each request, set up alerts for role!=admin hitting sensitive routes. If it doesn't log the role, fix that—operational security monitoring depends on it.
For forensics after an incident, check the created_by field in your schedule/action tables. If you see operators scheduling things they shouldn't, you've got a trail. But that only helps if you look.
The actual fix: don't trust the UI
The fix in FUXA was adding a server-side role check. Something like:
router.post('/api/schedule', authenticateMiddleware, adminOnlyMiddleware, async (req, res) => {
// ...
});
// adminOnlyMiddleware
function adminOnlyMiddleware(req, res, next) {
if (req.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
next();
}Simple. Inexpressibly boring. That's the point.
But I'd go further. In a SCADA context, don't just check the role; check the permission in a granular way. Maybe a role called scheduler_admin or task-creator. Use an ACL or a policy engine that answers the question: "Can this user create a task with this action on this device?" A role check is a blunt instrument; policy checks give you defense-in-depth.
Also, no matter what you do on the backend, the frontend will try to be helpful and hide things. That's fine—UX matters. But never, ever rely on it as a security mechanism. Comment your code with // WARNING: UI hides this from operators, but auth is enforced by the adminOnly middleware above if it helps the next developer remember.
The bigger picture: API authorization as infrastructure
The FUXA bug is a single instance of a systemic problem in OT web applications. We've moved PLC programming from RS-232 cables to web-based configuration panels, and we've brought along all the weaknesses of web development. Broken authorization, insecure direct object references, missing rate limiting—these are not new. They're just newly catastrophic on the factory floor.
I've reversed enough undocumented IoT and SCADA APIs to know one thing: the authorization model is usually whatever the first developer thought of at 11 PM on a Friday. There's rarely a dedicated authorization service. Often the isAdmin check is a boolean column on the user table, and that boolean is only consulted by the UI rendering code. The API handlers assume that if you're authenticated, you're allowed.
The fix isn't just patching FUXA. It's building authorization as a first-class concern in every OT product. Use an API gateway that enforces policies. Move role checks into middleware that runs before every request handler. Test authorization states, not just happy paths. And for goodness' sake, don't let operators schedule the plant shutdown.
The advisory for FUXA is a wake-up call. Not because the bug is exotic—it's painfully mundane. But because it shows how fast a mundane web bug can become an operational emergency when the scheduled action is "close valve" instead of "send email."
Patch it. Audit your own scheduler endpoints. And never, ever trust the frontend.
Related posts
- Security
How I got free cinema credit by ordering -2 popcorns
A missing input validation on M-Tix Cinema XXI's food ordering API let me increase my account balance by submitting negative quantities. No tools needed — just a browser.
May 19, 2026 · 6 min - Security
How I analyze API security headers in 30 seconds
A quick checklist for reading HTTP response headers and spotting security misconfigurations before you even look at the response body.
May 18, 2026 · 7 min - Security
Common auth mistakes I find when reverse-engineering APIs
After years of poking at APIs that weren't meant to be poked at, these are the auth patterns that break most often — and why.
May 18, 2026 · 9 min