
PoC, non-destructive scanner, Docker lab, PCAP and Suricata/Sigma detection rules for CVE-2026-58115, unauthenticated root RCE via the Node-RED admin API on Siemens SIMATIC IoT2050.
Check · Exploit · Detection · PCAP · Impact · Write-up
Disclaimer: This tool is provided for authorized security testing and educational purposes only. Only use against systems you own or have explicit written permission to test. Unauthorized access to computer systems is illegal.
CWE-306 | Missing Authentication for Critical Function | SIMATIC IoT2050 Advanced (6ES7647-0BA00-1YA2), Industrial OS < V4.3.4.1 with Node-RED installed | Remote pre-auth, TCP/1880, no user interaction
The Node-RED runtime shipped on the IoT2050 listens on port 1880 with no adminAuth configured and runs as root. Anyone who can reach the port can read the current flows, deploy a new flow through POST /flows, and include an exec node in it. The command runs as uid 0 and its output can be routed straight back to the attacker through an http in / http response pair. One unauthenticated request deploys the flow, a second one triggers it. CVSS 10.0 on both v3.1 and v4.0. VulnCheck observed exploitation attempts against its honeypots on 2026-10-30, two and a half months after the Siemens advisory.
Author: al4n4n (Dadb0d)
Write-up: blog.al4n4n.com/CVE-2026-58115-Siemens-IoT2050-Node-RED-Unauthenticated-Root-RCE/
Fixed in: Industrial OS V4.3.4.1 (Siemens SSA-834709)
Siemens publishes the IoT2050 board support package as open source: siemens/meta-iot2050 (MIT, Isar/Debian). The Node-RED layer in that repo is what this CVE is about, and it tells the whole story in two files.
The systemd unit, meta-node-red/recipes-app/node-red/files/node-red.service.tmpl at tag V01.06.01 (commit 5259222771, 2025-12-22):
[Service]
Type=idle
User=root # <-- runs as uid 0
WorkingDirectory=${NODE_RED_HOME_DIR}
ExecStart=/usr/bin/node-red -u ${NODE_RED_HOME_DIR}/.node-red
with NODE_RED_HOME_DIR ?= "/root" in node-red-home.inc. The layer ships no settings.js and the string adminAuth does not appear anywhere in the repository. On first start Node-RED therefore writes its default settings.js, in which the whole authentication block is commented out:
//adminAuth: { // <-- never enabled
// type: "credentials",
// users: [{ username: "admin", password: "...", permissions: "*" }]
//},
Node-RED's admin API is designed to be open unless adminAuth is set; that is documented behaviour. The bug is shipping it that way on an industrial gateway, bound to all interfaces, as root, and started automatically: the recipe has no preset, so Debian's dh_installsystemd default enables and starts the unit on install. The recipe is named node-red_4.0.9.bb, but the Siemens npm-shrinkwrap.json it installs from resolves Node-RED 4.1.0, which is what actually runs.
The only change to the layer between V01.06.01 and V01.06.04 is commit 6d7046b4 (2026-03-25, "node-red: disable service by default"), which adds a 99-node-red.preset with disable node-red.service and --no-start. It stops the service from running unless an operator enables it. It does not add authentication. Whether Industrial OS V4.3.4.1 contains this change, an adminAuth configuration, or both, is not public; Siemens' own mitigation advice is to uninstall Node-RED or harden it following the Node-RED security guide.
adminAuth in settings.js (the shipped configuration)Node-RED-API-Version: v2 header is sent for convenience only.Root comes for free because the unit runs as User=root. On a Node-RED instance that runs as an unprivileged user the same requests still give command execution as that user.
| File | Purpose |
|---|---|
setup.py | Lab: builds the target image from Siemens' lockfile, starts vulnerable / fixed / hardened containers |
Dockerfile | Target image: Debian + Node-RED built exactly as meta-node-red builds it, started as the Siemens unit starts it |
check.py | Non-destructive scanner: GET /auth/login, /settings, /flows; --json; exit 0/1/2 |
poc.py | Deploys one flow (http in → exec → http response), reads back id, restores the original flows |
verify.py | Dashboard: environment, check, N exploit rounds, negative control, verdict |
detection/cve-2026-58115.rules | Suricata rules for the unauthenticated deployment over the admin API |
detection/cve-2026-58115.yml | Sigma rule for the host side: the Node-RED process spawning a shell |
capture/CVE-2026-58115.pcap | Live capture of a check + exploit run (70 packets, plain HTTP) |
poster.png | Repository poster image |
CVE-2026-58115/
├── README.md
├── LICENSE
├── poster.png
├── Dockerfile # Emulated target (see "What this repo proves")
├── setup.py # Lab setup / teardown
├── check.py # Non-destructive vulnerability scanner
├── poc.py # Single-shot exploit (deploy flow, run `id`, restore)
├── verify.py # Verification dashboard with negative control
├── capture/
│ ├── CVE-2026-58115.pcap # Live exploit capture
│ └── README.md # How it was captured, what is in it
└── detection/
├── cve-2026-58115.rules # Suricata
└── cve-2026-58115.yml # Sigma
# Downloads Siemens' npm-shrinkwrap.json (sha256 verified), builds the image,
# starts :1880 vulnerable, :1881 fixed (service disabled), :1882 hardened (adminAuth)
python3 setup.py
# Match the V01.06.01 base exactly (Debian trixie) instead of bookworm
python3 setup.py --base debian:trixie-slim
python3 setup.py --teardown
python3 check.py http://192.0.2.10:1880
python3 check.py http://192.0.2.10:1880 --json
python3 poc.py http://192.0.2.10:1880 # deploys flow, runs `id`, restores flows
python3 poc.py http://192.0.2.10:1880 --cmd 'cat /etc/os-release'
python3 verify.py http://192.0.2.10:1880 --rounds 3 --negative-target http://192.0.2.11:1880
python3 verify.py http://192.0.2.10:1880 --rounds 3 --no-negative