VAITP Dataset

← Back to the dataset

CVE-2026-43948

wger auth bypass allows takeover of accounts with no assigned gym.

  • CVSS 9.9
  • CWE-863
  • Authentication, Authorization, and Session Management
  • Remote

wger is a free, open-source workout and fitness manager. Prior to 2.6, the reset_user_password and gym_permissions_user_edit views in wger perform a gym-scope authorization check using Python object comparison (!=) that evaluates None != None as False, silently bypassing the guard when both the attacker and victim have no gym assignment (gym=None). A user with gym.manage_gym permission and gym=None can reset the password of any other gym=None user; the new plaintext password is returned verbatim in the HTML response body, enabling one-shot full account takeover. The victim's original password is invalidated, locking them out permanently. This vulnerability is fixed in 2.6.

CVSS base score
9.9
Published
2026-05-12
OWASP
A01 Broken Access Control
Orthogonal defect classification
Checking
Code defect classification
Incorrect Check
Category
Authentication, Authorization, and Session Management
Subcategory
Privilege Escalation
Accessibility scope
Remote
Impact
Unauthorized Access
Affected component
wger
Fixed by upgrading
Yes

Solution

Upgrade to wger version 2.6 or later.

Vulnerable code sample

# Simplified representation of user and gym models for context
class User:
    def __init__(self, username, permissions=None, gym=None):
        self.username = username
        self.permissions = permissions if permissions else set()
        # The 'gym' attribute can be a Gym object or None
        self.gym = gym
        self.password = "a_secret_password"

# This function represents the vulnerable 'reset_user_password' view
# prior to the fix in version 2.6.
#
# In a real web application, `requesting_user` would be derived from the
# session, and `target_user` would be looked up from the database.
def reset_user_password(requesting_user: User, target_user: User, new_password: str):
    """
    Allows a user with 'gym.manage_gym' permission to reset another user's password.
    """

    # Step 1: Check for the required permission.
    if 'gym.manage_gym' not in requesting_user.permissions:
        # In a real app, this would return an HTTP 403 Forbidden response.
        return "Error: Permission denied."

    # Step 2: VULNERABLE AUTHORIZATION CHECK
    # The code intends to ensure the action is within the same gym scope.
    # However, if both `requesting_user.gym` and `target_user.gym` are None,
    # the comparison `None != None` evaluates to `False`, and the check is bypassed.
    if requesting_user.gym != target_user.gym:
        return "Error: You can only manage users assigned to your own gym."

    # Step 3: If authorization passes (or is bypassed), reset the password.
    target_user.password = new_password

    # Step 4: VULNERABLE INFORMATION DISCLOSURE
    # The new password is leaked in the response body, enabling account takeover.
    response_body = f"Success! The password for {target_user.username} has been changed to: {new_password}"
    return response_body

Patched code sample

def fixed_gym_permission_check(requesting_user_gym, target_user_gym):
    """
    Represents the fixed authorization logic that prevents CVE-2026-43948.

    The original vulnerable code used a check like:
    `if requesting_user_gym != target_user_gym:`
    This would incorrectly pass if both gym values were None, as `None != None`
    evaluates to False, bypassing the intended security check.

    The fix ensures that an action is denied if the requesting user is not
    assigned to a gym, or if the gyms do not match.
    """
    # The FIX: Explicitly check if the requester's gym is None before comparison.
    if requesting_user_gym is None or requesting_user_gym != target_user_gym:
        # Deny permission
        return False

    # Grant permission only if both users are in the same, non-None gym
    return True

Payload

POST /gym/user/VICTIM_ID/password/reset/ HTTP/1.1
Host: wger.example.com
Cookie: sessionid=ATTACKER_SESSION_ID
Content-Type: application/x-www-form-urlencoded
Content-Length: 104

csrfmiddlewaretoken=VALID_CSRF_TOKEN&new_password1=NewP@ssw0rdF0rVictim&new_password2=NewP@ssw0rdF0rVictim

Cite this entry

@misc{vaitp:cve202643948,
  title        = {{wger auth bypass allows takeover of accounts with no assigned gym.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-43948},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-43948/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::