Bandit 29 → 30: the branches you weren't shown
Third git level in a row off an identical level page. The clone works, the README is there, and where the password should be there’s a sentence instead:
- password: <no passwords in production!>
Last level taught me the working tree isn’t everything and sent me into the history. This level punishes exactly that reflex — the history is clean, and what I needed was never hidden, just never listed.
the goal
There is a git repository at
ssh://bandit29-git@localhost/home/bandit29-git/repovia the port2220. The password for the userbandit29-gitis the same as for the userbandit29. From your local machine (not the OverTheWire machine!), clone the repository and find the password for the next level.
Word for word the last two apart from the username. localhost still means
whichever machine runs the command, so the host swap from 27 → 28 still applies.
the approach
reading the file as a sentence
<no passwords in production!> looks like a malfunction, the same way
xxxxxxxxxx did the level before. It isn’t. No command failed and nothing was
suppressed — it’s a sentence a person typed into a README and committed on
purpose.
Read as a sentence, it says something specific. It does not say there is no password. It says the password is not in production — a claim about location, not existence. Someone naming one environment implies there are others.
dead end: the history
Worth checking, since it solved the level before:
git log
commit b607fba0c867d0bbdf4b4a5e62cd04b79c8fea83 (HEAD -> master, origin/master, origin/HEAD)
fix username
commit 84c16f8255d7a07c45b96a1780234ed20f7457b7
initial commit of README.md
Two commits. git show on the tip:
fix username
-- username: bandit29
+- username: bandit30
- password: <no passwords in production!>
The username line, and nothing else. The password line is identical on both sides of the diff, so it was the placeholder in the first commit and the placeholder in the last. The secret was never in this branch’s history at all — a real elimination, and it stops you re-running last level’s technique.
“there’s only one branch”
git branch
* master
One branch, called master — which looks like it kills the “other environments”
idea. It doesn’t. git branch shows local branches, and a fresh clone creates
exactly one of those no matter how many the remote had. Everything else arrives as
a remote-tracking ref, which the default output never mentions.
The evidence is on the first line of that git log:
(HEAD -> master, origin/master, origin/HEAD)
master and origin/master. Two names, listed separately, because they’re two
different refs — mine, and the record of the remote’s. Git wouldn’t print both if
they were the same thing.
the branches
git branch -r
origin/HEAD -> origin/master
origin/dev
origin/master
origin/sploits-dev
dev and sploits-dev, downloaded during the clone, on disk the entire time,
never listed because nobody asked. Nothing was fetched here — this was a display
problem from the start, not a download problem.
dead end: refs aren’t paths
remotes/origin/dev looks like a directory, and isn’t one:
cat remotes/origin/dev
cat: remotes/origin/dev: No such file or directory
git checkout remotes/origin/de
error: pathspec 'remotes/origin/de' did not match any file(s) known to git
It’s a ref name with slashes in it, not a path on disk. And git checkout reads
an argument it can’t resolve as a pathspec, which is why the error talks about
files when what it got was a mistyped branch.
Spelled correctly it resolves:
git checkout remotes/origin/dev
Note: switching to 'remotes/origin/dev'.
You are in 'detached HEAD' state. ...
HEAD is now at 0bf8160 add data needed for development
Detached HEAD sounds like a problem and isn’t one here. Checking out a remote-tracking ref points you at a commit rather than a local branch, so there’s nothing to move if you commit. Reading is all this needs.
the file
ls
code README.md
A code/ directory that wasn’t on master, holding gif2ascii.py — one byte,
empty, a decoy. The README is the one that matters:
cat README.md
## credentials
- username: bandit30
- password: <redacted>
Plain text, no history digging, no diff, on a branch that was in the clone the whole time.
the takeaway
git branch lists local branches, and cloning creates exactly one. Everything
else the remote had comes down with the clone and stays invisible until you ask
with -r or -a. Concluding “this repo only has one branch” from bare
git branch is concluding something the command never told you.
Same shape three times across two levels, and this is the pair’s real lesson:
- the working tree isn’t everything the repo has → the history
- the history of one branch isn’t everything the repo has → other branches
- and what a command prints by default isn’t everything it knows
None of those were missing data. All three were on disk already, and the fix each time was a flag or a different subcommand, never another fetch.
Two smaller bricks:
Read the file, don’t diagnose it. <no passwords in production!> and
xxxxxxxxxx both read like malfunctions and were both messages. A committed
string is something a human chose to write, and “not in production” is a person
telling you which environment they didn’t put it in. That’s a lead.
A ref is not a path. remotes/origin/dev has slashes and looks like a
directory, but it names a reference inside git’s ref namespace — no such directory
exists in the working tree. When git checkout says pathspec did not match any
files, it usually means it couldn’t resolve what you typed as a ref and fell back
to reading it as a filename. Nine times out of ten that’s a typo in a branch name,
not a missing file.