Alta

pyLoad has Stale Session Privilege After Role/Permission Change (Privilege Revocation Bypass)

  • Publicada: 14.04.2026
  • Actualizado: 11.10.2026 14:17

Summary

pyLoad caches 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.

Details

The WebUI auth flow stores authorization state in session:

  • src/pyload/webui/app/helpers.py:187-200
    • set_session(...) writes:
      • "role": user_info["role"]
      • "perms": user_info["permission"]

Authorization checks later trust cached session values:

  • src/pyload/webui/app/helpers.py:134-151
    • parse_permissions(...) reads session.get("role") / session.get("perms")
  • src/pyload/webui/app/helpers.py:225-230
    • is_authenticated(...) only verifies authenticated and api.user_exists(user) (existence), not fresh role/permission
  • src/pyload/webui/app/helpers.py:267-275
    • login_required(...) uses parse_permissions(s) for allow/deny decisions
  • src/pyload/webui/app/helpers.py:356-365
    • API session auth path also trusts s["role"] and s["perms"]

Role/permission updates are written to DB but active sessions are not invalidated/refreshed:

  • src/pyload/webui/app/blueprints/json_blueprint.py:389-434
    • update_users(...) calls api.set_user_permission(...) and returns
  • src/pyload/core/api/__init__.py:1643-1645
    • set_user_permission(...) updates DB role/permission only

Default exposure window is long:

  • src/pyload/core/config/default.cfg:47
    • session_lifetime = 44640 minutes (~31 days)

Therefore, privilege revocation is not enforced immediately for active sessions.

Note on duplicates:

  • This appears distinct from CVE-2023-0227 (session validity after user deletion) because this report is about stale authorization after role/permission changes while the user still exists.

PoC

python
#!/usr/bin/env python3
"""
Repro: stale session privilege after role/permission changes.

This PoC is source-based and leaves no persistent state.
It validates that:
1) Role/permission are cached into session at login.
2) Authorization checks read role/permission from session, not fresh DB values.
3) User updates write DB permission/role without invalidating active sessions.
4) Default session lifetime is long, increasing stale-privilege exposure window.
"""

from __future__ import annotations

import pathlib
import re
from typing import Iterable


ROOT = pathlib.Path(__file__).resolve().parent / "pyload" / "src" / "pyload"


def read(rel: str) -> str:
    return (ROOT / rel).read_text(encoding="utf-8")


def has_any(text: str, patterns: Iterable[str]) -> bool:
    return all(re.search(p, text, re.MULTILINE) for p in patterns)


def main() -> None:
    helpers = read("webui/app/helpers.py")
    json_blueprint = read("webui/app/blueprints/json_blueprint.py")
    api_init = read("core/api/__init__.py")
    default_cfg = (ROOT / "core/config/default.cfg").read_text(encoding="utf-8")

    checks = {
        "set_session_caches_role_perms": has_any(
            helpers,
            [
                r'def\\s+set_session\\(',
                r'"role"\\s*:\\s*user_info\\["role"\\]',
                r'"perms"\\s*:\\s*user_info\\["permission"\\]',
            ],
        ),
        "is_authenticated_only_checks_user_exists": has_any(
            helpers,
            [
                r'def\\s+is_authenticated\\(',
                r'api\\s*=\\s*flask\\.current_app\\.config\\["PYLOAD_API"\\]',
                r'return\\s+authenticated\\s+and\\s+api\\.user_exists\\(user\\)',
            ],
        ),
        "parse_permissions_reads_session_cache": has_any(
            helpers,
            [
                r'def\\s+parse_permissions\\(',
                r'session\\.get\\("role"\\)\\s*==\\s*Role\\.ADMIN',
                r'session\\.get\\("perms"\\)',
            ],
        ),
        "login_required_uses_parse_permissions_session": has_any(
            helpers,
            [
                r'def\\s+login_required\\(',
                r'if\\s+is_authenticated\\(s\\):',
                r'perms\\s*=\\s*parse_permissions\\(s\\)',
            ],
        ),
        "api_session_auth_uses_cached_role_perms": has_any(
            helpers,
            [
                r'if\\s+is_authenticated\\(s\\):',
                r'"role"\\s*:\\s*s\\["role"\\]',
                r'"permission"\\s*:\\s*s\\["perms"\\]',
            ],
        ),
        "update_users_changes_db_without_session_invalidation": has_any(
            json_blueprint,
            [
                r'def\\s+update_users\\(',
                r'api\\.set_user_permission\\(name,\\s*data\\["permission"\\],\\s*data\\["role"\\]\\)',
                r'return\\s+jsonify\\(True\\)',
            ],
        ),
        "set_user_permission_only_updates_db": has_any(
            api_init,
            [
                r'def\\s+set_user_permission\\(',
                r'self\\.pyload\\.db\\.set_permission\\(user,\\s*permission\\)',
                r'self\\.pyload\\.db\\.set_role\\(user,\\s*role\\)',
            ],
        ),
        "default_session_lifetime_long": re.search(
            r'session_lifetime\\s*:\\s*"Session lifetime \\(minutes\\)"\\s*=\\s*44640',
            default_cfg,
            re.MULTILINE,
        )
        is not None,
    }

    for name, ok in checks.items():
        print(f"{name}={ok}")

    stale_privilege_repro_success = all(checks.values())
    print(f"stale_privilege_repro_success={stale_privilege_repro_success}")

    # Cleanup: this PoC creates/modifies no runtime/data files.
    print("cleanup_done=True")


