VAITP Dataset

← Back to the dataset

CVE-2026-27469

Isso is vulnerable to stored XSS in comment fields via improper escaping.

  • CVSS 6.1
  • CWE-79
  • Input Validation and Sanitization
  • Remote

Isso is a lightweight commenting server written in Python and JavaScript. In commits before 0afbfe0691ee237963e8fb0b2ee01c9e55ca2144, there is a stored Cross-Site Scripting (XSS) vulnerability affecting the website and author comment fields. The website field was HTML-escaped using quote=False, which left single and double quotes unescaped. Since the frontend inserts the website value directly into a single-quoted href attribute via string concatenation, a single quote in the URL breaks out of the attribute context, allowing injection of arbitrary event handlers (e.g. onmouseover, onclick). The same escaping is missing entirely from the user-facing comment edit endpoint (PUT /id/) and the moderation edit endpoint (POST /id//edit/). This issue has been patched in commit 0afbfe0691ee237963e8fb0b2ee01c9e55ca2144. To workaround, nabling comment moderation (moderation = enabled = true in isso.cfg) prevents unauthenticated users from publishing comments, raising the bar for exploitation, but it does not fully mitigate the issue since a moderator activating a malicious comment would still expose visitors.

CVSS base score
6.1
Published
2026-02-21
OWASP
A03 Injection
Orthogonal defect classification
Checking
Code defect classification
Incorrect Check
Category
Input Validation and Sanitization
Subcategory
Cross-Site Scripting (XSS)
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
Isso
Fixed by upgrading
Yes

Solution

Upgrade Isso to version 0.12.5 or later.

Vulnerable code sample

import html

def create_comment_vulnerable(data):
    """
    This function simulates the vulnerable comment creation logic from Isso
    before the patch for CVE-2026-27469.

    The 'website' field is processed with html.escape(..., quote=False).
    This flaw fails to escape single or double quotes, creating a stored XSS
    vulnerability when the value is later rendered inside an HTML attribute.
    """
    # The text and author fields are assumed to be handled separately.
    text = html.escape(data.get('text', ''))
    author = html.escape(data.get('author', ''))

    # VULNERABLE LINE: The `quote=False` parameter prevents the escaping of
    # single (') and double (") quotes in the 'website' field.
    # An attacker can submit a payload like:
    # https://example.com' onmouseover='alert("XSS")'
    website = html.escape(data.get('website', ''), quote=False)

    # The processed data would then be stored in the database.
    return {
        'text': text,
        'author': author,
        'website': website
    }

def update_comment_vulnerable(data):
    """
    This function simulates the vulnerable comment editing endpoint (e.g., PUT /id/).
    The CVE notes that HTML escaping was missing entirely from this endpoint for
    certain fields, allowing for direct injection of malicious HTML.
    """
    # VULNERABLE: 'author' and 'website' are taken directly from user input
    # without any HTML escaping. An attacker can set the website to a value like:
    # javascript:alert('XSS')
    # Or inject event handlers by breaking out of attributes during rendering.
    author = data.get('author')
    website = data.get('website')
    text = html.escape(data.get('text', '')) # Text field might be handled correctly.

    # The unescaped data would be saved directly to the database,
    # to be rendered later for other users.
    return {
        'text': text,
        'author': author,
        'website': website
    }


def render_vulnerable_comment(comment):
    """
    Simulates how a frontend template would render the comment.
    The 'website' value is concatenated directly into a single-quoted HTML attribute.
    """
    # When rendering, the unescaped single quote from an attacker's payload
    # breaks out of the `href` attribute, allowing arbitrary attribute injection.
    # For example, a stored 'website' value of:
    #   https://example.com' onmouseover='alert(1)' class='injected
    # would result in the following broken and malicious HTML:
    #   <a href='https://example.com' onmouseover='alert(1)' class='injected'>
    author_link = f"<a href='{comment['website']}' rel='nofollow'>{comment['author']}</a>"

    return f"""
    <div class="comment-author">{author_link}</div>
    <div class="comment-text">{comment['text']}</div>
    """

Patched code sample

import markupsafe

def fixed_comment_serialization(comment_data):
    """
    This function demonstrates the logic that fixes the Stored XSS vulnerability
    (originally identified as CVE-2021-28469) in Isso.

    The vulnerability was caused by improperly escaping the 'website' field
    using a call equivalent to `markupsafe.escape(..., quote=False)`.
    This setting does not escape single or double quotes. Since the frontend
    injected the 'website' URL into a single-quoted href attribute (e.g.,
    <a href='...'>), an attacker could provide a URL containing a single quote
    to break out of the attribute and inject malicious event handlers.

    For example, a malicious URL like:
    https://example.com' onmouseover='alert(\"XSS\")'

    ...would result in the following vulnerable HTML:
    <a href='https://example.com' onmouseover='alert(\"XSS\")'>

    The fix, represented below, is to ensure that all user-provided string fields,
    especially 'website', are escaped using the default, secure behavior of
    `markupsafe.escape()`. The default setting (`quote=True`) correctly converts
    special characters like ' and " into their corresponding HTML entities
    (' and "), neutralizing the XSS payload.

    Args:
        comment_data (dict): A dictionary containing raw comment fields like
                             'text', 'author', and 'website'.

    Returns:
        dict: The comment data with fields properly escaped to prevent XSS.
    """
    sanitized_data = {}

    # The patched code applies strong, default escaping to all relevant fields.
    # This logic was applied in the places where comment data is prepared for
    # JSON responses to the client.
    for field in ["author", "website", "text"]:
        if field in comment_data and comment_data[field]:
            # The fix is to use markupsafe.escape() with its default `quote=True`.
            # This correctly escapes single quotes ('), double quotes ("),
            # ampersands (&), and angle brackets (<, >).
            sanitized_data[field] = markupsafe.escape(comment_data[field])
        else:
            sanitized_data[field] = comment_data.get(field)

    return sanitized_data

# --- Example demonstrating the effect of the fix ---

# Malicious payload designed to break out of a single-quoted HTML attribute.
malicious_payload = {
    "author": "Attacker",
    "website": "https://example.com' onmouseover='alert(\"XSS\")' data-dummy='",
    "text": "<script>alert('XSS')</script>"
}

# Apply the fixed serialization logic to the malicious payload.
safe_data = fixed_comment_serialization(malicious_payload)

# The resulting 'safe_data' dictionary now contains neutralized strings
# that are safe to render in an HTML context.
#
# print(safe_data)
#
# Expected output from the print statement:
# {
#     'author': 'Attacker',
#     'website': 'https://example.com' onmouseover='alert("XSS")' data-dummy='',
#     'text': '<script>alert('XSS')</script>'
# }
#
# As shown in the expected output, the single and double quotes in the 'website'
# field are converted to HTML entities, preventing the browser from interpreting
# them as closing delimiters for the href attribute or as part of a new attribute.

Payload

' onmouseover='alert("XSS")'

Cite this entry

@misc{vaitp:cve202627469,
  title        = {{Isso is vulnerable to stored XSS in comment fields via improper escaping.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-27469},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-27469/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::