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 plain text 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.
Enumeration
I start my enumeration with nmap which scans all ports on the target machine.
It finds two open TCP ports, SSH (22) and HTTP (8080):
Based on the OpenSSH version, the host is likely running Ubuntu 20.04 focal. Port 8080 is also open, but Nmap is having difficulties identifying the service running behind this port. It’s a web server, so I can open it in Firefox by browsing to http://10.10.11.204:8080.
The webservice
When the request finishes loading, I get presented with the following web page. Seems like I’m dealing with some storage provider. I start a gobuster scan in the background (gobuster dir -u http://10.10.11.204:8080/ -w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt -o 4_gobuster_root) while I start browsing around and discover a couple of interesting pages.
Navigating to the Blogs page (/blogs), it lists three blog posts. But none of them seems to be going anywhere.
Going to the Registration page (/register) shows an “Under Construction banner.
At the top right of the Home page, there’s an upload link, which goes to /upload:
Before diving into this upload functionality, I see my gobuster scan has finished and came back with the following results:
One interesting result I didn’t bump into during my manual recon is the /show_image endpoint, which returns a 400 (Bad Request). Sending a request with curl reveals how I should use the endpoint:
It seems I just need to add an img parameter to the query string. Before diving into this I decide to quickly play around with the upload functionality by uploading a small plain text fil:
That doesn’t seem to work, but when I give it an actual image, it returns a link to that image.The link goes to the same endpoint I discovered via burp just earlier /show_image?img=[image file name] .
While navigating to the image I proxy the request through BurpSuite so I can have a look at the response headers. But that doesn’t seem to reveal anything new.
Frankly I need a shell
While navigating to the image endpoint in burp I can see that the uploaded image gets returned. Given the img parameter takes a file I decide to test for LFI. Browsing around on hacktricks the first payload I can find on the website is ../../../../../. I send the request to the repeater and modify the img parameter to the LFI payload.
This returns a directory confirming my assumption that the endpoint is vulnerable to a LFI. If I had to take a guess this returned the directory listing of what seems to be the /var directory. Changing the payload to ../../../../../../home I discover 2 different users on the system:
fran
phil
At this point given it’s a CTF I decided to immediately try my luck and grab the user.txt without going for a shell first. Sending requests to both home folders I discover that the user.txt is in phils home folder. But trying to grab the flag I get an empty response.
This means I probably don’t have enough permissions to read the file from the user that is currently running the web process. So I decided to focus on the web app itself, I got access to the /var/www folder, so I can read the source code and maybe that can lead me to code execution or a shell on the box.
Seems there are 2 folder, I decide to look at /var/www/html first but that folder turns up empty. So it either doesn’t contain any files or I don’t have permissions again. Trying my luck with the WebApp folder I get the following listing.
The file that immediately draws my attention is HELP.md, which returns the following:
This reveals I’m dealing with a spring boot application, it has been ages since I professionally used Java or spring for that matter. What I can remember is that pom.xml is the configuration file for maven and helps manage dependencies and build processes. It basically contains information about the project such as its name, version, and dependencies on other software
libraries. In this case I can leverage this pom.xml to get an overview of the apps dependencies and check if any of them are out of date and contain any vulnerabilities.
Downloading the pom.xml file via the LFI vulnerability reveals the following:
Copying the file to my local machine I can now use a vulnerability scanner to check for outdated packages. In this case I use the Snyk command line tool to start the scan. At the first try it returned the following error for me:
This was because I didn’t have Maven installed on my system. After running sudo apt install maven and rerunning the command again I get the following overview
Snyk does an amazing job listing out all the vulnerable packages. It also includes a link to a more detailed write up and a severity score. Going through the list I highlight the following packages as interesting candidates to dive deeper into:
I briefly played around with the beans exploit, because it seemed a rather trivial exploit. But I didn’t really get it working. So I moved on plan B the spring-cloud-function. The CVE report on Snyk refers to the following POC on github: https://github.com/hktalent/spring-spel-0day-poc. Which looks something like this:
Translating this to a curl request I get the following command, which when executed successfully it should create a file /tmp/exce_me on the target machine.
To verify if my exploit works I can use the LFI to see if the file got created. And indeed it does!
I craft a bash reverse shell and save it to a file called exec_me.sh that I expose via a python3 -m http.server webserver.
And the following command downloads this file to the remote machine.
To receive the reverse shell from the remote host I start a netcat listener on port 8888 (same port as in the bash reverse shell script) on my local machine. To then trigger the exploit:
Becoming phil
After sending the request I receive a reverse shell as the frank user on my netcat listener. I then proceed to upgrade my shell by executing python3 -c "import pty; pty.spawn('/bin/bash') . Then I press CTRL+Z to temporarily jump out of the reverse shell and execute stty raw -echo; fg which brings me back into my reverse shell. Now I can change the term variable with export TERM=xterm and be sure I wont jump out of my reverse shell unexpectedly.
Enumeration of the filesystem reveals the password for the user phil in the
/home/frank/.m2/settings.xml Maven configuration file.
That password didn’t work for SSH as phil, but it does work to su as phil:
Now I can read the user.txt flag.
I am Root
Running my good old trusty [linpeas.sh](http://linpeas.sh) didn’t provide any useful information. I also tried manual
enumeration but had no luck there either. So I decided to give pspy a try. I downloaded the 64bit binary from GitHub, added it into the folder being hosted by my python webserver. And then proceeded download the binary on to the target machine.
After running it for a few minutes, I notice the following processes running as root indicating that they are being run on a CRON schedule.
More particularly this is the exact command:
This shows that the root user is using ansible-parallel to execute any playbook in the /opt/automation/tasks directory.
Ansible is a tool that is pretty dear to my heart. It’s a powerful and flexible tool for automating IT tasks and managing infrastructure at scale, making it easier to maintain consistency across multiple servers and environments. It uses a declarative language called YAML to describe configuration changes and tasks, and can be used to manage systems running on a wide range of operating systems. And a playbook is Ansible’s primary way to describe the configuration, deployment and orchestration of different tasks.
Checking the permission of the /opt/automation/tasks directory, it seems that only the root user and the staff user group have read and write access.
Fortunately, the user phil is a member of the user group staff .
The simplest way to run some command via Ansible is with the built-in Shell module. I’ll make a file that’s as simple as:
I’ll save this as /opt/automation/tasks/exec-me.yml. The playbook creates a copy of bash that’s owned by root with the SetUID bit enabled. This way I can launch bash with -p and force it to maintain privileges. The next time the CRON runs, a new bash file appears in /tmp. Executing this copy gives me a root shell and allows me to read the flag.