Skip to main content

Python CVE-2026-40474

HIGH
Improper Access Control (CWE-284)
2026-04-16 https://github.com/wger-project/wger GHSA-xppv-4jrx-qf8m
7.6
CVSS 3.1 · GitHub Advisory
View

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain

  1. Access
    Authenticate as low-privilege user
  2. Delivery
    Navigate to /config/gym-config/edit
  3. Exploit
    Submit POST with modified default_gym value
  4. Execution
    Trigger GymConfig.save() side effects
  5. Persist
    Installation-wide user profile reassignment completes
  6. Impact
    Access tenant-isolated user data in target gym

Vulnerability Assessment

Weakness
CWE
OWASP Top 10:2025
A01:2025 - Broken Access Control
ASVS 5.0
V8 - Authorization Access control enforcement
MITRE ATT&CK

1 technique across 1 tactic: Defense Evasion

The technique list with ATT&CK links and the kill-chain view is part of the Basic plan and up.

Exploitation Conditions

Authenticated access with any valid user account (PR:L in CVSS vector). No administrative privileges, specific roles, or group memberships required-any registered user can exploit this. The vulnerability affects default configurations where GymConfigUpdateView is accessible via standard URL routing at /config/gym-config/edit. Exploitation requires the wger installation to have the config.gym_config module installed and URL patterns mapped (confirmed in default deployments). The target installation must use GymConfig singleton (pk=1) with the vulnerable mixin inheritance-this is the upstream implementation as shown in wger/config/views/gym_config.py. Attack is network-based (AV:N), does not require user interaction (UI:N), and succeeds against standard Docker Compose deployments as demonstrated in the POC. Limiting factor: attacker must know or discover the /config/gym-config/edit endpoint URL, though this is trivial via source code review or directory enumeration on open-source deployments.

Impact

