Vulnerability GHSA-fmqp-4wfc-w3v7

High Risk
HIGH RISK
CVSS Score: 7.7
Score Range: 7.0–8.9
High severity vulnerabilities (CVSS 7.0–8.9). Serious vulnerabilities that should be prioritized soon after critical fixes.
6 months ago
April 14, 2026 at 08:06 PM UTC
Kyverno APICall SSRF Vulnerability Leading to Multi-Tenant Isolation Breach
v0.1.0 - v1.16.2-rc.1
v0.1.0 - v1.16.2-rc.1

Summary

Kyverno APICall SSRF Vulnerability Leading to Multi-Tenant Isolation Breach

Details

Summary

Kyverno's APICall feature contains a Server-Side Request Forgery (SSRF) vulnerability that allows users with Policy creation permissions to access arbitrary internal resources through Kyverno's high-privilege ServiceAccount. In multi-tenant Kubernetes environments, this constitutes a classic Confused Deputy problem: low-privilege tenants can steal sensitive data from other tenants (such as database passwords and API keys) and cloud platform IAM credentials, completely breaking tenant isolation. This vulnerability does not require cluster-admin privileges and can be exploited with only namespace-level Policy creation permissions.

Details

Vulnerability Mechanism

Kyverno's APICall feature allows Policies to fetch external data via HTTP requests. This feature does not validate target URLs when executing HTTP requests, leading to an SSRF vulnerability.

Source Point - User-Controlled URL

File: api/kyverno/v1/common_types.go, lines 247-250

type ServiceCall struct {
    // URL is the JSON web service URL
    URL string `json:"url"`  // User-controlled, no validation
    Headers []HTTPHeader `json:"headers,omitempty"`
    CABundle string `json:"caBundle,omitempty"`
}

The URL field is completely controlled by users through Policy configuration, with no validation mechanism to restrict target addresses.

Sink Point - HTTP Request Execution

File: pkg/engine/apicall/executor.go, lines 65-110

func (a *executor) executeServiceCall(ctx context.Context, apiCall *kyvernov1.APICall) ([]byte, error) {
    if apiCall.Service == nil {
        return nil, fmt.Errorf("missing service for APICall %s", [a.name](http://a.name/))
    }

    client, err := a.buildHTTPClient(apiCall.Service)
    if err != nil {
        return nil, err
    }

    req, err := a.buildHTTPRequest(ctx, apiCall)
    if err != nil {
        return nil, fmt.Errorf("failed to build HTTP request for APICall %s: %w", [a.name](http://a.name/), err)
    }

    // Line 80: Directly executes HTTP request without URL validation
    resp, err := client.Do(req)
    if err != nil {
        return nil, fmt.Errorf("failed to execute HTTP request for APICall %s: %w", [a.name](http://a.name/), err)
    }
    defer resp.Body.Close()

    // Read and return response content
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        // ...
    }

    return body, nil
}

Line 80's client.Do(req) directly executes the HTTP request without checking if the target URL is an internal IP address (like 169.254.169.254) or resources belonging to other tenants.

Confused Deputy Problem

In multi-tenant environments, Kyverno uses a cluster-wide high-privilege ServiceAccount to execute all APICall requests. When a low-privilege tenant creates a Policy containing malicious APICall directives, Kyverno executes these requests with its own high privileges, leading to privilege escalation.

Attack path:

Tenant A (namespace-level permissions)
  → Creates malicious Policy
  → Kyverno (cluster-wide high privileges)
  → Accesses Tenant B's Secrets / Cloud metadata service
  → Sensitive data leaked to PolicyReport
  → Tenant A reads PolicyReport to obtain data

PoC

Environment Setup

Prerequisites

  • Kubernetes cluster
  • Kyverno v1.16.0 installed
  • Mock cloud metadata service (optional, for testing cloud credential theft)

Step 1: Install Kyverno

kubectl create namespace kyverno
kubectl create -f https://github.com/kyverno/kyverno/releases/download/v1.16.0/install.yaml
kubectl wait --for=condition=Ready pods --all -n kyverno --timeout=300s

Verify installation:

$ kubectl get pods -n kyverno
NAME                                             READY   STATUS    RESTARTS   AGE
kyverno-admission-controller-5c84845f5-28hz5     1/1     Running   0          2m
kyverno-background-controller-59b7b8d686-7pqxl   1/1     Running   0          2m
kyverno-cleanup-controller-5fd988d64f-nsgdb      1/1     Running   0          2m
kyverno-reports-controller-546cb78fbc-2dd74      1/1     Running   0          2m

Step 2: Deploy Mock Metadata Service

Create file metadata-mock.yaml:

apiVersion: v1
kind: Namespace
metadata:
  name: metadata-mock
apiVersion: v1
kind: Service
metadata:
  name: metadata-mock
  namespace: metadata-mock
spec:
  selector:
    app: metadata-mock
  ports:
  - port: 80
    targetPort: 80

Deploy:

kubectl apply -f metadata-mock.yaml
kubectl wait --for=condition=Ready pods --all -n metadata-mock --timeout=120s

Step 3: Create Multi-Tenant Environment

Create two tenant namespaces:

kubectl create namespace tenant-a
kubectl create namespace tenant-b

Create sensitive data in tenant-b:

kubectl create secret generic db-credentials -n tenant-b \
  --from-literal=username=admin \
  --from-literal=password=SuperSecret123! \
  --from-literal=database=production-db

Create restricted ServiceAccount for tenant-a:

kubectl create serviceaccount tenant-a-admin -n tenant-a

Create file tenant-a-rbac.yaml:

apiVersion: [rbac.authorization.k8s.io/v1](http://rbac.authorization.k8s.io/v1)
kind: Role
metadata:
  name: policy-creator
  namespace: tenant-a
rules:
- apiGroups: ["[kyverno.io](http://kyverno.io/)"]
  resources: ["policies"]
  verbs: ["create", "get", "list", "update", "patch", "delete"]
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["create", "get", "list"]
- apiGroups: ["[wgpolicyk8s.io](http://wgpolicyk8s.io/)"]
  resources: ["policyreports"]
  verbs: ["get", "list"]

Timeline

Published
6 months ago
April 14, 2026 at 08:06 PM UTC
Fixed (v1.16.2)
9 months ago
January 09, 2026 at 11:36 AM UTC
Last Modified
3 days ago
October 14, 2026 at 11:11 AM UTC