Overview
A TCP service, source included, drops you into a shell that only runs git
commands. Somewhere under /tmp sit 501 randomly-named .txt files: 500
contain a copy-pasted fake flag, and exactly one is the real thing, copied
from /flag.txt.
The filter
challenge.py splits each input line on spaces and rejects any token
containing a blocked character or word:
chars: | " ' ; $ \ # * ( ) & ^ @ ! < > % : , ? { } `
words: diff grep flag patch ./ push alias remote
Fake Flag For Testing work update-ref lfs-remote /dev/null
That kills most of the obvious moves in one pass: no grep, no diff, no
* globbing, no : (so git show HEAD:file is dead on arrival), and the
literal word flag blocks any path that mentions /flag.txt directly.
Building a repo out of the haystack
None of these hit the blocklist:
init /tmp
-C /tmp add -A
-C /tmp commit -m x
-C /tmp ls-tree -r HEAD
That initializes a repo over /tmp, stages and commits all 501 files, and
dumps the tree: one line per file, each with its blob SHA.
Finding the odd one out
Locally, hash the known fake flag content the same way git would:
$echo 'SkillBit{This_Is_A_Fake_Flag}' | git hash-object --stdin 937fc2f3a9653a549fe41c35f4e3102c02a3cad9
In the ls-tree output, 500 files share exactly that hash. One file has a
different hash — that's the real flag, and git just told us which one
without us ever reading a single file's contents.
Reading a blob without :
git show HEAD:filename is off the table, but git cat-file -p <hash>
reads any object straight by its SHA and needs no ref:path syntax at all:
-C /tmp cat-file -p 35ab8c9e8787383f759aae2db4ee654c73080548
That prints the flag.
Insight
The filter only ever looks at what you type in, never at what git prints
back out. ls-tree and cat-file -p are both read-only, blob-level
plumbing commands, so nothing about "diff" or "grep" or "flag" needs to
appear in the command line for them to do exactly that job.
Flag
SkillBit{R3m3mb3r_t0_4lw4ys_3sc4p3_G1t_C0mm4nds}