May 21, 2026·7 min read·2 visits
An incomplete patch in wger allows an attacker with limited gym management permissions to bypass tenant isolation and delete or deactivate unaffiliated user accounts by exploiting a Python null comparison logic error.
GHSA-MW8F-W6P8-XRF4 is a critical authorization bypass vulnerability in the wger fitness manager. The flaw exists due to an incomplete patch for CVE-2026-43948, leaving specific user management views vulnerable to flawed null value comparisons. This enables attackers with restricted permissions to permanently delete or deactivate arbitrary user accounts across the global unassigned user pool.
The wger fitness manager application contains a critical Incorrect Authorization vulnerability (CWE-863) within its core user management module. The affected component, specifically wger/core/views/user.py, fails to properly enforce tenant isolation boundaries during administrative actions. This flaw is an incomplete remediation of a previously disclosed vulnerability, CVE-2026-43948, wherein maintainers patched certain views but omitted several critical account management endpoints.
The application implements a tenant-based authorization model designed to restrict administrative capabilities to specific gym environments. Users granted the gym.manage_gym or gym.gym_trainer permissions are expected to manage only the user profiles associated with their explicitly assigned Gym entity. The tenant isolation boundary relies on verifying the equality of the gym_id attributes between the requesting user and the target user.
The vulnerability originates from the manner in which the application handles equivalence operations between two uninitialized or None data types within this tenant verification logic. The default state for newly registered users, including newly provisioned trainers, is gym=None. When the application compares these null states using a basic inequality operator, the logic evaluates unexpectedly and bypasses the intended authorization guard.
Attackers leverage this default unassigned state to transcend their intended authorization scope. By issuing specific requests against the unpatched views, an attacker can execute unauthorized state-changing operations against any other user in the unassigned pool. This fundamentally breaks the multi-tenant architecture and exposes the global user base to destructive actions initiated by low-privileged actors.
The root cause of this vulnerability is a raw comparison logic error during the evaluation of user permissions. In wger/core/views/user.py, the system attempts to enforce tenant isolation by comparing the gym_id foreign key of the target user (edit_user.userprofile.gym_id) with the gym_id of the requesting user (request.user.userprofile.gym_id). If the values are not equal, the application returns an HttpResponseForbidden.
The logic failure occurs due to Python's evaluation of the expression None != None. Both the default attacker profile and the default victim profile possess a None value for their gym_id attribute. When the system processes the inequality check, None != None evaluates to False. Because the condition requires a True result to trigger the HttpResponseForbidden return path, the authorization check is bypassed entirely.
The project maintainers previously attempted to remediate this exact logic pattern across the application by introducing a centralized helper function. The is_same_gym(user_a, user_b) function, located in wger/gym/helpers.py, correctly handles the null state by explicitly verifying if either parameter is None before comparing the integer values. If either user lacks a gym affiliation, the helper safely returns False.
Despite the existence of this helper function, the developers failed to implement it comprehensively across all user management interfaces. Three critical views—UserDeactivateView, UserActivateView, and the account delete view—were omitted from the refactoring process. These endpoints retained the vulnerable raw inequality comparison, leaving the application exposed to the same authorization bypass technique.
An examination of the vulnerable delete view at line 131 of wger/core/views/user.py reveals the flawed implementation. The function first validates whether the requesting user possesses the required gym.manage_gym permission. It then evaluates the gym_id attribute equality without validating whether either attribute contains an initialized integer value.
The vulnerable code block demonstrates how the logical and condition processes the authorization requirements. The permission check executes first, and if successful, the execution flow proceeds to the flawed identity comparison.
if not request.user.has_perm('gym.manage_gyms') and (
not request.user.has_perm('gym.manage_gym')
or request.user.userprofile.gym_id != user.userprofile.gym_id
):
return HttpResponseForbidden()The required patch replaces the raw inequality comparison with the is_same_gym() helper. This abstraction centralizes the tenant verification logic and ensures that null attributes are handled securely. By passing both the request.user and the edit_user objects to the helper, the view guarantees an accurate authorization assessment.
--- a/wger/core/views/user.py
+++ b/wger/core/views/user.py
- if (request.user.has_perm('gym.manage_gym') or request.user.has_perm('gym.gym_trainer')) \
- and edit_user.userprofile.gym_id != request.user.userprofile.gym_id:
+ if (request.user.has_perm('gym.manage_gym') or request.user.has_perm('gym.gym_trainer')) \
+ and not is_same_gym(request.user, edit_user):
return HttpResponseForbidden()Exploitation of this vulnerability requires the attacker to fulfill specific state preconditions within the application. The attacker must authenticate using an account provisioned with either the gym.manage_gym or gym.gym_trainer permission. Concurrently, the attacker's user profile must maintain the default gym=None attribute, indicating no affiliation with a specific tenant.
The target acquisition phase relies on identifying a victim account that also maintains the gym=None state. Because this is the default configuration for all newly registered users in the wger platform, the pool of potential targets is extensive. The attacker must determine the victim's primary key (PK), which the framework assigns as a predictable, sequential integer.
The exploitation sequence begins with an initial access request to the administrative interface. The attacker issues an authenticated HTTP GET request to the /en/user/<victim_pk>/delete endpoint. Due to the null comparison flaw, the application fails to return the expected HTTP 403 Forbidden response. Instead, it processes the request and renders the password confirmation form required to finalize the deletion.
To complete the attack, the adversary submits an HTTP POST request to the same endpoint, providing their own credentials and a valid CSRF token. The application authenticates the destructive operation and executes the database deletion command. The exact exploit payload can be delivered via a standard command-line utility.
curl -b cookies.txt -d "password=AttackerPass&csrfmiddlewaretoken=..." \
"http://target/en/user/<victim_pk>/delete"The primary impact of this vulnerability manifests as a critical violation of system integrity and data availability. The successful execution of the delete endpoint results in the permanent removal of the targeted user account from the backend database. The Django framework's cascading deletion behavior subsequently destroys all relational data associated with the victim, including historical workout logs, custom exercises, and active nutrition plans.
Beyond outright data destruction, the attacker can leverage the UserDeactivateView endpoint to manipulate access controls. By deactivating a target account, the attacker revokes the victim's ability to authenticate, creating a targeted and persistent denial of service condition. This action requires the identical prerequisites and bypasses the identical authorization mechanisms as the deletion vector.
The vulnerability facilitates a severe authorization scope change. The gym.manage_gym permission is architecturally designed to restrict administrative actions to an isolated, single-tenant environment. The logic flaw allows the attacker to breach this boundary and exert administrative control over the global "no gym" user pool, affecting accounts outside their legitimate purview.
These factors contribute to a CVSS v3.1 base score of 9.9. The high severity rating reflects the comprehensive compromise of integrity and availability, the lack of necessary user interaction, and the capability of a low-privileged user to execute actions typically reserved for global administrators.
The definitive remediation strategy requires system administrators to apply the source code patch to the wger installation. The patch must modify wger/core/views/user.py to replace all instances of raw gym_id comparisons with the is_same_gym() helper function. This modification standardizes the tenant verification logic and ensures that unassigned user states are explicitly rejected during cross-account operations.
For environments unable to deploy the code modifications immediately, administrators must implement strict configuration-based workarounds. Security teams should conduct a comprehensive audit of all user accounts possessing the gym.manage_gym or gym.gym_trainer permissions. Every privileged account must be explicitly linked to a valid Gym entity within the database.
This configuration workaround directly neutralizes the vulnerability by altering the mathematical evaluation of the permission check. When an attacker possesses a valid integer assigned to their gym_id attribute, the comparison against the victim's null state changes from None != None to [Integer] != None. This expression accurately evaluates to True, triggering the intended HTTP 403 Forbidden response and blocking the unauthorized action.
Long-term architectural stability requires robust handling of uninitialized states across the application. The project developers should enforce strict constraints within the permission assignment workflow, explicitly preventing the allocation of tenant-management roles to profiles lacking a definitive tenant association.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
wger wger-project | <= 2.5 | - |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-863 |
| Attack Vector | Network |
| CVSS Score | 9.9 |
| Impact | Arbitrary cross-tenant account deletion and state manipulation |
| Exploit Status | Proof of Concept available |
| KEV Status | Not Listed |
The software performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check.