Description
I accidentally wrote the flag down. Good thing I deleted it!
Download the challenge zip, unzip it locally, then change into the drop-in directory.
wget https://artifacts.picoctf.net/c_titan/77/challenge.zip && \
unzip challenge.zip && \
cd drop-in/Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
ls -a in drop-in directory you can see the .git file which allows you to see the commits. Before looking at prior commits in the log there is a file called "message.txt" with the file contents of "TOP SECRET".Step 1
List commitsObservationI noticed the challenge description said the flag was 'deleted' and the drop-in directory contained a .git folder, which suggested the deletion was a git commit and that the flag could be recovered by inspecting the commit history with git log.git log walks the commit chain back from HEAD. The history shows a commit labeled "create flag" with hash 3d5ec8a26ee7b092a1760fea18f384c35e435139.bashgit logcommit 9b21a04c... (HEAD -> main) Author: ctf-author Date: ... delete flag commit 3d5ec8a26ee7b092a1760fea18f384c35e435139 Author: ctf-author Date: ... create flagWhat didn't work first
Tried: Running git status to look for deleted files instead of git log
git status only shows the difference between the working tree and the current HEAD commit - it reports that message.txt does not exist, but gives no information about prior commits. git log is the right tool because it walks the entire commit chain, revealing the 'create flag' commit where the file was introduced before being removed.
Tried: Running git diff HEAD to find the flag content
git diff HEAD compares the working tree against the current commit and produces empty output here because both are in sync after the deletion commit. To see what changed between two commits you need git diff <hash1> <hash2>, or to read the file at a specific historical commit you need git show or git checkout.
Learn more
This challenge hinges on a fundamental misunderstanding many people have: deleting a file in git does not erase it from history. Git is a content-addressable store. Every version of every file ever committed is permanently recorded under
.git/objects/, even after a follow-up commit deletes the file.You don't even have to switch the working tree to read the deleted contents.
git show <hash>:message.txtprints the file contents from any historical commit without changing what's on disk.git checkoutis the heavier hammer, useful when you need multiple files restored together.This is why you should never commit secrets to a repository, even "temporarily." Removing a secret in a follow-up commit does not protect it. The only safe remediation is to rotate the secret and rewrite history with
git filter-repo(then force-push and coordinate with everyone who has a clone).Step 2
Check out the flag commitObservationI noticed git log revealed a commit hash labeled 'create flag' that preceded the deletion commit, which suggested checking out that specific commit would restore message.txt to the working tree and expose the flag.Run git checkout <hash> to change the working tree to that exact commit, then cat message.txt to see the flag. You'll be in detached HEAD; that's fine for reading.bashgit checkout 3d5ec8a26ee7b092a1760fea18f384c35e435139 && \ cat message.txtExpected output
picoCTF{s@n1t1z3_30e86...}What didn't work first
Tried: Using git revert 3d5ec8a26ee7b092a1760fea18f384c35e435139 to recover the file
git revert creates a new commit that undoes the changes of the target commit - in this case it would undo the 'create flag' commit, which means it would delete message.txt again rather than restore it. The correct command is git checkout <hash>, which moves the working tree to the state at that commit so message.txt is present and readable.
Tried: Running git stash pop expecting the deleted file to be stashed
git stash only saves uncommitted working-tree changes; it has nothing to do with committed history. The deletion was committed as a separate commit, so there is nothing in the stash. Only git checkout <hash> or git show <hash>:message.txt can retrieve content that was part of a committed state.
Learn more
git checkout <hash>moves your working tree to the exact state the repository was in at that commit. Files present at that point are restored, and files added later disappear. You end up in "detached HEAD" state, meaning HEAD points directly to a commit rather than a branch name.You can explore freely in this state. To get back to the latest commit on your branch, run
git checkout main(ormaster).Beyond CTFs, this technique drives real debugging. If you know a bug was introduced somewhere in the last 50 commits,
git bisectautomates a binary search through history by checking out midpoints until it isolates the culprit.
Flag
Reveal flag
picoCTF{s@n1t1z3_30e86...}
Key takeaway
How to prevent this
How to prevent this
Deleting a file in a follow-up commit does not remove it from history. The blob stays in .git/objects until pruned.
- Pre-commit secret scanning is the only intervention that prevents the leak. Install
gitleaks,trufflehog, ordetect-secretsas a Git hook plus a CI check. - If a secret slips through, rotate the secret first, then rewrite history with
git filter-repoand force-push. Old clones, mirrors, and forks still have the data; rotation is the only real fix. - Do not store credentials in repos at all. Use a secrets manager (Vercel env vars, AWS Secrets Manager, Doppler, 1Password CLI) and pull at runtime. Then a leak yields nothing useful.