# wger: cross-tenant admin notes/contracts leak via gym=None bypass (5 views)

Five wger gym views compare gyms with `!=`, so a gym-less trainer passes the None==None guard and reads any unaffiliated user's admin notes, documents and contracts.

- Payout: Research
- Severity: high
- Program: wger
- Found by: whatisproblem
- Disclosed: 2026-10-07
- CVE: CVE-2026-43976
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-c72h-82w6-rqfp
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-c72h-82w6-rqfp
- Tags: area:web domain:web impact:data-leak impact:pii-leak impact:privesc kind:research platform:ghsa sev:high tech:python technique:source-review vuln:access-control vuln:auth-bypass vuln:idor

**Target:** Django gym management views (admin notes, documents, contracts, user config, permissions)
**Root cause:** The authorization guard `request.user.userprofile.gym != user.userprofile.gym` evaluates to False when both gyms are None, so HttpResponseForbidden is never returned. The follow-up querysets filter only on the attacker-supplied member_id with no gym-scoped secondary check.
**Steps:**
1. Authenticate as a user holding `gym.gym_trainer` + `gym.add_adminusernote` with `userprofile.gym=None` via `POST /en/user/login`.
2. Request `GET /en/gym/notes/list/user/2` with the session cookie; response 200 returns victim's private admin notes.
3. Request `GET /en/gym/documents/list/user/2` and `GET /en/gym/contract/list/2` to disclose documents and contracts.
4. Enumerate user PKs 1..N against `/en/gym/notes/list/user/{uid}`: 200 = gym=None victim, 403 = gym-assigned user.
5. Optionally reach `GET /en/gym/user/<member_pk>/permissions` to modify victim permissions.
**How found:** Noticed the shared `gym_a != gym_b` guard pattern across five views and reasoned that Python's `None != None` is False, then confirmed with a three-scenario RBAC disproof (same-gym 200, None/None 200 with PII, differing gyms 403).
**Exploitation:** Any authenticated low-privileged trainer whose account has no gym assignment (the default for newly registered users) iterates member IDs and pulls admin notes, uploaded documents, contracts and config for every other gym=None user; the permissions endpoint is reachable for potential privilege escalation.
**Escalation:** Permissions edit view reachable through the same bypass, giving a path to modify victim permissions (not fully explored).
**Insight:** Object-identity/equality guards on nullable FK attributes silently pass when both sides are None; grep for `!=` comparisons on nullable scope fields and test with unassigned accounts, since default-unassigned state is common on registration.

