10 minute read


Admirer was a fun box that took me a couple of hours to finish - I mostly got stuck on enumeration, since I usually only use one wordlist (whoops!). After that, a bulk of my time was spent researching the Adminer CVE since I hadn’t used it before. Privesc was fun but something I had seen before, so it didn’t take me as long :) Let’s get to it!

Creator: mrb3n
Rating: 4.2 stars


As with most of the HTB machines, I start by adding it to my /etc/hosts file. This allows me to be a lazy hacker and type a name instead of memorizing an IP address. To do so, add the following line to your hosts file:    admirer.htb

After that, we can start up our nmap scan - this helps us discover ports we can start enumerating. Begin with nmap -sCV -O -oA tcp-full -p- admirer.htb:

# Nmap 7.80 scan initiated Thu Jul  9 23:13:40 2020 as: nmap -sCV -O -oA tcp-full -p- admirer.htb
Nmap scan report for admirer.htb (
Host is up (0.059s latency).
Not shown: 65532 closed ports
21/tcp open  ftp     vsftpd 3.0.3
22/tcp open  ssh     OpenSSH 7.4p1 Debian 10+deb9u7 (protocol 2.0)
| ssh-hostkey: 
|   2048 4a:71:e9:21:63:69:9d:cb:dd:84:02:1a:23:97:e1:b9 (RSA)
|   256 c5:95:b6:21:4d:46:a4:25:55:7a:87:3e:19:a8:e7:02 (ECDSA)
|_  256 d0:2d:dd:d0:5c:42:f8:7b:31:5a:be:57:c4:a9:a7:56 (ED25519)
80/tcp open  http    Apache httpd 2.4.25 ((Debian))
| http-robots.txt: 1 disallowed entry 
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: Admirer
No exact OS matches for host (If you know what OS is running on it, see ).

Network Distance: 2 hops
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

OS and Service detection performed. Please report any incorrect results at .
# Nmap done at Thu Jul  9 23:14:38 2020 -- 1 IP address (1 host up) scanned in 57.87 seconds

Okay, so we have 3 services - FTP, SSH, and HTTP. SSH isn’t likely to give us anything to work with and doesn’t have a known RCE, so let’s ignore it for now. If you try to FTP in, you’ll notice that it doesn’t allow for anonymous login (using username: anonymous and no password) meaning we need to get credentials first. That leaves us with one option - web enumeration.

One of two methods finds the next step - either kick off a gobuster or manually browsing will do - but let us assume that you ran a gobuster. If so, you’d come up with something similar to the below list:

Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
[+] Url:            http://admirer.htb
[+] Threads:        30
[+] Wordlist:       /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Extensions:     ini,cgi,php,html,txt,sh
[+] Timeout:        10s
2020/06/03 14:26:33 Starting gobuster
/index.php (Status: 200)
/images (Status: 301)
/assets (Status: 301)
/robots.txt (Status: 200)
/server-status (Status: 403)
2020/06/03 15:10:08 Finished

Only 2 pages have a 200 OK as the response code - /index.php and /robots.txt. For those unaware, here’s how Cloudflare defines a robots.txt file.

A robots.txt file is a set of instructions for bots. This file is included in the source files of most websites. Robots.txt files are mostly intended for managing the activities of good bots like web crawlers, since bad bots aren’t likely to follow the instructions. Think of a robots.txt file as being like a “Code of Conduct” sign posted on the wall at a gym, a bar, or a community center: The sign itself has no power to enforce the listed rules, but “good” patrons will follow the rules, while “bad” ones are likely to break them and get themselves banned.

Let’s be the bad patrons and see what /robots.txt has to offer up:

Admirer's robots.txt

Okay, so there’s a hidden directory - let’s browse to it. Unfortunately, if we do so this nets us a 403 Forbidden page. Don’t stop here, though - let’s run another gobuster just in case:

Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
[+] Url:            http://admirer.htb/admin-dir
[+] Threads:        30
[+] Wordlist:       /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Extensions:     ini,php,html,txt,sh
[+] Timeout:        10s
2020/06/03 15:18:55 Starting gobuster
/contacts.txt (Status: 200)
/credentials.txt (Status: 200)
2020/06/03 15:55:23 Finished

Bingo! Checking out credentials.txt proves to be a lucrative endeavor and we’re gifted the following credentials:

[Internal mail account]

[FTP account]

[Wordpress account]

2 of the 3 look useful, but the only standout one is the ftpuser account since we KNOW that the box is running FTP already. Let’s try the accounts on the FTP service:

FTP login success!

Great success! Start listing and downloading all of the files in the FTP directories we have access to - you never know what could be useful. We download all of the files - in this case dump.sql and html.tar.gz - and start with the tarball. De-compress it by running tar xzvf html.tar.gz and then check out the files inside. On doing so we notice a “utility-scripts” directory that wasn’t there before…

Rabbit hole alert! Be careful here - it’s easy to go into the *.php files under the utility-scripts and start trying different things based on them. Remember it’s important to take a step back and confirm that what’s on the production site matches what we’ve found - some files may be missing or unused in production that were left in for testing.

With that warning out of the way, let’s get to it - running yet another gobuster against /utility-scripts nets us the following (note: you may have to change up which wordlist you use on this one):

Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
[+] Url:            http://admirer.htb/utility-scripts/
[+] Threads:        30
[+] Wordlist:       /usr/share/wordlists/dirb/big.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Extensions:     txt,php,py,html
[+] Timeout:        10s
2020/07/09 23:39:57 Starting gobuster
/.htpasswd (Status: 403)
/.htpasswd.php (Status: 403)
/ (Status: 403)
/.htpasswd.html (Status: 403)
/.htpasswd.txt (Status: 403)
/.htaccess (Status: 403)
/.htaccess.php (Status: 403)
/ (Status: 403)
/.htaccess.html (Status: 403)
/.htaccess.txt (Status: 403)
/adminer.php (Status: 200)
/info.php (Status: 200)
/phptest.php (Status: 200)
2020/07/09 23:43:27 Finished

Interesting - adminer.php DEFINITELY wasn’t in that FTP dump that we found earlier. Let’s check it out.

Adminer login page

It appears to be an Adminer login page to help manage SQL servers. This could be really interesting - at a minimum, we have the ability to possibly log in to local databases. However, research on Google shows us that there was an interesting article from Foregenix that showed a vulnerability in how Adminer handles connections. Specifically, it allows attackers to specify that the server connects to their attacker-controlled database instead of the local database. In order to fully exploit this, we need a few things:

  • A SQL server running on our box
  • Firewall rules to allow traffic to 3306
  • A username and password to connect with
  • Databases, tables, and columns created

This is going to involve a small amount of SQL server administration skills! Don’t worry if you’ve never done this, though - I have you covered. Let’s start with configuring MySQL on our attacker box.

  1. sudo service mysql start
  2. sudo mysql_secure_installation
    1. Change the root password - y
    2. Remove anonymous users - y
    3. Disallow remote root login
    4. Remove test databases - y
    5. Reload the privilege tables - y
  3. sudo mysql -u root -p
    1. Enter your newly-created root password from 2.1
  4. NOTE: For the following steps, make sure to include the “;” - SQL needs this in order to terminate the command!
  5. GRANT ALL PRIVILEGES ON . TO ‘username’@’’ IDENTIFIED BY ‘password’;
    1. IMPORTANT: Make sure to change username and password to the actual values that you want to use for the account!
  6. CREATE DATABASE hacker; (This creates a database named “hacker” - can be changed to whatever you wish!)
  7. USE hacker; (This selects the “hacker” database for working in)
  8. CREATE TABLE phpindex (line VARCHAR(300)); (This creates a table in the current database - “hacker”)
  9. Edit the MySQL configuration file to allow remote connections
  10. sudo service mysql restart

Whew! After all that, we finally have a SQL server that’s open and accepting connections externally. Now that you have that, make sure to edit any necessary firewall rules in your firewall of choice to allow 3306 traffic inbound to the server - ideally scoped just to I’ll leave this section to you, since configurations and options may vary.

Finally - we’re at the section where we hack things! If you follow the Foregenix article, it should be relatively straightforward to get access. Start with filling out the connection details as they correspond to your MySQL instance:

Adminer exploit details

Once we hit connect, if everything went according to plan we should drop into the interface. As with the video we’re going to use the load data local technique, but we won’t target the same file as in the Foregenix disclosure; instead, we’ll target ../index.php to see what’s going on with the website. Once we do that, our DB has the contents of index.php loaded into its column so we have to click select phpindex on the left side - it’ll execute the SQL statement for us to display the contents. Scanning through the source code gives us this gem:

index.php source code hardcoded credentials

We’re almost there - now that we have these credentials, what can we do with them? Think back to our initial enumeration - let’s try them on the final port (hint: it’s SSH). Once we do, we’re granted a shell as Waldo and we’re off to the races once more.


Okay so we’re Waldo now, what can we do? I always like to start Linux hosts by checking if my user has any special access with a sudo -l - in this case we’ve hit paydirt. It returns that we have the following access to a script called /opt/scripts/

waldo's sudo rights

Something seems…off about that. It’s definitely off from standard output for sudo -l. If you do some investigation, it’s apparent that the SETENV portion of the output shows that the process inherits whatever environment from the user that started it. This will come in handy later; for now let’s check out If we cat the file out, we’ll see that for the most part it’s just a regular bash script…except there’s a part that calls another Python script for the web backup. Let’s cat that out.

Web backup script

So let’s list out what we’ve got so far:

  • We have sudo rights to run /opt/scripts/
  • calls
  • Whatever is passed in for environment, the sudo will keep

That last line is key - check the man pages for sudo and you’ll discover this:

sudo man page entry

So this means that we can throw in a sudo -E <command> and it will carry our environment variables with it. So how can we use this exactly? Well, there’s a privilege escalation vector involving Python library hijacking - but this one’s a bit different than rastating’s article due to us not having write access to ANY of the directories that Python looks in. However, not to fear - PYTHONPATH is here! By combining the usage of PYTHONPATH with our ability to keep environment variables intact with sudo -E, we can create a Python path hijack that works in any directory. All that’s left is writing a module for exploitation…

Python module for privilege escalation

Once we’ve written this file and transferred it to the victim, we can run sudo -E PYTHONPATH=$(pwd) /opt/scripts/ and follow the prompts to hit the script. Once it hits that script and tries to read the old call to make_archive(), it will look inside our malicious and find the function we created, run the code in there, and return a reverse shell to our waiting netcat listener.

Our glorious reverse shell root!


So here’s the breakdown. TL;DR is that we:

  • Enumerated the web service for credentials
  • Used FTP to find a sensitive directory that we didn’t know about in a backup
  • Enumerated THAT new directory via the web port
  • Did a small bit of SQL server administration
  • Used a vulnerability in Adminer to connect to our local database and get the contents of index.php
  • Used the source code of index.php to find credentials
  • Re-used the credentials to login as waldo on SSH
  • Used a modified version of Python path hijacking to privilege escalation

Overall I really enjoyed this box and spent WAY more time than I should have on the Python path hijacking - hats off to mrb3n for teaching me a new way to do path hijacking! As always, remember to respect the box creators for their hard work!

- sp1icer


DigitalOcean LAMP Stack Tutorial -
MySQL Add User -
MySQL Remote Connection -
Foregenix Adminer exploit -
Rastating Python Path Hijacking -
Medium Python Path Hijacking -