Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

🏠 Back to Blog

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.