Bandit 22 → 23: the filename you can compute
Same setup as the last level: a job runs from cron as a user I’m not, and dumps a
password into /tmp. The difference is the filename. Last time it was hardcoded,
so once I’d read the script I had the path. This time the script computes the
path from a username, and the one line that would print it gets thrown away. The
level is realising the name isn’t hidden — it’s derived, and I can derive it too.
the goal
A program is running automatically at regular intervals from cron, the time-based job scheduler. Look in
/etc/cron.d/for the configuration and see what command is being executed.
The page also notes the script is meant to be read, and that running it prints debug info. Both turn out to matter.
the approach
The cron config names my target the same way as before:
cat /etc/cron.d/cronjob_bandit23
* * * * * bandit23 /usr/bin/cronjob_bandit23.sh &> /dev/null
/etc/cron.d/ files use the system crontab format, so there’s an extra field
before the command — the user to run as, here bandit23. * * * * * is every
minute, forever. So this runs as bandit23, once a minute. And &> /dev/null on
the end throws away everything the script prints — remember that line.
The script is readable, so I read it:
cat /usr/bin/cronjob_bandit23.sh
#!/bin/bash
myname=$(whoami)
mytarget=$(echo I am user $myname | md5sum | cut -d ' ' -f 1)
echo "Copying passwordfile /etc/bandit_pass/$myname to /tmp/$mytarget"
cat /etc/bandit_pass/$myname > /tmp/$mytarget
So it takes whoever’s running it, hashes the string I am user <name> to get a
filename, echoes where it’s copying to, then copies that user’s password into
/tmp/<hash>. When cron runs it as bandit23, that’s bandit23’s password landing
in a computable spot.
Running it directly is the obvious first move, and it’s instructive rather than useful:
./cronjob_bandit23.sh
Copying passwordfile /etc/bandit_pass/bandit22 to /tmp/8169b67bd894ddbb4412f91573b38db3
That copies bandit22’s password, because $(whoami) resolves to me. Useless as
an answer, but it proves the mechanism: the path is built entirely from the
username.
The next instinct is to become bandit23 so the script copies the right file. Every version of that is a dead end:
- Run it as bandit23. Circular — becoming bandit23 needs bandit23’s password, which is the thing I’m after.
- Fake
whoamiso$mynamesays bandit23. The variable would build the right path, but the last line iscat /etc/bandit_pass/bandit23, and that read is checked against my uid, not against a string in a shell variable:
bash
ls -l /etc/bandit_pass/bandit23
# -r-------- 1 bandit23 bandit23 33 /etc/bandit_pass/bandit23
-r--------, owned by bandit23. bandit22 can’t read it no matter what any
variable claims.
- chgrp/chown the password file, like an earlier level. chgrp needs
ownership, chown needs root; the ls -l above says I’m neither.
All of those are attempts to be the actor. But the actor already acted — bandit23
ran this script under a minute ago, read its own password (which it’s allowed to),
and wrote it to /tmp/<hash>. Nothing needs running. Recompute the name and read
the result.
The name is just an md5 of a fixed string, so I reproduce line 4 with the username pinned to bandit23 — deterministic, same input, same hash anywhere:
echo I am user bandit23 | md5sum | cut -d ' ' -f 1
8ca319486bfbbc3663ea0fbe81326349
That &> /dev/null in the cron line means the script’s own “Copying to…” echo
never reaches me — but it protects nothing, because I just rebuilt the exact same
name from the exact same string. /tmp won’t even let me list it:
ls /tmp
ls: cannot open directory '.': Permission denied
Which doesn’t matter — there’s no need to browse for a file whose name you already hold:
cat /tmp/8ca319486bfbbc3663ea0fbe81326349
<redacted>
the takeaway
Last level’s brick was reading versus running — the job already did the work, go
find what it left. This one adds the twist: a filename computed from known
inputs isn’t a secret. The script hashes a fixed, public string to name its
output, and cron &> /dev/nulls away the one line that would spell that name out
— but none of that hides anything, because the same string through the same
md5sum gives the same name on any box, run by anyone. Throwing away the echo
would only matter if the name couldn’t be recomputed. It can.
And the trap underneath, wearing new hats from the previous level: run-as, fake
whoami, chown the file. Every one of those fails on a permission check that
doesn’t care what a shell variable says. The move is never to become the actor —
it’s to recompute what the actor already produced, and read it.