Escaping Restricted Shells
A restricted shell limits what commands a user can execute — either to a specific allowlist of commands, or to commands run from specific directories. Administrators use restricted shells to give users (employees, contractors, external partners) a safe, controlled environment without full system access. Common examples: rbash and the Windows “Restricted-access Shell.”
Restricted Shell Variants
RBASH — Restricted Bourne Shell
A restricted version of Bourne shell (bash). Limits the user’s ability to:
- Change directories
- Set or modify environment variables
- Execute commands in other directories
RKSH — Restricted Korn Shell
A restricted version of the Korn shell (ksh). Limits the user’s ability to:
- Execute commands in other directories
- Create or modify shell functions
- Modify the shell environment
RZSH — Restricted Z Shell
A restricted version of Z shell (zsh) — the most powerful/flexible of the three underlying shells. Limits the user’s ability to:
- Run shell scripts
- Define aliases
- Modify the shell environment
Typical enterprise use case: external partners needing only email/file sharing get rbash; contractors needing database/web server access get rksh (more flexibility, still limited); employees running specific applications/scripts get rzsh (most flexibility, still restricted).
Escape Techniques
Restricted shells enforce their limits by filtering what the user types before execution — not by sandboxing what the underlying shell binary is capable of. Any input path that lets you smuggle a full command past that filter (or reach a shell metacharacter/feature the filter doesn’t know to block) breaks out of the restriction.
Command Injection
If the restricted shell only permits a specific built-in with specific arguments, arbitrary commands can sometimes be embedded within an allowed argument via subshell expansion:
rnemeth@htb[/htb]$ ls -l `pwd`
Here ls -l is the only “allowed” invocation, but backticks force the shell to first execute pwd (unrestricted) and substitute its output as the argument — effectively executing an arbitrary command through an allowed one.
Command Substitution
Same underlying idea as command injection, but framed around the substitution syntax itself: if the shell lets any input reach backtick (` command `) or $(command) substitution, whatever’s inside is evaluated by the full shell, bypassing the restriction — regardless of which “allowed” command it’s nested inside.
Command Chaining
If shell metacharacters like ;, |, &&, or || aren’t stripped/blocked, a permitted command can be chained with an arbitrary one:
rnemeth@htb[/htb]$ allowed_command ; /bin/bash
The restriction typically only validates the first token/command on the line — anything after a metacharacter may run unchecked.
Environment Variables
If the restricted shell relies on an environment variable to determine where/what it can execute (e.g., a restricted PATH, or a variable pointing at an allowed script directory), modifying that variable — where permitted — can redirect execution outside the intended boundary. Common targets: PATH, ENV, BASH_ENV, SHELL.
Shell Functions
If the shell allows defining and calling functions, a function can be defined that wraps or invokes a command the restriction would otherwise block:
rnemeth@htb[/htb]$ function escape() { /bin/bash; }; escape
Since the restriction typically inspects the literal command name being invoked, wrapping the blocked command inside a differently-named function can slip past a naive allowlist/blocklist check.
Key Takeaways
- Restricted shells filter input, not capability — the underlying interpreter (bash/ksh/zsh) is still fully capable; the goal is finding any path that reaches its unrestricted feature set.
- Subshell expansion (backticks,
$()), metacharacters (;,|,&&), environment variables (PATH,ENV,BASH_ENV), and function definitions are the recurring bypass primitives — try each systematically when the built-in filter’s rules are unclear. - The general goal of any escape is to reach a full, unrestricted shell (e.g.,
/bin/bash,/bin/sh) rather than just running one extra unrestricted command.