GitHub Security Advisory: GHSA-jjp7-g2jw-wh3j
Package: open-webui (PIP)
Severity: HIGH (CVSS 7.1)
CWE: CWE-639: Authorization Bypass Through User-Controlled Key
Vulnerable versions: < 0.8.6
Patched in: 0.8.6

Summary: Open WebUI's process_files_batch() endpoint missing ownership check, allows unauthorized file overwrite

Details:
### Summary

Any authenticated user can overwrite any file's content by ID through the `POST /api/v1/retrieval/process/files/batch` endpoint. The endpoint performs no ownership check, so a regular user with read access to a shared knowledge base can obtain file UUIDs via `GET /api/v1/knowledge/{id}/files` and then overwrite those files, escalating from read to write. The overwritten content is served to the LLM via RAG, meaning the attacker controls what the model tells other users.

### Details

The `process_files_batch()` function in `backend/open_webui/routers/retrieval.py` appears to be designed as an internal helper. The knowledge base router (`add_files_to_knowledge_batch()` in `knowledge.py`) imports and calls it directly after performing its own ownership and access control checks. The frontend never calls the retrieval route directly; all legitimate UI flows go through the knowledge base wrapper.

However, the function is also exposed as a standalone HTTP endpoint via `@router.post(...)`. This direct route only requires `get_verified_user` (any authenticated user) and performs no ownership check of its own:

```python
for file in form_data.files:
    text_content = file.data.get("content", "")  # attacker-controlled

    file_updates.append(FileUpdateForm(
        hash=calculate_sha256_string(text_content),
        data={"content": text_content},            # written to DB
    ))

for file_update, file_result in zip(file_updates, file_results):
    Files.update_file_by_id(id=file_result.file_id, form_data=file_update)
    #                       ^^^ no ownership check
```

There is no verification that `file.user_id == user.id` before the write. Any authenticated user who knows a file UUID can overwrite that file.

**How an attacker obtains file UUIDs:**

Same as with read access, any user who can see a knowledge base can retrieve file IDs for every document in it via `GET /api/v1/knowledge/{id}/files`. In deployments where knowledge bases are shared across teams, this gives any regular user a list of valid targets.

**Suggested fix:** Add an ownership check before writing:

```python
for file in form_data.files:
    db_file = Files.get_file_by_id(file.id)
    if not db_file or (db_file.user_id != user.id and user.role != "admin"):
        file_errors.append(BatchProcessFilesResult(
            file_id=file.id, status="failed",
            error="Permission denied: not file owner",
        ))
        continue
```

**Classification:**
- CWE-639: Authorization Bypass Through User-Controlled Key
- OWASP API1:2023: Broken Object Level Authorization

Tested on Open WebUI **0.8.3** using a default Docker configuration.

### PoC

**Prerequisites:**
- Default Open WebUI installation (Docker: `ghcr.io/open-webui/open-webui:main`)
- An admin or user creates a knowledge base with shared read access and uploads a file
- A regular user account exists (the attacker)

**Obtaining the file UUID (attacker):**

```
GET /api/v1/knowledge/{kb_id}/files
```