GHSA-5vjq-rrrf-7h2q: OS Command Injection
Impact
zsh users activate an environment with the bash activate script, which puts the environment name into PS1. Since 21.13.0 the script escapes backslashes, dollar signs and backticks in that name, but only when BASHVERSION is set, so zsh gets the raw value. With the PROMPTSUBST option set, zsh runs parameter expansion, command substitution and arithmetic expansion on PS1 each time it draws the prompt. A name that contains $(...), backticks or ${...} then runs as commands in the user's shell, once per prompt.
The name comes from the environment directory, or from --prompt or VIRTUALENVPROMPT. After you create an environment with:
console $ virtualenv 'x$(touch PWNED)y'
source 'x$(touch PWNED)y/bin/activate' in a zsh session with PROMPTSUBST set creates PWNED, and the prompt shows (xy). Passing that value as --prompt to an environment with a plain name has the same effect. Without PROMPTSUBST, zsh shows the name as text and runs nothing. bash runs nothing either.
A plain zsh starts with PROMPTSUBST off. oh-my-zsh users have it on: oh-my-zsh loads lib/theme-and-appearance.zsh at startup, and that file sets it. An attacker needs to influence the environment path or prompt: a CI job or wrapper tool that passes a branch, project or job name through, or a directory in a cloned repository or unpacked archive. --prompt . takes the current directory's name.
zsh also reads % in PS1 as a prompt escape, so a name with %n showed the user name in place of those characters, though without running commands.
The bug is in releases up to and including 21.14.3. The payload runs on 20.26.6 and 21.14.3, and the reporter confirmed 21.9.1.
Patches
Fixed in 21.14.4. Under zsh, activate now escapes the name the way it does under bash and doubles %, so the example above runs nothing and the prompt shows the name as given. zsh gets the escaping even when PROMPTSUBST is off, because a user can set the option after activation; a name with a backslash, dollar sign or backtick then shows the added backslashes. Fix: https://github.com/pypa/virtualenv/commit/5b915367da82bd024b2a0d28d2f98007034b4aa2
Workarounds
Set VIRTUALENVDISABLEPROMPT=1 before you source activate, which leaves PS1 alone, or run unsetopt promptsubst if your prompt does not need it. Check the name of an environment you did not create before you source its activate.
Credits
Kaya Emre Arikan (@kemrec) reported this issue.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/virtualenvto a version that resolves this vulnerability.Fixed in 21.14.4 - Upgrade
Upgrade
virtualenvto a version that resolves this vulnerability.Fixed in 21.14.4 - Configuration
Set VIRTUAL_ENV_DISABLE_PROMPT=1 before sourcing the activate script to leave PS1 unchanged.
virtualenv zsh activation VIRTUAL_ENV_DISABLE_PROMPT = 1 - Configuration
Run unsetopt prompt_subst if prompt substitution is not needed.
zsh PROMPT_SUBST = off
Event History
Frequently Asked Questions
Who is exposed in practice?
Exposure requires zsh with the PROMPT_SUBST option enabled while activating an environment through its bash activate script. A plain zsh installation has PROMPT_SUBST disabled by default, but oh-my-zsh users have it enabled.
What must an attacker control for exploitation?
An attacker needs to cause a user to activate an environment whose directory name contains zsh expansion syntax, or an environment created with a malicious --prompt value or VIRTUALENV_PROMPT value. The user must source that environment's activate script in the affected zsh configuration.
What can be done if updating is not immediately possible?
Disable PROMPT_SUBST in zsh, or avoid activating environments with untrusted directory names or prompt values. Without PROMPT_SUBST, zsh displays the environment name as text rather than executing its contents.
How can I check for exposure in existing environments?
Review environment directory names and any --prompt or VIRTUALENV_PROMPT values for command substitutions such as $(...), backticks, or parameter-expansion syntax such as ${...}. Also check whether PROMPT_SUBST is enabled in the zsh sessions used to activate those environments.