CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



GHSA-7PPG-37FH-VCR6
9.8

Vector Injection? No, Just Regular Injection: Milvus Critical Auth Bypass

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 11, 2026·6 min read·6 visits

PoC Available

Executive Summary (TL;DR)

Milvus exposes its administrative REST API and a debug console on port 9091 by default. Due to missing middleware, requests to this port bypass all authentication. Attackers can create admin users, dump credentials, or execute arbitrary code using the default `by-dev` token.

In the gold rush of the AI revolution, vector databases like Milvus have become the vaults where companies store their most precious semantic data. But while everyone was worried about prompt injection or model poisoning, Milvus accidentally left the back door wide open. A critical vulnerability in the metrics and management interface (port 9091) exposes the entire administrative REST API without authentication. Worse, a debug endpoint allows arbitrary Go expression evaluation with a hardcoded default token. This isn't just a data leak; it's a full system compromise waiting to happen.

The Hook: The AI Vault with a Screen Door

We live in the age of RAG (Retrieval-Augmented Generation). Companies are dumping gigabytes of proprietary documents, customer chats, and trade secrets into vector databases like Milvus to make their LLMs smarter. Milvus is complex software; it has coordinators, proxies, data nodes, and query nodes. To manage this beast, it exposes various ports.

The main entrance, port 19530, handles the high-speed gRPC traffic for vector insertion and searching. This door is usually heavily guarded with authentication, TLS, and API gateways. But software needs to be observed. DevOps teams love metrics. So, Milvus provides a dedicated HTTP server for metrics and health checks on port 9091.

In theory, port 9091 should just reply with some harmless Prometheus-formatted stats. In practice, due to a copy-paste catastrophe in the initialization logic, port 9091 became a fully functional, unauthenticated administrative console. It’s the equivalent of a bank vault having a thermal exhaust port that leads directly to the main reactor—and the door is propped open with a brick.

The Flaw: Middleware Amnesia

The root cause of this vulnerability is a classic case of "Middleware Amnesia." In modern web frameworks like Gin (which Milvus uses), you define routes and then apply middleware to them. Middleware handles things like logging, rate limiting, and—crucially—authentication. If you forget to apply the middleware, the routes still work, they just stop checking ID at the door.

Inside internal/distributed/proxy/service.go, the Milvus developers set up the HTTP server. They correctly initialized the main API group. However, when setting up the metrics server (which runs on a separate port), they decided to register the same business logic handlers to it.

Why? Likely for convenience, so administrators could hit the REST API on the management port if needed. The fatal flaw was that while they copied the routes, they forgot to copy the apiv1.Use(httpserver.RequestHandlerFunc) line (or its equivalent auth check) to the metrics server context. This means GET /api/v1/collection on port 19530 asks for a password, but the exact same request on port 9091 rolls out the red carpet.

The Code: Anatomy of a blunder

Let's look at the smoking gun in internal/distributed/proxy/service.go. This is where the Proxy service spins up its HTTP listeners.

// registerHTTPServer register the http server
func (s *Server) registerHTTPServer() {
    // ... setup code ...
    metricsGinHandler := gin.Default()
    
    // 1. Create the API group on the METRICS handler
    apiv1 := metricsGinHandler.Group(apiPathPrefix) 
    
    // 2. Register the business logic handlers
    handlers := httpserver.NewHandlers(s.proxy)
    handlers.RegisterRoutesTo(apiv1) 
    
    // 3. Register the Proxy REST router
    if p, ok := s.proxy.(*proxy.Proxy); ok {
        p.RegisterRestRouter(apiv1)
    }
    
    // 4. Bind this handler to the metrics server
    mhttp.Register(&mhttp.Handler{
        Path:        mhttp.RootPath,
        Handler:     metricsGinHandler.Handler(),
    })
}

Notice what is missing? There is no authentication middleware attached to apiv1. In the standard HTTP server initialization function (not shown here, but present in the codebase), there is explicit logic that checks Params.ProxyCfg.AuthorizationEnabled and enforces token validation. Here, on the metrics path, the code simply mounts the full RegisterRoutesTo(apiv1) router directly to the open web.

The Exploit: God Mode via `by-dev`

If the unauthenticated API wasn't bad enough, Milvus includes a debug endpoint located at /expr. This endpoint allows the caller to evaluate arbitrary internal Go expressions. Yes, you read that right. It's essentially a web-based eval() for the running Go process.

