GitHub Security Advisory: GHSA-vqv8-j3mj-wjxj
Package: wger (PIP)
Severity: MODERATE (CVSS 5.4)
CWE: CWE-601: URL Redirection to Untrusted Site ('Open Redirect')
Vulnerable versions: <= 2.5
Patched in: 2.6

Summary: wger: trainer_login open redirect - ?next= parameter not validated against host

Details:
### Summary

The `trainer_login` view in wger redirects to `request.GET['next']` directly via `HttpResponseRedirect()` without calling `url_has_allowed_host_and_scheme()`. After the trainer successfully enters impersonation mode, their browser is redirected to any attacker-controlled URL supplied in the `?next=` parameter, enabling Referer exfiltration and phishing.

### Details

**File**: `wger/core/views/user.py`, approximately line 203

```python
# VULNERABLE - wger/core/views/user.py
if not own:
    request.session['trainer.identity'] = orig_user_pk
    if request.GET.get('next'):
        return HttpResponseRedirect(request.GET['next'])   # no host/scheme validation
```

After the impersonation logic succeeds, the view performs no validation of the `next` parameter before issuing the redirect. An attacker who can deliver a crafted link (e.g. `/en/user/2/trainer-login?next=https://evil.example/steal`) to a trainer can redirect the trainer's browser to any external host immediately after the impersonation session is established. The `Location` header contains the raw attacker-controlled URL.

**Affected endpoint**:
- `GET /en/user/<user_pk>/trainer-login` -> `wger.core.views.user.trainer_login` (the `?next=` redirect branch)

**Suggested patch**:

```diff
--- a/wger/core/views/user.py
+++ b/wger/core/views/user.py
+from django.utils.http import url_has_allowed_host_and_scheme
+
 if not own:
     request.session['trainer.identity'] = orig_user_pk
-    if request.GET.get('next'):
-        return HttpResponseRedirect(request.GET['next'])
+    next_url = request.GET.get('next')
+    if next_url and url_has_allowed_host_and_scheme(
+        next_url, allowed_hosts={request.get_host()}, require_https=request.is_secure()
+    ):
+        return HttpResponseRedirect(next_url)
     return HttpResponseRedirect(reverse('core:index'))
```

Adding `@require_POST` to `trainer_login` (see also VULN-030) moves the `next` parameter to the POST body where CSRF protection applies and eliminates the combined CSRF + open-redirect attack surface entirely.

### PoC

Tested on `wger/server:latest` Docker image. Victim: `trainer1` (`gym.gym_trainer` permission).

Step 1 - Authenticate as trainer:

```
POST /en/user/login HTTP/1.1
Host: target
Content-Type: application/x-www-form-urlencoded

username=trainer1&password=[REDACTED]&csrfmiddlewaretoken=[REDACTED]

-> 302 Found; Set-Cookie: sessionid=[trainer1_session]
```

Step 2 - Trainer clicks (or is delivered) the crafted link:

```
GET /en/user/2/trainer-login?next=https://evil.example/steal HTTP/1.1
Host: target
Cookie: sessionid=[trainer1_session]

-> 302 Found
   Location: https://evil.example/steal
```

Step 3 - Attacker server logs Referer:

```
Referer: http://target/en/user/2/trainer-login?next=https://evil.example/steal
(victim user_pk and next URL exposed)
```

Reproducibility: 2/2 runs.

### Impact

An attacker who can deliver a crafted URL to a trainer (phishing email, malicious gym management system integra