GHSA-9h9j-4vrj-gf7g: CRLF Injection
Summary
pyvenv.cfg is a line-based format with no escape syntax. PyEnvCfg.write() wrote values verbatim, while PyEnvCfg.readvalues() parses the file with str.splitlines(). A value containing a line boundary therefore became additional configuration lines, and because reading is last-wins, the injected keys replaced any key written earlier in the file.
Impact
The prompt value is the reachable input: it is set by --prompt, by the VIRTUALENVPROMPT environment variable, or from the config file, and write() emits prompt before home. A crafted prompt can therefore set home in the generated pyvenv.cfg:
console $ virtualenv --prompt $'x"\nhome = /attacker/path\nprompt = "z' venv $ grep '^home' venv/pyvenv.cfg home = /attacker/path
home is what tooling reads to locate the base interpreter, so a consumer that trusts it can be pointed elsewhere. implementation, versioninfo, version, executable, command and virtualenv are also written before prompt and can be replaced the same way.
This requires the prompt to come from somewhere other than the person running the command, for example a CI job templating a branch name into it, tooling deriving an environment name from user-supplied data, or an inherited VIRTUALENVPROMPT. Where the operator supplies the prompt directly they already control the command line, and the effect is corruption rather than privilege gain: the value is truncated at the boundary and read back with a dangling quote.
Details
The boundary set is the one str.splitlines() recognizes, which is wider than \n: \r, \v, \f, the file, group and record separators, U+0085, U+2028 and U+2029 were all written through unchanged and all split the line when read back.
Patches
PyEnvCfg.write() now collapses those boundaries to spaces as it serializes each line, so it cannot emit a structurally invalid file regardless of what a caller places in content.
Workarounds
Do not pass externally influenced data as the virtualenv prompt. Strip line boundaries from any value before using it as --prompt or VIRTUALENVPROMPT.
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.7.11 - Compensating control
Do not pass externally influenced data as the virtualenv prompt; strip all line-boundary characters recognized by str.splitlines() before using a value as --prompt or VIRTUALENV_PROMPT.
Event History
Frequently Asked Questions
Who is realistically exposed to this issue?
The relevant exposure is automation or tooling that supplies a virtualenv prompt from data controlled by someone other than the person running the command. Examples include CI jobs that template branch names into the prompt or tooling that derives the prompt from external input.
What does an attacker need to control?
An attacker needs to influence the prompt value and include a line boundary in it. The prompt can be supplied through --prompt, the VIRTUALENV_PROMPT environment variable, or configuration input.
How can I check whether a generated environment was tampered with?
Inspect its pyvenv.cfg for unexpected values in home, prompt, implementation, version_info, version, executable, command, or virtualenv. In particular, verify that home points to the intended base interpreter location.
What can be done if updating is not immediately possible?
Do not pass untrusted or externally derived data into the prompt value. Ensure prompt inputs cannot contain line boundaries before creating the environment.