
Flash CTF - Git Sleuth
Git Sleuth
Overview
Git Sleuth is a misc challenge that hands you a restricted, interactive git shell over a raw TCP connection. Every line you type is executed as git <line>, but only after passing a blacklist of forbidden characters and keywords. The flag is hidden inside a single file that has been committed alongside hundreds of decoys. To win, you have to drive git through the blacklist and read the contents of the one file that matters.
Background
A common but fragile way to "sandbox" user input is to reject a list of known-bad tokens (a blacklist) rather than to allow only a known-good set (a whitelist). Blacklists are brittle because the space of dangerous inputs is almost always larger than the author anticipated: there are many ways to express the same intent, and a single overlooked alias or alternative subcommand defeats the entire filter.
Git is an especially rich target for this mistake. It is a family of dozens of subcommands rather than a single command, each with its own flags and aliases, and several of them can read arbitrary repository content. Block git grep and a player reaches for git show. Block cat and a player uses git ls-tree. The blacklist here even forbids the substring flag, yet none of the commands required to read the flag file contain that word.
Reconnaissance
Connecting to the service drops you into a prompt that collects git commands until you submit an empty line:
================= GitSleuth Quest =================
[?] A mysterious repository holds ancient secrets...
[!] Your mission: Uncover the sacred text within /flag.txt
[*] Use your git-fu wisely, brave adventurer
=================================================
Please enter git commands (Press Enter on an empty line to finish):
Each whitespace-separated token is checked against a long blacklist. The interesting entries are the keyword bans: diff, patch, grep, push, remote, flag, Fake, Flag, For, Testing, plus shell metacharacters like |, $, &, ;, (, ) and even ./. Anything that trips the filter terminates the session.
The prompt only ever runs git, so there is no shell to escape into, and the working directory starts empty. There is a writable /tmp though, and the banner points at /flag.txt.
Exploitation
The blacklist focuses on shell injection and the most obvious "read a file" verbs, but it leaves the building blocks of a normal git workflow untouched. init, add, commit, ls-tree, and show are all allowed, and so is the -C <dir> option that tells git which directory to operate in.
The strategy is to turn /tmp into a git repository and then inspect it. At startup the service seeds /tmp with five hundred decoy .txt files and one copy of the real flag. Every one of them is named after a random md5, the flag file included, so there is nothing to target by name and committing the whole directory is the only way to capture it:
-C /tmp init
-C /tmp add .
-C /tmp commit -m meow
With a commit in place, git ls-tree enumerates the tree. Adding -r -l recurses and prints each blob's size, which is what turns a pile of identical looking filenames into something searchable:
-C /tmp ls-tree -r -l main
Five hundred of the lines look alike, because the decoys all hold the same string and therefore weigh the same. A representative one:
100644 blob 1b9a1c3d5e7f9a1b3c5d7e9f1a3b5c7d9e1f3a5b 30 0f2c9b5d8a4e6f1c3b7d9e2a5c8f1b4d.txt
The single blob whose size differs from that crowd is the flag file. Its name is regenerated on every instance, so it has to be read out of this listing rather than known up front.
None of these commands contain a blacklisted token: ls-tree and show are not on the list, -C is harmless, and an md5 filename has no forbidden substring.
Getting the Flag
The prompt buffers every line until a blank one and only then runs the batch, so the listing and the read cannot share a session. Reconnect and ask for the odd blob by the name the sizes pointed at:
nc <host> <port>
# then type, ending with a blank line:
-C /tmp show <the size outlier from the ls-tree listing>
git show resolves that path against HEAD, and HEAD is the root commit that added the file, so the entire body comes back in the diff:
SkillBit{R3m3mb3r_t0_4lw4ys_3sc4p3_G1t_C0mm4nds}
Key Takeaways
- Blacklisting characters and keywords is a losing game; prefer an explicit allowlist of safe subcommands and arguments.
gitis effectively a file-reading toolkit:git show,git ls-tree, andgit cat-fileall expose repository content without ever invoking a shell.- Hiding a secret among many lookalikes is no protection when an attacker can sort by size or name;
git ls-tree -lturns "needle in a haystack" into a one-line query.