Access to this endpoint is theoretically restricted by an auth parameter. However, the default value for this check is derived from etcd.rootPath. In almost every default installation, and in the Docker Compose examples provided by Milvus, this value is set to by-dev.

So, we have an unauthenticated Remote Code Execution (or at least Remote Configuration Execution) primitive. Here is how an attacker creates a "Kill Chain":

  1. Leak Secrets: The attacker calls /expr to read the global configuration memory. They can extract the MinIO SecretAccessKey (used for S3 storage) and the root user's password hash.
requests.get("http://target:9091/expr", params={
    "auth": "by-dev",
    "code": "param.MinioCfg.SecretAccessKey.GetValue()"
})
  1. Shutdown: If the attacker just wants to watch the world burn, they can call internal lifecycle methods.
requests.get("http://target:9091/expr", params={
    "auth": "by-dev",
    "code": "proxy.Stop()"
})
  1. Persistence: Using the unauthenticated REST API on the same port, the attacker creates a new admin user to ensure they have legitimate access to the main data port (19530) even if the metrics port is later patched.
requests.post("http://target:9091/api/v1/credential", json={
    "username": "apt_group",
    "password": "base64_encoded_payload"
})

The Impact: Total Recall (of Data)

The impact here is catastrophic for any organization running Milvus exposed to the internet or an untrusted internal network. Because Milvus is a vector database, it holds the meaning of your data.

Data Theft: An attacker can simply list all collections and query the vectors. If they have the MinIO keys (which they can get via /expr), they can bypass Milvus entirely and download the raw data segments from your object storage bucket.

Data Poisoning: In a RAG context, poisoning the vector store is a subtle and dangerous attack. An attacker could insert vectors that are semantically close to common queries but contain malicious or incorrect data. This forces the downstream LLM to hallucinate or provide harmful responses to users.

Infrastructure Compromise: The /expr endpoint can theoretically be used to modify file paths for logging. If an attacker can trick the server into writing logs to a script file (e.g., a web shell or a cron job location), they can achieve full OS-level Remote Code Execution.

The Mitigation: Close the Door

If you are running Milvus versions prior to 2.5.27 or 2.6.10, you are vulnerable. The patch applied by the maintainers simply removes the registration of the API router and the debug endpoint from the metrics port.

Immediate Steps:

  1. Upgrade: Update to v2.5.27 or v2.6.10 immediately.
  2. Firewall: Block TCP port 9091 from external access. This port should strictly be for internal Prometheus scraping, not public internet access.
  3. Config Hardening: Change the etcd.rootPath in your configuration from by-dev to a random high-entropy string. This acts as the password for the /expr endpoint if it is ever accidentally exposed again.

> [!NOTE] > Many Kubernetes deployments expose the metrics port via a Service for monitoring. Ensure your Ingress controller does NOT route external traffic to port 9091.

Official Patches

MilvusMilvus Release Notes v2.5.27
MilvusMilvus Release Notes v2.6.10

Fix Analysis (1)

Technical Appendix

CVSS Score
9.8/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Affected Systems

Milvus Vector Database

Affected Versions Detail

Product
Affected Versions
Fixed Version
Milvus
LF AI & Data
< 2.5.272.5.27
Milvus
LF AI & Data
>= 2.6.0, < 2.6.102.6.10
AttributeDetail
CWE IDCWE-306 (Missing Authentication for Critical Function)
CVSS Score9.8 (Critical)
Attack VectorNetwork (Port 9091)
Secondary WeaknessCWE-1188 (Insecure Default Initialization of Resource)
Exploit StatusPoC Available / High Reliability
Default Tokenauth=by-dev

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1552Unsecured Credentials
Credential Access
T1059.006Command and Scripting Interpreter: Python
Execution
CWE-306
Missing Authentication for Critical Function

The software does not perform any authentication for functionality that requires a provable user identity.

Known Exploits & Detection

ManualRequests to port 9091 with 'auth=by-dev' query parameter.

Vulnerability Timeline

Vulnerability Published
2026-02-11
Patched Versions 2.5.27 and 2.6.10 Released
2026-02-11

References & Sources

  • [1]GitHub Advisory
  • [2]OSV Database Entry

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.