# pyLoad: Api.set_user_permission never invalidates the target's session

pyLoad's Api.set_user_permission updates the DB but never invalidates the target's Flask cookie session, so demoted users keep elevated permissions for ~31 days.

- Payout: Research
- Severity: high
- Program: pyLoad
- Found by: FlowOverFail
- Disclosed: 2026-10-09
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-889w-m37p-88m5
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-889w-m37p-88m5
- Tags: area:web domain:web impact:privesc kind:research platform:ghsa sev:high tech:python technique:legacy-endpoint technique:source-review vuln:access-control vuln:auth-bypass vuln:privilege-escalation

**Target:** Flask WebUI /api/<func> RPC dispatcher and cookie session auth
**Root cause:** Api.set_user_permission writes new permission/role to the DB but never calls clear_all_user_sessions; only the WebUI update_users() route does, and the RPC dispatcher bypasses it. Sessions cache role/perms at login and are never refreshed from the DB.
**Steps:**
1. Victim logs in via POST /login, receiving a Flask session cookie with cached role/perms.
2. Admin calls `POST /api/set_user_permission` (or `/api/setUserPermission`) with victim username and lower permission/role, using X-API-Key auth.
3. Confirm DB row shows reduced permission (e.g. permission=0).
4. Replay the victim's original cookie against a permission-gated endpoint like `GET /api/get_userdir`; it still returns 200 with pre-revocation access.
5. Control: same victim via X-API-Key gets 401, confirming cookie-session-specific staleness.
**How found:** Source review: grepped set_user_permission/setUserPermission, found only one call site pairing it with clear_all_user_sessions (json_blueprint.update_users), and noticed the RPC dispatcher invokes Api methods directly, bypassing that route. Then traced session population (set_session only at login/autologin, no before_request refresh) and confirmed with a live in-process test client PoC.
**Exploitation:** A demoted or revoked non-admin user simply keeps using their existing cookie; apikey_auth's cookie branch rebuilds user_info from stale session role/perms, so is_authorized() and parse_permissions() grant the old privileges on every WebUI page and /api/<func> call for up to session_lifetime (~31 days) or until logout.
**Insight:** When a state-changing admin API exists both as a WebUI route and a generic RPC dispatcher, check whether security side-effects (session invalidation, cache purge) live in the route rather than the underlying method — the RPC path will skip them. Also check whether auth middleware caches role/perms in the session vs re-reading the DB per request.

