VAITP Dataset

← Back to the dataset

CVE-2026-45306

pyLoad allows authenticated users to steal session files for account takeover.

  • CVSS 6.5
  • CWE-706
  • Input Validation and Sanitization
  • Remote

pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev100, the fix for CVE-2026-33509 prevents setting storage_folder inside PKGDIR or userdir, but does NOT protect the Flask session directory (/tmp/pyLoad/flask). An authenticated attacker can set storage_folder to the session directory and download session files of other users via /files/get/, leading to account takeover. This vulnerability is fixed in 0.5.0b3.dev100.

CVSS base score
6.5
Published
2026-05-28
OWASP
A01 Broken Access Control
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Path Traversal
Accessibility scope
Remote
Impact
Privilege Escalation
Affected component
pyLoad
Fixed by upgrading
Yes

Solution

Upgrade to pyLoad version 0.5.0b3.dev100 or later.

Vulnerable code sample

import os
import shutil
from flask import Flask, session, request, send_from_directory, jsonify
from flask.sessions import FileSystemSessionInterface

# --- Application Setup ---
# This setup simulates the environment described in the CVE.

# Define key directories. The vulnerability lies in not protecting SESSION_DIR.
PKG_DIR = "/opt/pyload/app"
USER_DIR = "/home/user/.pyload"
SESSION_DIR = "/tmp/pyload/flask"  # The unprotected session directory
DOWNLOAD_DIR = "/home/user/downloads"  # Default safe storage folder

# Create directories for the demo to run
os.makedirs(SESSION_DIR, exist_ok=True)
os.makedirs(DOWNLOAD_DIR, exist_ok=True)

app = Flask(__name__)
# Use a file-based session interface to store session data in files
app.config["SESSION_TYPE"] = "filesystem"
app.config["SESSION_FILE_DIR"] = SESSION_DIR
app.secret_key = os.urandom(24)
app.session_interface = FileSystemSessionInterface(
    app.config["SESSION_FILE_DIR"], threshold=500, mode=384
)


# --- Vulnerable Code ---

# Global configuration state, similar to how pyLoad manages it.
# An attacker's goal is to change 'storage_folder' to SESSION_DIR.
CONFIG = {"storage_folder": DOWNLOAD_DIR}


def is_path_forbidden(path):
    """
    Simulates the incomplete fix for a previous CVE.
    It prevents setting storage_folder to PKGDIR or USERDIR but
    misses the Flask session directory.
    """
    forbidden_paths = [os.path.abspath(PKG_DIR), os.path.abspath(USER_DIR)]
    target_path = os.path.abspath(path)

    for forbidden in forbidden_paths:
        if target_path.startswith(forbidden):
            return True  # Path is forbidden
    return False  # Path is allowed


@app.route("/api/setConfig", methods=["POST"])
def set_config():
    """
    Authenticated endpoint allowing users to change configuration.
    The vulnerability is triggered here by setting 'storage_folder'
    to the session directory path.
    """
    # In a real scenario, this endpoint would require authentication.
    key = request.json.get("key")
    value = request.json.get("value")

    if key == "storage_folder":
        # The flawed security check is called here.
        if is_path_forbidden(value):
            return jsonify({"error": "Path is not allowed"}), 403

        # The check passes for the session directory, and the
        # configuration is updated with the malicious path.
        CONFIG["storage_folder"] = value
        return jsonify(
            {"status": "success", "message": f"storage_folder updated to {value}"}
        )

    return jsonify({"error": "Invalid key"}), 400


@app.route("/files/get/<path:filename>")
def get_file(filename):
    """
    This endpoint serves files from the configured 'storage_folder'.
    It blindly trusts the path in the CONFIG dictionary. If an attacker
    has pointed 'storage_folder' to the session directory, this function
    will serve session files of other users.
    """
    # No validation is performed here; it trusts the global config.
    storage_directory = CONFIG["storage_folder"]

    # The function serves a file from the potentially compromised directory.
    try:
        return send_from_directory(storage_directory, filename, as_attachment=True)
    except FileNotFoundError:
        return "File not found", 404


@app.route("/")
def index():
    """
    A sample route to create a session file for a visitor.
    This simulates a victim user accessing the application.
    The session file contains sensitive data.
    """
    session["user_id"] = "victim_user_123"
    session["role"] = "admin"
    return f"Welcome. A session file has been created for you in {SESSION_DIR}"

Patched code sample

import os
from pathlib import Path

# In a real application, these paths would be determined from configuration.
# We define them here as resolved absolute paths for the demonstration.
PKGDIR = Path("/usr/share/pyload/").resolve()
USERDIR = Path("~/.pyload/").expanduser().resolve()

# The Flask session directory that was unprotected in the vulnerable version.
# The fix involves explicitly identifying and blocking this directory path.
FLASK_SESSION_DIR = Path("/tmp/pyLoad/flask").resolve()


def is_safe_storage_path(new_path_str: str) -> bool:
    """
    Validates if a given path is a safe location for the 'storage_folder'.

    This function represents the fixed logic for CVE-2026-45306. The vulnerable
    version of this check was missing the `FLASK_SESSION_DIR`.
    """
    try:
        # Resolve the new path to an absolute, canonical path to prevent
        # traversal attacks (e.g., using '..' or symbolic links).
        candidate_path = Path(new_path_str).resolve()
    except (TypeError, ValueError):
        # Path is malformed or invalid
        return False

    # The fix is to add the Flask session directory to the set of forbidden paths.
    # The vulnerable code only checked against PKGDIR and USERDIR.
    forbidden_paths = {PKGDIR, USERDIR, FLASK_SESSION_DIR}

    for protected_path in forbidden_paths:
        # Check if the candidate path is the same as a protected path or
        # is a subdirectory of a protected path.
        if candidate_path == protected_path or protected_path in candidate_path.parents:
            return False

    # If the path is not within any forbidden locations, it is considered safe.
    return True

Payload

{
    "section": "download",
    "option": "storage_folder",
    "value": "/tmp/pyLoad/flask"
}

Cite this entry

@misc{vaitp:cve202645306,
  title        = {{pyLoad allows authenticated users to steal session files for account takeover.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-45306},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-45306/}}
}
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 ::