critical · 9.6CVE-2026-54725Jul 31, 2026

CVE-2026-54725: vault-secrets-webhook Annotation SSRF and ServiceAccount Token Theft

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

The bank-vaults webhook blindly uses an attacker-supplied Vault address from a ConfigMap or Secret annotation, letting anyone who can create those resources force the webhook to make arbitrary HTTP…

Packagegithub.com/bank-vaults/vault-secrets-webhook
Ecosystemgo
Affected<= 1.22.2
Fixed in1.23.1
CVE-2026-54725: vault-secrets-webhook Annotation SSRF and ServiceAccount Token Theft

The problem

The vault-secrets-webhook admission handler reads the vault.security.banzaicloud.io/vault-addr annotation from any ConfigMap or Secret and passes it straight to the Vault client with no URL validation, allowlist, or RFC-1918 filter (pkg/webhook/config.go:102-107).

When the object contains at least one vault:... value, the webhook opens a live HTTP connection to that address synchronously during the admission review, entirely inside the webhook server process.

The webhook also holds a cluster-wide serviceaccounts/token:create ClusterRole grant. If vault-serviceaccount is also annotated, the webhook calls the Kubernetes TokenRequest API to mint a JWT for the named ServiceAccount and then POSTs it to the attacker-supplied address (/v1/auth/<path>/login).

The combined effect is arbitrary outbound SSRF from a privileged cluster component plus exfiltration of SA JWTs that can be replayed against the real Vault.

Proof of concept

A working proof-of-concept for CVE-2026-54725 in github.com/bank-vaults/vault-secrets-webhook, with the exact payload below.

text
apiVersion: v1
kind: ConfigMap
metadata:
  name: ssrf-poc
  namespace: tenant-ns
  annotations:
    vault.security.banzaicloud.io/vault-addr: "http://169.254.169.254/latest/meta-data/"
    vault.security.banzaicloud.io/vault-skip-verify: "true"
    vault.security.banzaicloud.io/vault-serviceaccount: "high-priv-sa"
data:
  secret-key: "vault:secret/data/test#value"

The root cause is an unconditional annotation read at pkg/webhook/config.go:102-107: vaultConfig.Addr = value with no scheme check, no hostname allowlist, and no private-range filter. The vault-skip-verify: true annotation then sets InsecureSkipVerify: true, removing the TLS certificate barrier entirely.

On any ConfigMap or Secret that contains a vault: prefixed value, MutateConfigMap / MutateSecret call newVaultClient, which sets clientConfig.Address = vaultConfig.Addr (webhook.go:285) and immediately dials the attacker host. The SA token path compounds the impact: the webhook calls CoreV1().ServiceAccounts(...).CreateToken(...) using its cluster-wide RBAC grant, then POSTs the resulting JWT to the attacker address, enabling credential replay against real Vault.

This maps to CWE-918 (Server-Side Request Forgery). The patch (commit 76db459) adds URL validation and a configurable allowlist against which vault-addr values are checked before the client is instantiated.

The fix

Upgrade to vault-secrets-webhook v1.23.1. The fix in commit 76db45976fee0f54cafd94dffa425e6b542f65a0 adds allowlist validation of the vault-addr annotation value before it is used to construct a Vault client, rejecting addresses that are not explicitly permitted by the operator.

As a defense-in-depth measure, restrict which namespaces the webhook watches using namespace selectors, and apply egress network policies on the webhook pod to block outbound connections to cloud IMDS ranges (169.254.169.254, fd00:ec2::254) and RFC-1918 addresses.

Reporter not attributed.

References: [1][2][3][4]

Related research