Open WebUI's process_files_batch() endpoint missing ownership check, allows unauthorized file overwrite
High7.1CVE-2026-28788 · Published Mar 27, 2026 · updated Jul 13, 2026
### 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.da...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| open-webui PyPI | < 0.8.6 | 0.8.6 |
Details and references
### 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 ``` This returns metadata for all files in the KB, including their UUIDs. **Exploit (attacker):** ```bash python3 poc_exploit.py --url http://<host>:3000 --file-id <target-file-uuid> -t <attacker-jwt> ``` The PoC script: [poc_exploit.py](https://github.com/user-attachments/files/25470374/poc_exploit.py) 1. Authenticates as the attacker 2. Overwrites the target file via `POST /api/v1/retrieval/process/files/batch` with a canary payload containing a unique marker string 3. Reads the file back and confirms the attacker's content replaced the original **Verifying RAG poisoning:** After the overwrite, log in as any other user, start a chat with the poisoned knowledge base attached, and ask about the document. The model's response will include the attacker's canary string (`BOLA-<marker>`), confirming that attacker-controlled content reached the LLM and influenced the response. No special tooling is required. The script uses only Python 3 standard library (`urllib`). ### Impact **Who is
- CVSS 3.1
- CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L
- Severity from
- GitHub (reviewed advisory)
- Weakness
- CWE-639
- Also known as
- CVE-2026-28788, PYSEC-2026-2742
More Open WebUI advisories
All Open WebUI| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| May 8 | Open WebUI has an LDAP Empty Password Authentication Bypass | Critical9.1 | 0.9.0 |
| May 8 | open-webui Vulnerable to Stored XSS via Model Description | High7.3 | 0.9.0 |
| Apr 1 | Open WebUI has Broken Access Control in Tool Valves | High7.7 | 0.8.11 |
| Mar 27 | Open WebUI's Insecure Direct Object Reference (IDOR) allows access to other users' memories | Low3.1 | 0.8.6 |
| Mar 27 | Open WebUI has unauthorized deletion of knowledge files | Medium5.4 | 0.8.6 |
| Mar 27 | Open WebUI vulnerable to Path Traversal in `POST /api/v1/audio/transcriptions` | Medium4.3 | 0.8.6 |