~$ skillshelf
← OverTheWire Bandit

Bandit 22 → 23: the filename you can compute

banditlinuxcronpermissions

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 whoami so $myname says bandit23. The variable would build the right path, but the last line is cat /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.