if __name__ == "__main__":
    main()
set_session_caches_role_perms=True
is_authenticated_only_checks_user_exists=True
parse_permissions_reads_session_cache=True
login_required_uses_parse_permissions_session=True
api_session_auth_uses_cached_role_perms=True
update_users_changes_db_without_session_invalidation=True
set_user_permission_only_updates_db=True
default_session_lifetime_long=True
stale_privilege_repro_success=True
cleanup_done=True

Impact

  • Privilege revocation is not immediate for active sessions.
  • A user can continue using stale, previously granted privileges (including admin) after downgrade/restriction.
  • This can allow continued access to privileged WebUI/API actions until session expiry or manual logout/session reset.

Software afectado

Haz clic en una tecnología para ver todas sus vulnerabilidades.

PlataformaPaqueteVersiones vulnerablesCorregida en
Python (PyPI) pyload-ng <= 0.5.0b3.dev97 sin corrección

Referencias

Puntuación CVSS 8.8/10
Vector CVSS
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Debilidad (CWE)
CWE-613
Plataforma
Python (PyPI)
Paquete
pyload-ng
Publicada
14.04.2026
Actualizado
11.10.2026 14:17

Fuentes y licencias

OSV / GitHub Advisory Database - PYSEC-2026-2990 - PyPA Advisory Database; CC BY 4.0

OSV / GitHub Advisory Database - GHSA-66hx-chf7-3332 - CC BY 4.0

Historial de vulnerabilidades: pyload-ng

Todas las vulnerabilidades publicadas de este paquete (Python (PyPI)), de la más reciente a la más antigua. Muestra si es un fallo aislado o un paquete que hay que vigilar.

Todas (58) en el archivo
Entradas en total
58
Primera → última
01.2023 10.2026
Sin corrección
24
Explotada en ataques
0

Distribución por gravedad

  • Crítica 9
  • Alta 23
  • Media 25
  • Baja 1

Divulgaciones por año

  1. 2023: 9
  2. 2024: 8
  3. 2025: 8
  4. 2026: 33
Gravedad:
  1. Alta · 7.5 ghsa-889w-m37p-88m5 >= 0.5.0b3.dev98, <= 0.5.0b3.dev101 sin corrección
  2. Alta · 8.1 ghsa-r44w-v6gf-x3p6 < 0.5.0b3.dev101 0.5.0b3.dev101
  3. Alta · 8.1 CVE-2026-42315 < 0.5.0b3.dev100 0.5.0b3.dev100
  4. pyLoad has Stale Session Privilege After Role/Permission Change (Privilege Revocation Bypass) esta entrada
    Alta · 8.8 CVE-2026-41133 <= 0.5.0b3.dev97 sin corrección
  5. Alta · 8.8 CVE-2026-35463 <= 0.5.0b3.dev96 sin corrección
  6. Alta · 7.7 CVE-2026-35187 <= 0.5.0b3.dev96 sin corrección
  7. Alta · 7.1 CVE-2026-29778 >= 0.5.0b3.dev13, < 0.5.0b3.dev97 0.5.0b3.dev97
  8. Alta · 8.1 CVE-2025-61773 < 0.5.0b3.dev91 0.5.0b3.dev91
  9. Alta CVE-2025-57751 < 0.5.0b3.dev92 0.5.0b3.dev92
Mostrar más (12) Mostrando 20 de 32

Identifica las vulnerabilidades de tu aplicación

Con la integración de DockRay y el inventario de software activado, DockRay compara las versiones notificadas de los componentes con las vulnerabilidades conocidas y avisa de las coincidencias confirmadas. La cobertura depende de la tecnología compatible y de los avisos disponibles.

Conectar tu aplicación
Newsletter DockRay

La seguridad de tus aplicaciones en tu correo

Vulnerabilidades importantes, recomendaciones concretas y consejos prácticos para tus aplicaciones. Elige tus tecnologías y sigue lo que importa para tu proyecto.

  • Un resumen diario de nuevas vulnerabilidades, también las críticas.
  • Avisos sobre actualizaciones importantes.
  • Consejos prácticos sobre seguridad y monitorización de aplicaciones.

Déjalo vacío para recibir avisos de todas las tecnologías.

Suscripción gratuita. Date de baja con un clic en cualquier correo.