CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-113721

CVE-2026-113721: Unsafe Link Resolution in virtualenv File Generation

Amit Schendel
Amit Schendel
Senior Security Researcher

Oct 23, 2026·5 min read·3 visits

Executive Summary (TL;DR)

virtualenv prior to 21.14.9 follows dangling symlinks during environment setup, allowing local attackers to write files outside the target directory.

In Python virtualenv prior to version 21.14.9, activation script generators and configuration writers use Path.exists() before writing files. Because Path.exists() resolves symbolic links, it returns False for dangling symlinks. Consequently, virtualenv skips unlinking pre-planted symbolic links or hard links, writing data directly to target locations outside the intended virtual environment directory.

Vulnerability Overview

The Python Package Index (virtualenv) tool creates isolated Python execution environments. When constructing a target environment, virtualenv writes activation scripts (activate, activate.ps1, activate.csh), seed launchers, import hooks (_virtualenv.pth), and configuration files (pyvenv.cfg). During file generation, affected versions evaluate destination file paths using pathlib.Path.exists() before overwriting existing files.

Because pathlib.Path.exists() traverses symbolic links, it resolves to False if a target file pointed to by a symbolic link does not exist. This creates a Link Following vulnerability (CWE-59). When a dangling symbolic link exists in the target directory, virtualenv treats the destination as non-existent and skips the step intended to remove pre-existing files.

The attack surface requires local directory access where an environment creation process occurs. In shared build servers, multi-tenant developer systems, or CI/CD pipelines operating in shared directory spaces (e.g., /tmp or persistent scratch spaces), standard low-privileged users can pre-stage symbolic links or hard links in anticipation of automated virtualenv creation.

Root Cause Analysis

The fundamental flaw resides in the difference between link existence and link target existence in Python standard library path handling. In src/virtualenv/activation/via_template.py and related configuration components, file creation logic was implemented using standard existence checks prior to writing content.

When Path.exists() is called on a symbolic link, Python's pathlib module attempts to resolve the underlying destination path via stat(). If the destination target does not exist on the file system, the operation fails and Path.exists() returns False. The logic in virtualenv interpreted this boolean response as an indication that no object occupied the destination file path.

As a consequence of this false negative, virtualenv bypassed its internal dest.unlink() procedure. Subsequent calls to dest.write_bytes() or file stream open operations opened the existing file path. The operating system resolved the symbolic link and opened the target destination file outside the virtual environment root, creating or overwriting the file with the invoking user's file permissions.

Code Analysis & Patch Assessment

In affected versions of virtualenv, template generation methods used vulnerable control flow when writing activation files:

# Vulnerable logic in virtualenv/activation/via_template.py
dest = to_folder / self.as_name(template)
if dest.exists():
    dest.unlink()
dest.write_bytes(text.encode(encoding))

If dest is a dangling symlink, dest.exists() evaluates to False, skipping dest.unlink(). The execution proceeds directly to dest.write_bytes(), which follows the link to the external path.

The upstream fix in commit d25f28699eefb1fc34a251e5adf2b5de844c36cd replaces direct write_bytes() calls with a dedicated helper virtualenv.util.path.write_file and introduces strict path boundary validation:

# Patched helper in src/virtualenv/util/path/_sync.py
def write_file(path: Path, data: bytes) -> None:
    # Check for symbolic links explicitly without resolving targets
    if path.is_symlink() or path.exists():
        path.unlink()
    # Exclusive creation flag 'x' prevents opening existing target files
    with path.open("xb") as stream:
        stream.write(data)

Additionally, the patch introduces ensure_within(root, path) to verify intermediate directories for Windows NTFS junction points and symbolic link components. In standard library venv creation routines, virtualenv explicitly checks and removes symbolic links or multi-link hard files (st_nlink > 1) before invocation.

Exploitation Mechanics

To exploit this vulnerability, an attacker requires local write permissions to the directory where another user or system process intends to create a virtual environment. The attacker pre-populates target file paths with symbolic links pointing to restricted destinations owned by or writeable by the victim user.

For example, an attacker pre-creates the path /tmp/shared_env/bin/activate as a dangling symbolic link pointing to /home/victim/.bashrc. When the victim user runs virtualenv /tmp/shared_env, virtualenv evaluates Path.exists() on /tmp/shared_env/bin/activate.

Because /home/victim/.bashrc may not exist or fails target checks, or in cases where configuration targets like pyvenv.cfg point to custom target files, virtualenv bypasses deletion and opens the path. The shell activation script data is written into the victim file location. The impact is limited to file creation or modification with the content generated by virtualenv.

Impact & Security Risks

The CVSS v3.1 base score for CVE-2026-113721 is 4.5 (Medium), with vector CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L. Attack complexity is rated high because successful exploitation relies on race conditions or specific local directory staging where the target path is predictable and writeable by the attacker.

Primary impact involves arbitrary file creation and content modification under the privilege context of the user executing virtualenv. An attacker cannot write arbitrary payloads, as the injected content is constrained to valid configuration values (pyvenv.cfg) or activation script text generated by virtualenv.

However, corrupting environment configuration files or overwriting key user configuration files can result in localized denial of service or execution redirection if an overwritten file is subsequently executed or sourced by the target user.

