CVE-2026-41133
pyLoad caches permissions, letting users keep revoked privileges in a session.
- CVSS 8.8
- CWE-613
- Authentication, Authorization, and Session Management
- Remote
pyLoad is a free and open-source download manager written in Python. Versions up to and including 0.5.0b3.dev97 cache `role` and `permission` in the session at login and continues to authorize requests using these cached values, even after an admin changes the user's role/permissions in the database. As a result, an already logged-in user can keep old (revoked) privileges until logout/session expiry, enabling continued privileged actions. This is a core authorization/session-consistency issue and is not resolved by toggling an optional security feature. Commit e95804fb0d06cbb07d2ba380fc494d9ff89b68c1 contains a fix for the issue.
- CWE
- CWE-613
- CVSS base score
- 8.8
- Published
- 2026-04-22
- OWASP
- A01 Broken Access Control
- Orthogonal defect classification
- Timing/Serialization
- Code defect classification
- Incorrect Check
- Category
- Authentication, Authorization, and Session Management
- Subcategory
- Session Management Issues
- Accessibility scope
- Remote
- Impact
- Privilege Escalation
- Affected component
- pyLoad
Solution
As no numbered release contains the fix, update to the latest code from the `stable` branch which includes commit `e95804fb0d06cbb07d2ba380fc494d9ff89b68c1`.
Vulnerable code sample
from functools import wraps
from flask import Flask, request, jsonify, session
# This is a simplified example using Flask to demonstrate the logical flaw.
# It does not use pyLoad's actual source code but mimics the vulnerable pattern.
app = Flask(__name__)
app.secret_key = 'vulnerable_secret_key' # Required for session management
# Simulate a user database
USERS_DB = {
'test_admin': {'password': 'password', 'role': 'admin', 'permissions': ['all']}
}
def requires_permission(permission):
"""Decorator to check permissions based on the session cache."""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# VULNERABILITY: Permissions are checked from the session cache.
# This cache is only created at login and is not updated if the
# user's permissions are changed in the database mid-session.
if permission not in session.get('permissions', []):
return jsonify({'error': 'Permission denied'}), 403
return f(*args, **kwargs)
return decorator
return decorator
@app.route('/login', methods=['POST'])
def login():
"""Logs a user in and caches their role and permissions in the session."""
data = request.json
username = data.get('username')
password = data.get('password')
user = USERS_DB.get(username)
if user and user['password'] == password:
# User's role and permissions are read from the DB and stored in the session.
# This is the only time they are fetched from the source of truth (the DB).
session['username'] = username
session['role'] = user['role']
session['permissions'] = user['permissions']
return jsonify({
'message': f'Login successful for {username}.',
'session_role': session['role'],
'session_permissions': session['permissions']
})
return jsonify({'error': 'Invalid credentials'}), 401
@app.route('/admin/revoke_permissions', methods=['POST'])
@requires_permission('all') # This action itself requires high privileges
def revoke_permissions():
"""
Simulates an admin revoking permissions for a user.
This action only modifies the backend 'database'.
"""
data = request.json
target_username = data.get('username')
if target_username in USERS_DB:
# VULNERABLE BEHAVIOR: The user's permissions are changed in the database,
# but the active session for that user is not updated or invalidated.
# The logged-in user will retain their old permissions until they log out.
USERS_DB[target_username]['role'] = 'user'
USERS_DB[target_username]['permissions'] = []
return jsonify({
'message': f"Permissions for '{target_username}' have been revoked in the database."
})
return jsonify({'error': 'User not found'}), 404
@app.route('/admin/sensitive_action', methods=['GET'])
@requires_permission('all')
def sensitive_action():
"""
A sensitive action that should only be accessible to users with 'all' permission.
Due to the vulnerability, a user whose permissions have been revoked can still
access this endpoint using their old session.
"""
return jsonify({
'status': 'Success',
'message': 'Sensitive admin action executed.',
'user_from_session': session.get('username'),
'permissions_from_session': session.get('permissions')
})Patched code sample
import sys
from functools import wraps
# This is a simplified, self-contained representation of the fix for CVE-2026-41133.
# The core issue was that user permissions were cached in the session at login and not
# re-validated on subsequent requests. The fix involves fetching the user's current
# permissions from the database on every authenticated request.
# --- Mock Environment: Representing pyLoad's database and session ---
class MockRole:
"""Represents a user role with associated permissions."""
def __init__(self, name, permission):
self.name = name
self.permission = permission
class MockUser:
"""Represents a user record in the database."""
def __init__(self, user_id, name, role):
self.id = user_id
self.name = name
self.role = role
class MockQuery:
"""Mocks the database query interface."""
def get(self, user_id):
# In a real application, this would query a database.
# This simulates fetching the latest user data.
return USERS_DATABASE.get(user_id)
class MockUsers:
"""Mocks the Users model."""
query = MockQuery()
# Mock database tables.
ROLES_DATABASE = {
"User": MockRole("User", 4),
"Admin": MockRole("Admin", 1023)
}
USERS_DATABASE = {
1: MockUser(1, "testuser", ROLES_DATABASE["User"])
}
# Mock session object, representing the user's cached session data.
session = {}
def login_fail():
"""Mock function to handle login failure."""
return "HTTP 401 Unauthorized"
def logout_user():
"""Mock function to clear the session."""
session.clear()
# --- Start of the Fixed Code ---
# This `login_required` decorator contains the logic from commit e95804fb0d06cbb07d2ba380fc494d9ff89b68c1.
# It is applied to web endpoints that require authentication.
def login_required(f):
"""
Ensures a user is logged in and, crucially, re-validates their
permissions against the database on every request.
"""
@wraps(f)
def decorated_function(*args, **kwargs):
user_id = session.get("user_id")
if not user_id:
return login_fail()
# VULNERABILITY FIX: Always fetch the user's current data from the database
# instead of trusting potentially stale data in the session.
user = MockUsers.query.get(user_id)
# If user has been deleted from the DB since login, invalidate the session.
if not user:
logout_user()
return login_fail()
# VULNERABILITY FIX: Update the session with fresh role and permission data.
# This ensures that any changes made by an admin (e.g., revoking privileges)
# are immediately reflected on the user's next request.
session["name"] = user.name
session["role"] = user.role.name
session["permission"] = user.role.permission
return f(*args, **kwargs)
return decorated_functionCite this entry
@misc{vaitp:cve202641133,
title = {{pyLoad caches permissions, letting users keep revoked privileges in a session.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-41133},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-41133/}}
}
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 ::
