wger, Stored XSS via Unescaped License Attribution Fields, (High – no CVE)

Listen to this Post

How the CVE Works

The `AbstractLicenseModel.attribution_link` property in `wger/utils/models.py` builds an HTML string by directly concatenating user-controlled fields (license_author, license_, license_object_url, license_author_url, license_derivative_source_url) without calling `escape()` or format_html(). The resulting raw HTML is then rendered in the ingredient view template (wger/nutrition/templates/ingredient/view.html, line 171) using Django’s `|safe` filter, which bypasses auto-escaping. An authenticated non-temporary user can create an ingredient via the web form at `/en/nutrition/ingredient/add/` and inject a malicious payload into the `license_author` field (e.g., <img src=x onerror=alert(1)>). This payload is stored in the database. When any user – including unauthenticated visitors – views the ingredient’s public detail page, the unsafe HTML is emitted and the JavaScript executes in the victim’s browser. The `IngredientForm` (field license_author, max_length=3500) performs no input sanitization, and the `set_author()` method only sets defaults without cleaning user input. Six models inherit from `AbstractLicenseModel` (Exercise, ExerciseImage, ExerciseVideo, Translation, Ingredient, Image), but only Ingredient and nutrition Image templates currently use |safe. An alternative API path exists for trusted users: `POST /api/v2/exerciseimage/` with `license_author` payload stores the XSS, potentially affecting future templates or API consumers.

dailycve form

Platform: wger web application
Version: All before fix
Vulnerability : Stored XSS
Severity: High
date: 2026-04-16

Prediction: Patch within 2 weeks

What Undercode Say:

Create malicious ingredient via API (authenticated)
curl -X POST https://wger.example.com/api/v2/ingredient/ \
-H "Authorization: Token <your_token>" \
-F "name=XSS_Test" \
-F "energy=125" \
-F "protein=10" \
-F "carbohydrates=10" \
-F "fat=5" \
-F "license_author=<img src=x onerror=alert(document.cookie)>" \
-F "license=1"
View public ingredient page (XSS triggers)
curl https://wger.example.com/en/ingredient/999/

Exploit:

Authenticated user submits `license_author` = `` via ingredient form. Unauthenticated visitor loads ingredient page → payload executes, exfiltrates session cookie.

Protection from this CVE

Replace `attribution_link` property with format_html(): `from django.utils.html import format_html, escape` and wrap each user field in `escape()` or format_html(). Remove the `|safe` filter from templates. Add input sanitization on `license_author` field using Django’s `strip_tags` or a validator.

Impact:

Session hijacking (steal admin cookies), account takeover, data theft (workout/nutrition plans), worm-like propagation (malicious ingredient creates more XSS), phishing redirection.

🎯Let’s Practice Exploiting & Learn Patching For Free:

Sources:

Reported By: github.com
Extra Source Hub:
Undercode

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow DailyCVE & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin Featured Image

Scroll to Top