This wiki is automatically published from ohmyzsh/wiki. To edit this page, go to ohmyzsh/wiki, make your changes and submit a Pull Request.
Oh My Zsh runs directly in people's environments, and there's currently no alternative. This is why it is paramount that our code is vetted and reviewed according to security best practices, specifically targeting Zsh.
Follow these practices when contributing code that runs in users' shells:
/tmp).[!NOTE] Here, we use
read somethingas a command to symbolize untrusted input. In real world scenarios, untrusted input usually comes from some other place.
Past vulnerabilities:
eval with untrusted inputInsecure pattern:
read filename # User input: "file; echo pwned"
eval "stat $filename" # Executes: stat file; echo pwned
This pattern is often seen in more complex code where the command is determined dynamically:
somefunction() {
local arg="$1" filename="$2"
local cmd=""
case "$arg" in
-s) cmd="stat" ;;
-l) cmd="ls" ;;
esac
eval "$cmd $filename" # VULNERABLE
}
# Attack example
somefunction -s "file; rm -rf $HOME"
Better pattern:
somefunction() {
local arg="$1" filename="$2"
local cmd=""
case "$arg" in
-s) cmd="stat" ;;
-l) cmd="ls" ;;
esac
# SECURE: Zsh handles argument parsing safely
$cmd "$filename"
}
The shell correctly parses each parameter as part of the same command. Even if $filename contains ;, it will not be parsed as the beginning of a new command.
What's preventing injection are not the quotes around $filename, but the fact that we are passing each argument separately
in the command. This way, zsh knows exactly what each argument is supposed to be in the parsing phase, before any variable expansion happens.
Insecure pattern:
This almost never makes sense:
read var # User input: "file; echo pwned"
eval "foo=$var" # Executes: foo=file; echo pwned
But this one is more common:
read var # User input: "echo pwned; myvariable"
eval "$var=helloworld" # Executes: echo pwned; myvariable=helloworld
Better pattern:
Use typeset (or declare, or any of the other built-in variable assignment commands):
read var
typeset "$var=helloworld" # SECURE: throws error instead of executing
This approach throws an error on malicious input rather than executing arbitrary commands:
➜ var="file; echo pwned"
➜ typeset "$var=helloworld"
➜ typeset -p myvariable
typeset myvariable=helloworld
➜ var="echo pwned; myvariable"
➜ typeset "$var=helloworld"
typeset: not valid in this context: echo pwned; myvariable
Past vulnerabilities:
print -P with untrusted inputInsecure pattern:
setopt promptsubst
dailyquote="$(get_daily_quote_from_the_internet)"
print -P "%F{yellow}%B${dailyquote}%b%f" # VULNERABLE
If dailyquote contains $() or backticks (`), zsh will execute those commands.
Better pattern:
# Disable prompt substitution for untrusted content
setopt localoptions nopromptsubst
dailyquote="$(get_daily_quote_from_the_internet)"
print -P "%F{yellow}%B${dailyquote}%b%f"
# Use `print` without `-P` to avoid prompt substitution
setopt promptsubst
dailyquote="$(get_daily_quote_from_the_internet)"
print "${fg[yellow]}${dailyquote}${reset_color}"
Past vulnerabilities:
Insecure pattern:
gitbranch() {
command git symbolic-ref --short HEAD
}
PROMPT='myuser:$(gitbranch) %% ' # VULNERABLE
Git branch names may contain % characters. In zsh versions older than 5.8.1 with setopt promptsubst enabled, this is vulnerable to CVE-2021-45444. An attacker could craft a malicious branch name to execute arbitrary commands.
Better pattern:
gitbranch() {
local branch="$(command git symbolic-ref --short HEAD)"
echo -n "${branch//\%/%%}" # Escape all % characters
}
PROMPT='myuser:$(gitbranch) %% ' # SECURE
Functions that output untrusted content for use in $PROMPT or $RPROMPT must:
% characters by replacing them with %%