VAITP Dataset

← Back to the dataset

CVE-2026-40071

Weak permissions in pyLoad's WebUI allow for privilege escalation.

  • CVSS 5.4
  • CWE-863
  • Authentication, Authorization, and Session Management
  • Remote

pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev97, the /json/package_order, /json/link_order, and /json/abort_link WebUI JSON endpoints enforce weaker permissions than the core API methods they invoke. This allows authenticated low-privileged users to execute MODIFY operations that should be denied by pyLoad's own permission model. This vulnerability is fixed in 0.5.0b3.dev97.

CVSS base score
5.4
Published
2026-04-09
OWASP
A01 Broken Access Control
Orthogonal defect classification
Checking
Code defect classification
Incorrect Check
Category
Authentication, Authorization, and Session Management
Subcategory
Poorly Designed Access Controls
Accessibility scope
Remote
Impact
Privilege Escalation
Affected component
pyLoad
Fixed by upgrading
Yes

Solution

Upgrade pyLoad to version 0.5.0b3.dev97 or later.

Vulnerable code sample

import functools

# --- Mock User and Global State ---
# This simulates a global 'current_user' object, as you'd find in a web framework session.
class User:
    def __init__(self, username, permissions):
        self.username = username
        self.permissions = set(permissions)

    def has_permission(self, permission):
        return permission in self.permissions

    def is_authenticated(self):
        return True

class AnonymousUser:
    def is_authenticated(self):
        return False

# Global state to hold the currently logged-in user
current_user = AnonymousUser()


# --- Mock Permission System Decorators ---
# A decorator to check if a user is logged in.
def login_required(f):
    @functools.wraps(f)
    def decorated_function(*args, **kwargs):
        if not current_user.is_authenticated():
            raise PermissionError("Authentication required.")
        return f(*args, **kwargs)
    return decorated_function

# A decorator to check if the user has a specific permission level.
def permission_required(permission):
    def decorator(f):
        @functools.wraps(f)
        def decorated_function(*args, **kwargs):
            if not current_user.has_permission(permission):
                raise PermissionError(f"'{permission}' permission denied for user '{current_user.username}'.")
            return f(*args, **kwargs)
        return decorator
    return login_required(decorator) # Permission check implies login is required


# --- Mock Core API Layer ---
# This simulates pyLoad's core backend logic.
# The key assumption for the vulnerability is that the core methods
# might trust the web layer (the caller) to have already performed authorization.
class CoreAPI:
    """
    Represents the core business logic of the application.
    Notice that the methods themselves do not enforce permissions,
    as they expect the calling web endpoint to handle it.
    """
    def reorder_package(self, package_id, new_position):
        """Core logic to change the order of a download package."""
        print(f"[CORE] User '{current_user.username}' is reordering package '{package_id}' to position {new_position}.")
        # (Imagine database/state modification logic here)
        print(f"[CORE] >> SUCCESS: Package '{package_id}' moved.")
        return {"success": True}

    def abort_link(self, link_id):
        """Core logic to abort a download link."""
        print(f"[CORE] User '{current_user.username}' is aborting link '{link_id}'.")
        # (Imagine logic to stop a download process here)
        print(f"[CORE] >> SUCCESS: Link '{link_id}' aborted.")
        return {"success": True}
        
    @permission_required('MODIFY') # For contrast, a correctly secured core method
    def delete_package(self, package_id):
        """Core logic to delete a package. This method is correctly secured."""
        print(f"[CORE] User '{current_user.username}' is DELETING package '{package_id}'.")
        # (Imagine database deletion logic here)
        print(f"[CORE] >> SUCCESS: Package '{package_id}' deleted.")
        return {"success": True}


# --- Mock Web UI Layer (Vulnerable Implementation) ---
# This simulates the web endpoints in app.py before the fix.
# It exposes core functionality to the user via JSON APIs.

core_api = CoreAPI()

# VULNERABLE ENDPOINT 1: /json/package_order
# The vulnerability is here: The endpoint only checks for authentication (`login_required`)
# but NOT for the 'MODIFY' permission. It then calls the core API method.
@login_required
def json_package_order(package_id, new_position):
    """
    Web endpoint to reorder packages.
    VULNERABILITY: Only checks if the user is logged in, not if they have MODIFY rights.
    """
    print(f"\n[WEB] Received request for /json/package_order from user '{current_user.username}'")
    # This call should be protected by a permission check, but it is not.
    return core_api.reorder_package(package_id, new_position)