Remediation & Defense Strategy

Maintainers resolved this vulnerability in virtualenv version 21.14.9. System administrators and developers should update all environment installations across build tools, CI/CD runners, and active virtual environments.

To update virtualenv using pip, execute:

pip install --upgrade "virtualenv>=21.14.9"

Where immediate updates cannot be deployed, restrict directory permissions on parent directories used for environment creation. Ensure temporary build spaces use private directories (chmod 700) rather than shared, world-writable directories.

Official Patches

PyPAUpstream commit containing the fix for unsafe link handling
PyPAGitHub Security Advisory GHSA-558c-9hv2-4prj

Fix Analysis (1)

Technical Appendix

CVSS Score
4.5/ 10
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L
EPSS Probability
0.12%
Top 98% most exploited

Affected Systems

virtualenv Python package (< 21.14.9)

Affected Versions Detail

Product
Affected Versions
Fixed Version
virtualenv
PyPA
< 21.14.921.14.9
AttributeDetail
CWE IDCWE-59
Attack VectorLocal
CVSS Score4.5 (Medium)
EPSS Score0.00117 (1.57th percentile)
ImpactArbitrary File Creation / Overwrite
Exploit StatusNo Active Exploitation
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1222.002File and Directory Permissions Modification: Linux and Mac File and Directory Permissions Modification
Defense Evasion
T1036Masquerading
Defense Evasion
CWE-59
Improper Link Resolution Before File Access ('Link Following')

The software attempts to access a file based on a filename, but it does not properly resolve symbolic links or hard links, allowing an attacker to traverse to unintended locations.

Vulnerability Timeline

Fix commit d25f28699eefb1fc34a251e5adf2b5de844c36cd merged in PR #3431
2026-10-17
Security advisory GHSA-558c-9hv2-4prj published and virtualenv 21.14.9 released
2026-10-20
CVE-2026-113721 assigned
2026-10-20
NVD metadata updated with CVSS v3.1 vector
2026-10-22

References & Sources

  • [1]GitHub Security Advisory GHSA-558c-9hv2-4prj
  • [2]Pull Request #3431: Safe File Generation
  • [3]Fix Commit d25f28699eefb1fc34a251e5adf2b5de844c36cd
  • [4]virtualenv Release 21.14.9
  • [5]NVD Vulnerability Detail CVE-2026-113721
  • [6]CVE Record CVE-2026-113721

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•25 minutes ago•GHSA-78G4-XQWV-Q3G8
7.5

GHSA-78G4-XQWV-Q3G8: Heap-Based Buffer Overflow in Pillow (libopenjp2)

A heap-based buffer overflow vulnerability in Pillow PyPI binary wheels (versions 11.1.0 to 12.x) allows remote code execution or application crash via crafted JPEG 2000 files processed by the bundled libopenjp2 library.

Alon Barad
Alon Barad
2 views•4 min read
•about 1 hour ago•CVE-2026-109618
9.8

CVE-2026-109618: Remote Code Execution in PHP Core via Out-of-Memory longjmp() Uninitialized Memory Access

CVE-2026-109618 (dubbed PHP2Shell) is a critical vulnerability in the PHP engine core resulting from improper out-of-memory exception handling via longjmp(). When request allocation fails mid-processing, non-local control flow bypasses cleanup procedures, leaving memory structures uninitialized or in a use-after-free state during request shutdown.

Amit Schendel
Amit Schendel
9 views•5 min read
•about 1 hour ago•CVE-2026-109619
9.2

CVE-2026-109619: Remote Code Execution in PHP Core via Stale Global State Memory Corruption (PHP2Shell)

CVE-2026-109619 (PHP2Shell) is a critical Use-After-Free vulnerability in the core PHP engine caused by improper global state cleanup during out-of-memory exception handling across persistent worker requests.

Alon Barad
Alon Barad
10 views•6 min read
•9 days ago•CVE-2026-87697
8.8

CVE-2026-87697: Untrusted Deserialization in .NET PresentationCore Clipboard Handling

A remote code execution vulnerability exists in Microsoft .NET due to untrusted data deserialization within the PresentationCore component. The flaw stems from implicit auto-conversion flags during clipboard data retrieval operations.

Alon Barad
Alon Barad
31 views•5 min read
•10 days ago•CVE-2026-70007
8.8

CVE-2026-70007: Out-of-Bounds Write Vulnerability in .NET Framework and WPF Subsystem

An out-of-bounds write vulnerability in Microsoft .NET Framework, .NET runtime, and Visual Studio allows unauthenticated remote attackers to execute arbitrary code via crafted pixel-shader bytecode or auto-converted clipboard payloads.

Amit Schendel
Amit Schendel
19 views•6 min read
•10 days ago•CVE-2026-70186
7.5

CVE-2026-70186: ASP.NET Core Denial of Service Vulnerability via Framework Deadlock

ASP.NET Core contains a thread deadlock vulnerability (CWE-833) that allows an unauthenticated remote attacker to cause a denial of service over the network by exhausting server thread pool resources.

Amit Schendel
Amit Schendel
18 views•5 min read