Real-world risk is HIGH for production wger installations with multiple gyms/tenants, MODERATE for single-gym deployments. CVSS 7.6 accurately reflects the technical severity: network-accessible (AV:N), trivial to exploit once authenticated (AC:L/PR:L), with significant integrity impact (I:H) from unauthorized tenant manipulation. EPSS data not available. No CISA KEV listing indicates no confirmed widespread exploitation. The GitHub security advisory provides sufficient technical detail for proof-of-concept development, lowering the bar for exploitation. SSVC assessment: exploitation status is none (advisory-only), automatable is yes (simple authenticated POST request), technical impact is partial (manipulates global state but doesn't grant direct code execution). The vulnerability's severity is amplified by GymConfig.save() side effects that perform bulk database updates affecting other users-this is not merely unauthorized access to a settings page but actual privilege escalation enabling installation-wide data manipulation. Single-tenant deployments see reduced impact as gym reassignment affects fewer boundaries. Defenders should prioritize patching if running multi-gym/SaaS configurations; single-gym users can deprioritize if the default_gym setting is not operationally relevant.

Exploit ScenarioDerived

An attacker registers a standard user account on a multi-tenant wger installation hosting multiple gyms. They navigate directly to /config/gym-config/edit, bypassing any UI restrictions, and submit a POST request changing the default_gym foreign key to a gym they wish to monitor or disrupt. The GymConfig.save() method executes, reassigning all UserProfile records with gym=None to the attacker-selected gym and creating GymUserConfig entries that may grant the attacker visibility into another tenant's user roster or training data. If the attacker controls or can register into the target gym, they gain access to user records that should be isolated by tenant boundaries.

This Week Schedule remediation this week. Why: HIGH severity (CVSS 7.0-8.9).
No patch listed yet 0.0% exploitation probability (EPSS)

Get an e-mail when this CVE is exploited, patched or re-scored. Free account.

Alert me

AnalysisAIv1 – Apr 16, 2026

Authenticated low-privileged users in wger can modify installation-wide gym configuration via /config/gym-config/edit due to missing permission enforcement, enabling vertical privilege escalation. The GymConfigUpdateView declares 'config.change_gymconfig' permission but inherits WgerFormMixin instead of WgerPermissionMixin, causing the permission check to never execute. Exploiting this allows attackers to manipulate default gym assignments affecting all users, with GymConfig.save() automatically reassigning user profiles and creating gym configurations tenant-wide. CVSS 7.6 (High) with network attack vector, low complexity, and low privileges required. No active exploitation (KEV) or public POC identified at time of analysis, though GitHub advisory provides detailed reproduction steps.

RemediationAI

Apply the vendor-recommended fix by modifying GymConfigUpdateView inheritance to include WgerPermissionMixin before WgerFormMixin in the class definition: 'class GymConfigUpdateView(WgerPermissionMixin, WgerFormMixin, UpdateView)' with 'login_required = True' set. Python's method resolution order (MRO) processes mixins left-to-right, so WgerPermissionMixin.dispatch() will execute before WgerFormMixin.dispatch(), enforcing the permission_required check. Alternatively, replace custom mixins with Django's built-in PermissionRequiredMixin and LoginRequiredMixin for standards-compliant access control. Patch availability and fixed version number not confirmed in available data-monitor https://github.com/wger-project/wger/security/advisories/GHSA-xppv-4jrx-qf8m for release information. Compensating control if immediate patching is not feasible: implement reverse proxy or Django middleware to restrict /config/gym-config/edit to superuser role via HTTP 403 response; this prevents unauthorized access but does not fix the underlying authorization flaw and will conflict with legitimate administrator workflows. Workaround side effects: middleware-based blocking requires maintaining URL pattern allowlists and may break if application URL routing changes. Verify fix effectiveness by testing that low-privileged authenticated users receive HTTP 403 when accessing /config/gym-config/edit.

Affected Packages

pypi wger no fix

Source: OSV (Open Source Vulnerabilities)

Affects your stack

Affects 1 package in pypi

Checking this CVE against your own stacks and servers is part of the Basic plan and up.

Technical ContextAI · show details

The vulnerability stems from Django class-based view mixin inheritance order and method resolution. The wger project implements two mixins: WgerPermissionMixin (checks user.has_perm() against permission_required in dispatch()) and WgerFormMixin (validates object ownership via get_owner_object()). GymConfigUpdateView inherits only WgerFormMixin, which attempts ownership validation but fails gracefully when GymConfig model lacks get_owner_object() method, returning owner_object=False and skipping all access control. The permission_required attribute exists as a class variable but is never evaluated because WgerPermissionMixin.dispatch() is not in the method resolution order. This is a textbook CWE-284 (Improper Access Control) where authorization logic is defined but not executed. The GymConfig singleton (pk=1) represents installation-wide settings, and its save() method performs CASCADE operations on UserProfile and GymUserConfig tables, making this a high-impact data integrity issue in multi-tenant deployments.

Know when Python gets its next vulnerability

  • Watch products and get an email when something new is published
  • Check this CVE against your own stack and servers
  • Exploit and PoC references, threat intelligence from 22 sources

No credit card · 7-day full trial

Severity by source

GitHub Advisory PRIMARY 7.6 HIGH
AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:L

Primary rating from GitHub Advisory · only source for this CVE.

CVSS VectorGitHub Advisory

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

DescriptionGitHub Advisory

Summary

wger exposes a global configuration edit endpoint at /config/gym-config/edit implemented by GymConfigUpdateView. The view declares permission_required = 'config.change_gymconfig' but does not enforce it because it inherits WgerFormMixin (ownership-only checks) instead of the project’s permission-enforcing mixin (WgerPermissionMixin) .

The edited object is a singleton (GymConfig(pk=1)) and the model does not implement get_owner_object(), so WgerFormMixin skips ownership enforcement. As a result, a low-privileged authenticated user can modify installation-wide configuration and trigger server-side side effects in GymConfig.save().

This is a vertical privilege escalation from a regular user to privileged global configuration control. The application explicitly declares permission_required = 'config.change_gymconfig', demonstrating that the action is intended to be restricted; however, this requirement is never enforced at runtime.

Affected endpoint

The config URLs map as follows.

File: wger/config/urls.py

python
patterns_gym_config = [
    path('edit', gym_config.GymConfigUpdateView.as_view(), name='edit'),
]

urlpatterns = [
    path(
        'gym-config/',
        include((patterns_gym_config, 'gym_config'), namespace='gym_config'),
    ),
]

This resolves to:

/config/gym-config/edit

Root cause

The view declares a permission but does not enforce it

File: wger/config/views/gym_config.py

python
class GymConfigUpdateView(WgerFormMixin, UpdateView):
    model = GymConfig
    fields = ('default_gym',)
    permission_required = 'config.change_gymconfig'
    success_url = reverse_lazy('gym:gym:list')
    title = gettext_lazy('Edit')

    def get_object(self):
        return GymConfig.objects.get(pk=1)

The permission string exists, but WgerFormMixin does not check permission_required.

The project’s permission mixin exists but is not used

File: wger/utils/generic_views.py

python
class WgerPermissionMixin:
    permission_required = False
    login_required = False

    def dispatch(self, request, *args, **kwargs):
        if self.login_required or self.permission_required:
            if not request.user.is_authenticated:
                return HttpResponseRedirect(
                    reverse_lazy('core:user:login') + f'?next={request.path}'
                )

            if self.permission_required:
                has_permission = False
                if isinstance(self.permission_required, tuple):
                    for permission in self.permission_required:
                        if request.user.has_perm(permission):
                            has_permission = True
                elif request.user.has_perm(self.permission_required):
                    has_permission = True

                if not has_permission:
                    return HttpResponseForbidden('You are not allowed to access this object')

        return super(WgerPermissionMixin, self).dispatch(request, *args, **kwargs)

GymConfigUpdateView does not inherit this mixin, so none of the login/permission logic runs.

The mixin that *is* used performs only ownership checks, and GymConfig has no owner

File: wger/utils/generic_views.py

python
class WgerFormMixin(ModelFormMixin):
    def dispatch(self, request, *args, **kwargs):
        self.kwargs = kwargs
        self.request = request

        if self.owner_object:
            owner_object = self.owner_object['class'].objects.get(pk=kwargs[self.owner_object['pk']])
        else:
            try:
                owner_object = self.get_object().get_owner_object()
            except AttributeError:
                owner_object = False

        if owner_object and owner_object.user != self.request.user:
            return HttpResponseForbidden('You are not allowed to access this object')

        return super(WgerFormMixin, self).dispatch(request, *args, **kwargs)

File: wger/config/models/gym_config.py

python
class GymConfig(models.Model):
    default_gym = models.ForeignKey(
        Gym,
        verbose_name=_('Default gym'),
# ...
        null=True,
        blank=True,
        on_delete=models.CASCADE,
    )
# No get_owner_object() method

Because GymConfig does not implement get_owner_object(), WgerFormMixin catches AttributeError and sets owner_object = False, skipping any access restriction.

Security impact

This is not a cosmetic setting: GymConfig.save() performs installation-wide side effects.

File: wger/config/models/gym_config.py

python
def save(self, *args, **kwargs):
    if self.default_gym:
        UserProfile.objects.filter(gym=None).update(gym=self.default_gym)

        for profile in UserProfile.objects.filter(gym=self.default_gym):
            user = profile.user
            if not is_any_gym_admin(user):
                try:
                    user.gymuserconfig
                except GymUserConfig.DoesNotExist:
                    config = GymUserConfig()
                    config.gym = self.default_gym
                    config.user = user
                    config.save()

    return super(GymConfig, self).save(*args, **kwargs)

On deployments with multiple gyms, this allows a low-privileged user to tamper with tenant assignment defaults, affecting new registrations and bulk-updating existing users lacking a gym. This permits unauthorized modification of installation-wide state and bulk updates to other users’ records, violating the intended administrative trust boundary.

Proof of concept (local verification)

Environment: local docker compose stack, accessed via http://127.0.0.1:8088/en/.

Observed behavior

An unauthenticated user can reach the endpoint via GET; POST requires authentication and redirects to login. An authenticated low-privileged user can submit the form and change the global singleton. After the save, the application redirects to success_url = reverse_lazy('gym:gym:list') (e.g. /en/gym/list), which is permission-protected; therefore the browser may display a “Forbidden” page even though the global update already succeeded.

DB evidence (before/after)

Before submission:

bash
default_gym_id= None
profiles_gym_null= 1

After a low-privileged user submitted the form setting default_gym to gym id 1:

bash
default_gym_id= 1
profiles_gym_null= 0

Recommended fix

Ensure permission enforcement runs before the form dispatch.

Using the project mixin (order matters):

python
class GymConfigUpdateView(WgerPermissionMixin, WgerFormMixin, UpdateView):
    permission_required = 'config.change_gymconfig'
    login_required = True

Alternatively, use Django’s PermissionRequiredMixin (and LoginRequiredMixin) directly.

Conclusion

The view explicitly declares permission_required = 'config.change_gymconfig', which demonstrates developer intent that this action be restricted. The fact that it is not enforced constitutes improper access control regardless of perceived business impact.

<img width="1912" height="578" alt="Screenshot 2026-02-27 230752" src="https://github.com/user-attachments/assets/c627b404-6d9c-4477-88bd-f867d0fa09d2" />

Executive ViewAI

Why It Matters

CVE-2026-40474 is a privilege escalation vulnerability in wger that allows authenticated low-privileged users to modify organization-wide gym configuration settings due to missing permission enforcement in the web interface. This could enable unauthorized users to manipulate default gym assignments affecting all users across the installation, potentially disrupting operational gym management and user experience. Organizations running wger should assess exposure and implement mitigations immediately.

Business Impact: High
Compensating Controls

No vendor-released patch identified at time of analysis. Implement network-level access controls restricting /config/gym-config/edit endpoint to administrative IP ranges or VPN; apply authentication via reverse proxy with additional Multi-Factor Authentication requirement; conduct immediate code review of GymConfigUpdateView to manually enforce permission checks at application level pending official patch; restrict gym configuration modification to administrative roles only via database-level constraints until patched version is available.

Threat Intelligence Overview

EPSS Score
0.0%
Top 90% exploitable
Threat Intel
1
1 source
Data Quality
5 sources
Confidence: Medium
NVD CVE.org EPSS Vulnrichment GHSA GitHub

How this decision was reached

The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.

SignalValue
Severity HIGH
CISA KEV No
Public POC No
EPSS 0.0%
Patch available No
Active threat No
Deciding rule
HIGH severity (CVSS 7.0-8.9)
What every action level means
Emergency Immediate action required. Criteria: CRITICAL + in CISA KEV (actively exploited) without patch, OR CRITICAL + EPSS > 30% without patch, OR CRITICAL + active threat signals without patch.
Act Now Prioritize within 24-48h. Criteria: in KEV with patch available, OR CRITICAL + public POC, OR HIGH + EPSS > 10%, OR CRITICAL + active threat, OR any CRITICAL severity.
This Week Schedule remediation this week. Criteria: HIGH severity (CVSS 7.0-8.9). Patch within normal change window.
This Month Plan within 30 days. Criteria: MEDIUM severity (CVSS 4.0-6.9), lower exploitation probability. Include in regular patch cycle.
Monitor Track and reassess. Criteria: LOW severity (CVSS 0.1-3.9), minimal exploitation risk. No immediate action needed.

SSVC AssessmentENISA

SSVC Decision
Scheduled
Exploitation
Poc
System Exposure· derived
Open
Automatable
No
Human Impact
High
Technical Impact
Partial

Priority Score

38
Low Medium High Critical
KEV: 0
EPSS: 0
CVSS: +38
PoC: 0
Weaponized: 0
Threat intel: 0
Gov advisory: 0

Attack Technique

Authentication bypass attacks exploit flaws in the verification mechanisms that control access to systems and applications.

Privilege escalation occurs when an attacker leverages flaws in access control mechanisms to gain permissions beyond what they were originally granted.

Affected Products (CPE)GitHub Advisory

pkg:pip/wger

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

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