# VULNERABLE ENDPOINT 2: /json/abort_link
# Another vulnerable endpoint with the same pattern.
@login_required
def json_abort_link(link_id):
    """
    Web endpoint to abort a link.
    VULNERABILITY: Only checks for authentication, not for MODIFY rights.
    """
    print(f"\n[WEB] Received request for /json/abort_link from user '{current_user.username}'")
    # This call is also not protected by a permission check.
    return core_api.abort_link(link_id)


# SECURE ENDPOINT (for comparison)
# This is how the endpoints *should* have been implemented.
@permission_required('MODIFY')
def json_delete_package(package_id):
    """
    Web endpoint to delete a package.
    CORRECT IMPLEMENTATION: Checks for both login and 'MODIFY' permission.
    """
    print(f"\n[WEB] Received request for /json/delete_package from user '{current_user.username}'")
    # The permission_required decorator will prevent this from being called by a low-privilege user
    return core_api.delete_package(package_id)


if __name__ == '__main__':
    # 1. Define users with different permission levels
    # The 'viewer' user can log in but should not be able to change anything.
    viewer_user = User("viewer", permissions=["VIEW"])
    
    # 2. Simulate the low-privileged 'viewer' user logging in
    print(f"--- Simulating login for low-privileged user: '{viewer_user.username}' ---")
    current_user = viewer_user

    # 3. DEMONSTRATE THE VULNERABILITY
    # The 'viewer' user attempts to call the vulnerable endpoint.
    # This succeeds because the endpoint is missing the permission check.
    print("\n--- ATTACK: Low-privileged user calls vulnerable /json/package_order endpoint ---")
    try:
        result = json_package_order(package_id=123, new_position=1)
        print(f"[WEB] >> Response: {result}. ATTACK SUCCEEDED.")
    except PermissionError as e:
        print(f"[WEB] >> Response: {e}. Attack failed.")

    # Another example of the vulnerability
    print("\n--- ATTACK: Low-privileged user calls vulnerable /json/abort_link endpoint ---")
    try:
        result = json_abort_link(link_id=456)
        print(f"[WEB] >> Response: {result}. ATTACK SUCCEEDED.")
    except PermissionError as e:
        print(f"[WEB] >> Response: {e}. Attack failed.")

    # 4. SHOW A CORRECTLY SECURED ENDPOINT
    # The 'viewer' user attempts to call a properly secured endpoint.
    # This fails as expected because the user lacks the 'MODIFY' permission.
    print("\n--- CONTRAST: Low-privileged user calls secure /json/delete_package endpoint ---")
    try:
        result = json_delete_package(package_id=789)
        print(f"[WEB] >> Response: {result}. Attack succeeded (this is a bug!).")
    except PermissionError as e:
        print(f"[WEB] >> Response: {e}. ACCESS DENIED AS EXPECTED.")

Patched code sample

import functools

def require(permission):
    """
    A mock decorator representing the permission check. In the real application,
    this would inspect the user's session and raise an error (e.g., HTTP 403)
    if the required permission is not present.
    """
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # In the actual application, this is where permission enforcement occurs.
            # For this example, we assume the check happens here before proceeding.
            # if not user_has_permission(permission):
            #     raise PermissionError("Forbidden")
            return func(*args, **kwargs)
        return wrapper
    return decorator

def jsonify(func):
    """Mock decorator to simulate JSON conversion."""
    return func

class MockCherryPy:
    """Mock class to simulate the @cherrypy.expose decorator."""
    def expose(self, func):
        return func

cherrypy = MockCherryPy()

class MockPyLoadCore:
    """Mock class to simulate the pyLoad core API."""
    def movePackage(self, pid, newpos):
        pass
    def moveFile(self, fid, newpos):
        pass
    def deleteFiles(self, fids):
        pass

# The code below represents the fixed version of the vulnerable endpoints.
# The vulnerability was the absence of the `@require("MODIFY")` decorator.
# The fix is the addition of this decorator to enforce proper permission checks.

class Json:
    def __init__(self):
        self.pyload = MockPyLoadCore()

    @cherrypy.expose
    @jsonify
    @require("MODIFY")
    def package_order(self, pid, newpos):
        self.pyload.movePackage(int(pid), int(newpos))
        return None

    @cherrypy.expose
    @jsonify
    @require("MODIFY")
    def link_order(self, fid, newpos):
        self.pyload.moveFile(int(fid), int(newpos))
        return None

    @cherrypy.expose
    @jsonify
    @require("MODIFY")
    def abort_link(self, fids):
        fids_list = fids.split(',')
        self.pyload.deleteFiles(map(int, fids_list))
        return None

Payload

{"pids": [2, 1, 3]}

Cite this entry

@misc{vaitp:cve202640071,
  title        = {{Weak permissions in pyLoad's WebUI allow for privilege escalation.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-40071},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-40071/}}
}
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 ::