CMU CERT/CC VINCE 2.0.6 - Stored XSS
# Exploit Tile: CMU CERT/CC VINCE 2.0.6 - Stored XSS
# Vendor: Carnegie Mellon University
# Product web page: https://www.kb.cert.org/vince/
# Affected version: <=2.0.6
Summary: VINCE is the Vulnerability Information and Coordination
Environment developed and used by the CERT Coordination Center
to improve coordinated vulnerability disclosure. VINCE is a
Python-based web platform.
Desc: The framework suffers from an authenticated stored
cross-site scripting vulnerability. Input passed to the
'content' POST parameter is not properly sanitised before
being returned to the user. This can be exploited to execute
arbitrary HTML/JS code in a user's browser session in context
of an affected site.
Tested on: nginx/1.20.0
Django 3.2.17
Vulnerability discovered by Gjoko 'LiquidWorm' Krstic
@zeroscience
Advisory ID: ZSL-2025-5917
Advisory URL: https://www.zeroscience.mk/en/vulnerabilities/ZSL-2025-5917.php
13.01.2023
--
$ curl -k https://kb.cert.org/vince/comm/post/CASE_NO \
> -H "Cookie: sessionid=xxxx" \
> -d 'content="><marquee>ZSL</marquee>%0A%0A&csrfmiddlewaretoken=xxx&paginate_by=10&reply_to=xxxxx' CMU CERT/CC VINCE 2.0.6 — Stored XSS Vulnerability (ZSL-2025-5917)
Summary: VINCE (Vulnerability Information and Coordination Environment) — the Python/Django platform used by CERT/CC — contains a stored cross-site scripting (XSS) flaw in versions up to and including 2.0.6. Unsanitized input passed to the POST parameter named content can be stored and later rendered to authenticated users without proper escaping, allowing arbitrary HTML/JavaScript execution in the victim's browser context.
Why this matters
- Stored XSS allows an attacker with write access (authenticated user or compromised account) to save malicious markup that executes whenever other users open the affected page.
- Attacks can steal session cookies, perform actions on behalf of victims, load malicious resources, or pivot to other parts of the application.
- Because VINCE manages vulnerability coordination, a compromise could expose sensitive communications or allow misinformation to be injected into disclosure workflows.
Vulnerability details
Root cause: user-supplied HTML passed in the content form field is not sanitized before being stored or returned, and the view/template renders it without escaping (or uses constructs that bypass Django's autoescaping such as |safe or mark_safe).
| Product | VINCE |
|---|---|
| Affected versions | <= 2.0.6 |
| Vulnerability type | Stored Cross-Site Scripting (Stored XSS) |
| Discovery | Gjoko 'LiquidWorm' Krstic / ZeroScience (Advisory ZSL-2025-5917) |
| Advisory | ZSL-2025-5917 |
Proof-of-concept (illustrative / for authorized testing only)
curl -k https://kb.cert.org/vince/comm/post/CASE_NO \
-H "Cookie: sessionid=xxxx" \
-d 'content=">ZSL%0A%0A&csrfmiddlewaretoken=xxx&paginate_by=10&reply_to=xxxxx'
This example demonstrates sending a crafted payload to the post endpoint that contains markup. It is included for defensive analysis and should only be run in an authorized, isolated test environment. Never test against production systems without permission.
How an attacker might exploit this
- Create or edit a post/comment and inject a payload (for example, an inline <script> or other markup) into the content field.
- When other users view that resource, the injected HTML/JS executes in their browser with the application's origin.
- Possible impacts include theft of session tokens, CSRF-style actions, credential theft via social engineering UI overlays, and distribution of malware.
Detection and verification
- Review templates and view code paths that render the stored content field for occurrences of
|safe,mark_safe(), or direct insertion of raw HTML into responses. - Search the codebase for fields named
contentor render calls that do not use autoescape. - Scan stored records for HTML tags or script-like payloads (e.g., search for "<", "<script", "<img", "<svg", ").
- Use automated scanners and manual testing in a non-production environment to confirm whether stored HTML executes when rendered.
Mitigation strategies — short-term and long-term
Mitigations can be applied at multiple layers: input sanitization, safe rendering, and HTTP security controls. Use a defense-in-depth approach.
- Escape on output: Ensure Django templates use autoescaping (default) and avoid using
|safeormark_safe()on user input. Treat all user content as untrusted. - Sanitize input when rich content is needed: If the application must allow limited HTML (e.g., for formatting), sanitize it server-side with a well-maintained library such as
bleach, using an allowlist of safe tags and attributes. - Validate/clean in forms or models: Implement
clean_methods on forms or modelsave()hooks to sanitize content before persistence. - Content Security Policy (CSP): Deploy a restrictive CSP header to reduce impact of injected script execution (e.g., disallow inline scripts with
script-src 'self'and use nonces/hashes for allowed scripts). - Least privilege and auditing: Limit who can post content and add moderation workflows; log content changes and review suspicious activity.
- Upgrade/patch: Apply vendor patches or upgrade VINCE to a version that addresses the issue if available.
Example fixes — Django-centric defenses
1) Sanitize using bleach in a Django form (server-side cleaning)
from django import forms
import bleach
ALLOWED_TAGS = ['b', 'i', 'u', 'em', 'strong', 'a', 'p', 'ul', 'ol', 'li']
ALLOWED_ATTRS = {'a': ['href', 'title', 'rel']}
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['content', ...]
def clean_content(self):
raw = self.cleaned_data.get('content', '')
cleaned = bleach.clean(raw, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRS, strip=True)
return cleaned
Explanation: This form-level cleaning uses bleach.clean to remove disallowed tags/attributes and strips malformed HTML. Cleaning at form validation prevents malicious markup from being stored in the database.
2) Sanitize on model save (alternative)
from django.db import models
import bleach
class Comment(models.Model):
content = models.TextField()
def save(self, *args, **kwargs):
self.content = bleach.clean(
self.content,
tags=ALLOWED_TAGS,
attributes=ALLOWED_ATTRS,
strip=True
)
super().save(*args, **kwargs)
Explanation: This approach ensures any code path that persists content (admin, APIs, scripts) will sanitize before storage. Use caution if you need to preserve raw input for auditing — consider storing a separate raw_content field.
3) Ensure safe rendering in templates
{# templates/comment_detail.html #}
{{ comment.content }}
Explanation: Django templates autoescape variables by default. Do not use |safe unless the content is proven sanitized and intentionally allowed. If you must render HTML, ensure the content has been sanitized with a library like bleach and document why it is safe.
4) Simple escaping alternative (if you want to keep raw text)
from django.utils.html import escape
def some_view(request):
comment = Comment.objects.get(pk=pk)
safe_html = escape(comment.content)
return render(request, '...', {'content': safe_html})
Explanation: escape() replaces special HTML characters (<, >, &, etc.) preventing interpretation as HTML. Use this if you want to preserve the exact text but prevent markup.
5) Deploy a Content Security Policy
# Example header (add via middleware or server config)
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self';
Explanation: CSP reduces the impact by restricting where scripts/styles can be loaded from and blocking inline script execution unless explicitly allowed via nonces/hashes. CSP is a mitigation, not a replacement for proper input/output handling.
Operational recommendations
- Audit all places where user-supplied content is displayed. Identify any use of
|safe,mark_safe(), or manual concatenation of HTML fragments. - Add automated tests: create unit/integration tests that attempt to store payloads and assert they are escaped or cleaned when rendered.
- Harden administrative access: enable multi-factor authentication and least privilege for users who can post content.
- Apply logging and alerts for newly created posts/comments that contain tags or potentially harmful attributes.
- Coordinate disclosure: if you operate VINCE, check with the vendor/maintainers for patches; if you are a user, upgrade or apply the recommended sanitization patches and follow the vendor advisory.
Detection signatures and search tips
- Code-level indicators: search repository for
mark_safe,|safe,format_htmlwith user input, or template constructs that intentionally bypass escaping. - Data-level indicators: run DB queries for textual fields containing '<', '<script', '<img', '<svg', '<marquee', 'onmouseover=', 'onerror='.
- Logging: monitor for unusual POST requests carrying HTML tags to endpoints that accept textual content.
Responsible testing and disclosure
Always test exploits only in controlled environments or with explicit permission from the system owner. For production incidents, follow an incident response plan: isolate, collect evidence, sanitize inputs, rotate credentials, and patch. Refer to the official advisory (ZSL-2025-5917) for the original report and coordinate with VINCE maintainers or CERT/CC for remediation guidance.
Conclusion
Stored XSS in VINCE 2.0.6 is a straightforward but serious vulnerability with real-world impact when untrusted HTML is stored and rendered. The safest approach is to sanitize at input, escape at output, and deploy additional controls (CSP, access restrictions, auditing). Implement the changes gradually in a test environment, add monitoring, and roll security updates through your deployment process.