~$ skillshelf
← OverTheWire Bandit

Bandit 25 → 26: the login shell that only runs more

banditlinuxrestricted-shellvi

bandit26’s login shell isn’t bash. It’s a tiny script that runs more on a text file and exits — so the moment you log in, the file flashes past and the connection closes before you can type a single character. The whole level is keeping that pager alive long enough to climb out of it into a real shell.

the goal

Logging in to bandit26 from bandit25 should be fairly easy… The shell for user bandit26 is not /bin/bash, but something else. Find out what it is, how it works and how to break out of it.

Three questions, in order: what the shell is, how it works, how to break it. You can answer the first two from bandit25 without ever logging in as bandit26.

the approach

recon from bandit25. A user’s login shell is the last field of their line in /etc/passwd, which anyone can read:

grep bandit26 /etc/passwd
# bandit26:x:11026:11026:bandit level 26:/home/bandit26:/usr/bin/showtext

So the shell is /usr/bin/showtext. That path is world-readable too, so read the script itself rather than guessing what it does:

cat /usr/bin/showtext
#!/bin/sh
export TERM=linux
exec more ~/text.txt
exit 0

That four-line script explains everything. exec more replaces the shell with more, so when more exits, the login session exits with it — there’s no shell underneath to fall back to. And more only pauses when the file is too big to fit on screen. If it fits, more prints it and quits instantly, which is exactly why logging in dumps the banner and drops you. The connection isn’t broken; more just finished.

keep the pager alive. The one thing in “does it fit on screen” that I control is the screen. Shrink the terminal until text.txt can’t fit, and more has to stop and wait. I did it with tmux — split the window down to a pane only a few rows tall (resizing a normal terminal window works the same) — then connected from inside that short pane with the key:

ssh -i bandit26.sshkey -p 2220 bandit26@bandit.labs.overthewire.org

This time more couldn’t fit the file, so instead of quitting it sat at a --More-- prompt, waiting. That prompt is the foothold.

from the pager into an editor. more has its own commands at that prompt — and one of them, v, opens the current file in an editor (vi). That gets me out of the pager and into a program that can run shell commands.

Then come the loops. Everything that should hand you a shell hands you showtext instead:

  • :shell inside vi drops you straight back to --More--. vi’s shell option defaults to $SHELL, and bandit26’s $SHELL is showtext — so “give me a shell” gives you the exact shell you’re trying to escape.
  • Setting the shell and then quitting vi with :q! to run it throws the setting away and lands you back in more. That option lives only inside the running editor.
  • The pager’s own shell-out, ! at the --More-- prompt, loops the same way: more runs ! commands through $SHELL too.

One more worth knowing: :id in vi returns E492: Not an editor command: id. id is a shell command, and vi isn’t a shell yet.

The fix is to do it all in one vi session, in order, never quitting in between:

:set shell=/bin/bash
:shell

:set shell=/bin/bash points vi’s shell option at a real shell instead of showtext; :shell then spawns that. No loop, no quitting.

id
uid=11026(bandit26) gid=11026(bandit26) groups=11026(bandit26)

A real bash prompt as bandit26. That’s the break-out. (The bandit26 password lives in /etc/bandit_pass/bandit26 and is redacted here.)

the takeaway

Two bricks, and the second is the real one.

A pager only pauses when the content can’t fit the screen — and the screen is yours to shrink. A login shell that just execs more looks unbreakable because it exits before you can act. It isn’t: make the terminal short enough that the pager has to wait, and a “read and quit” program becomes an interactive foothold.

Every shell-escape defaults to the shell you’re already in. vi’s :shell, more’s !, and friends all spawn $SHELL unless told otherwise. When $SHELL is the very thing you’re trapped inside, the escape just loops you home. So override it first — :set shell=/bin/bash, then :shell — and remember it’s per-session state: quit the editor and the override dies with it. Set it, use it, don’t leave.