GHSA-5587-2X54-JJ6H

Vulnerability from github – Published: 2026-07-17 21:46 – Updated: 2026-07-17 21:46
VLAI
Summary
Skipper's routesrv-no-auth component: All routesrv API Endpoints Lack Authentication
Details

Description

The routesrv component exposes the full cluster route topology (Ingress/RouteGroup configurations, backend URLs, filter chains, OAuth/OIDC callback paths) and cache-cluster topology (Redis/Valkey shard addresses) over plain HTTP with zero authentication. Any pod in the Kubernetes cluster can reach routesrv via its predictable DNS name and retrieve sensitive cluster-wide routing and cache infrastructure data.

Vulnerable Code

routesrv/routesrv.go:87-99,114-137 — all handler registrations on the main mux:

mux.Handle("/routes", b)          // eskipBytes.ServeHTTP — all route data
mux.Handle("/routes/{zone}", b)   // zone-scoped route data
mux.Handle("/swarm/redis/shards", rh)   // Redis cluster addresses
mux.Handle("/swarm/valkey/shards", vh)  // Valkey cluster addresses

routesrv/eskipbytes.go:134-196 — eskipBytes.ServeHTTP:

func (e *eskipBytes) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
    // ... only checks GET/HEAD method, NO auth check
    if r.Method != "GET" && r.Method != "HEAD" {
        w.WriteHeader(http.StatusMethodNotAllowed)
        return
    }
    // ... serves all route data immediately
}

routesrv/redishandler.go:28-41 — RedisHandler.ServeHTTP:

func (rh *RedisHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if r.Method != "GET" {
        w.WriteHeader(http.StatusMethodNotAllowed)
        return
    }
    // ... serves Redis cluster addresses immediately, NO auth check
}

routesrv/valkeyhandler.go:28-41 — ValkeyHandler.ServeHTTP:

func (vh *ValkeyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if r.Method != "GET" {
        w.WriteHeader(http.StatusMethodNotAllowed)
        return
    }
    // ... serves Valkey cluster addresses immediately, NO auth check
}

Attack Path

  1. Initial Compromise: Attacker compromises any pod in the Kubernetes cluster (via application CVE, supply-chain attack, malicious container image, etc.)
  2. Discovery: Attacker discovers routesrv via predictable Kubernetes DNS name: skipper-ingress-routesrv.kube-system.svc.cluster.local:9090 (documented at docs/tutorials/operations.md:108, docs/tutorials/ratelimit.md:137,197)
  3. Data Extraction without Auth:
  4. GET http://<routesrv>:9090/routes → All Ingress/RouteGroup configurations across ALL namespaces
  5. GET http://<routesrv>:9090/swarm/redis/shards → Redis cache cluster node addresses
  6. GET http://<routesrv>:9090/swarm/valkey/shards → Valkey cache cluster node addresses
  7. Subsequent Attacks: With cache cluster topology, attacker can perform direct cache-level attacks (ratelimit data manipulation, session data exfiltration)

Permission Boundary Analysis

The routesrv uses a ServiceAccount with cluster-wide RBAC to list Ingress (networking.k8s.io), RouteGroup (zalando.org), Endpoints, and Services across all namespaces (see clusterclient.go:648-653 fetchClusterState). The kube-apiserver requires proper ServiceAccount token + RBAC authorization for the Kubernetes API itself, but routesrv exposes the aggregated data over HTTP with zero authentication.

A compromised pod with limited RBAC (restricted to its own namespace) can bypass Kubernetes RBAC entirely by reading routesrv. This crosses the boundary from "namespace-scoped Kubernetes workload with restricted RBAC" to "full cluster route topology across all namespaces".

No NetworkPolicy manifests exist in the deploy/ directory. The default Kubernetes flat network model allows any pod to reach any service, further widening the attack surface.

Exposed Data

Endpoint Data Exposed Impact
GET /routes All ingress/routegroup backends: internal service URLs, filter chains (auth, rate limiting, OAuth, JWT, OPA policies), load balancer group membership Cluster-wide reconnaissance, targeted backend attacks
GET /routes/{zone} Zone-scoped subset of above route data Same, scoped
GET /swarm/redis/shards Redis cluster internal IP:port pairs Direct cache-level attacks, ratelimit data manipulation
GET /swarm/valkey/shards Valkey cluster internal IP:port pairs Same

Additionally, the data-plane client (eskipfile/remote.go:190-219) also performs plain HTTP GET with no credentials — only an ETag header is sent — confirming that no auth capability exists in the architecture at all.

Mitigation

  1. Add authentication to all routesrv HTTP endpoints (basic auth, bearer token, mTLS, or shared secret) via flag -route-server-filters=""
  2. Deploy Kubernetes NetworkPolicies restricting ingress to routesrv to only the data-plane skipper pod selectors
  3. Consider using mutual TLS authentication between data-plane and control-plane components

NetworkPolicy does not remove the missing-auth condition

Restrictive NetworkPolicies are a valid mitigation, but they are not an application-layer authentication mechanism. The security-relevant defect remains that routesrv serves control-plane-derived data to unauthenticated callers whenever network reachability exists.

Impact framing

This report does not rely on claiming direct integrity or availability impact. The verified issue is a confidentiality-focused control-plane exposure: route definitions, backend topology, filter-chain details, and Redis/Valkey shard addresses become readable to any reachable in-cluster client.

Resources

  • routesrv/routesrv.go:87-99 — handler registration (zero auth)
  • routesrv/eskipbytes.go:134-196 — route data handler (no auth)
  • routesrv/redishandler.go:28-41 — Redis shard handler (no auth)
  • routesrv/valkeyhandler.go:28-41 — Valkey shard handler (no auth)
  • dataclients/kubernetes/clusterclient.go:648-653 — fetchClusterState() — shows cluster-wide RBAC
  • eskipfile/remote.go:190-219 — data-plane client also has no auth capability
  • docs/tutorials/operations.md:108, docs/tutorials/ratelimit.md:137,197 — documented routesrv DNS name
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/zalando/skipper"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.27.13"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-54246"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-306"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-17T21:46:18Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "## Description\n\nThe `routesrv` component exposes the full cluster route topology (Ingress/RouteGroup configurations, backend URLs, filter chains, OAuth/OIDC callback paths) and cache-cluster topology (Redis/Valkey shard addresses) over plain HTTP with **zero authentication**. Any pod in the Kubernetes cluster can reach routesrv via its predictable DNS name and retrieve sensitive cluster-wide routing and cache infrastructure data.\n\n## Vulnerable Code\n\n**routesrv/routesrv.go:87-99,114-137** \u2014 all handler registrations on the main mux:\n```go\nmux.Handle(\"/routes\", b)          // eskipBytes.ServeHTTP \u2014 all route data\nmux.Handle(\"/routes/{zone}\", b)   // zone-scoped route data\nmux.Handle(\"/swarm/redis/shards\", rh)   // Redis cluster addresses\nmux.Handle(\"/swarm/valkey/shards\", vh)  // Valkey cluster addresses\n```\n\n**routesrv/eskipbytes.go:134-196** \u2014 `eskipBytes.ServeHTTP`:\n```go\nfunc (e *eskipBytes) ServeHTTP(rw http.ResponseWriter, r *http.Request) {\n    // ... only checks GET/HEAD method, NO auth check\n    if r.Method != \"GET\" \u0026\u0026 r.Method != \"HEAD\" {\n        w.WriteHeader(http.StatusMethodNotAllowed)\n        return\n    }\n    // ... serves all route data immediately\n}\n```\n\n**routesrv/redishandler.go:28-41** \u2014 `RedisHandler.ServeHTTP`:\n```go\nfunc (rh *RedisHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n    if r.Method != \"GET\" {\n        w.WriteHeader(http.StatusMethodNotAllowed)\n        return\n    }\n    // ... serves Redis cluster addresses immediately, NO auth check\n}\n```\n\n**routesrv/valkeyhandler.go:28-41** \u2014 `ValkeyHandler.ServeHTTP`:\n```go\nfunc (vh *ValkeyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n    if r.Method != \"GET\" {\n        w.WriteHeader(http.StatusMethodNotAllowed)\n        return\n    }\n    // ... serves Valkey cluster addresses immediately, NO auth check\n}\n```\n\n## Attack Path\n\n1. **Initial Compromise**: Attacker compromises any pod in the Kubernetes cluster (via application CVE, supply-chain attack, malicious container image, etc.)\n2. **Discovery**: Attacker discovers routesrv via predictable Kubernetes DNS name: `skipper-ingress-routesrv.kube-system.svc.cluster.local:9090` (documented at `docs/tutorials/operations.md:108`, `docs/tutorials/ratelimit.md:137,197`)\n3. **Data Extraction without Auth**: \n   - `GET http://\u003croutesrv\u003e:9090/routes` \u2192 All Ingress/RouteGroup configurations across ALL namespaces\n   - `GET http://\u003croutesrv\u003e:9090/swarm/redis/shards` \u2192 Redis cache cluster node addresses\n   - `GET http://\u003croutesrv\u003e:9090/swarm/valkey/shards` \u2192 Valkey cache cluster node addresses\n4. **Subsequent Attacks**: With cache cluster topology, attacker can perform direct cache-level attacks (ratelimit data manipulation, session data exfiltration)\n\n## Permission Boundary Analysis\n\nThe routesrv uses a ServiceAccount with **cluster-wide RBAC** to list Ingress (networking.k8s.io), RouteGroup (zalando.org), Endpoints, and Services across all namespaces (see `clusterclient.go:648-653` `fetchClusterState`). The kube-apiserver requires proper ServiceAccount token + RBAC authorization for the Kubernetes API itself, but **routesrv exposes the aggregated data over HTTP with zero authentication**.\n\nA compromised pod with limited RBAC (restricted to its own namespace) can bypass Kubernetes RBAC entirely by reading routesrv. This crosses the boundary from *\"namespace-scoped Kubernetes workload with restricted RBAC\"* to *\"full cluster route topology across all namespaces\"*.\n\nNo NetworkPolicy manifests exist in the `deploy/` directory. The default Kubernetes flat network model allows any pod to reach any service, further widening the attack surface.\n\n## Exposed Data\n\n| Endpoint | Data Exposed | Impact |\n|----------|-------------|--------|\n| `GET /routes` | All ingress/routegroup backends: internal service URLs, filter chains (auth, rate limiting, OAuth, JWT, OPA policies), load balancer group membership | Cluster-wide reconnaissance, targeted backend attacks |\n| `GET /routes/{zone}` | Zone-scoped subset of above route data | Same, scoped |\n| `GET /swarm/redis/shards` | Redis cluster internal IP:port pairs | Direct cache-level attacks, ratelimit data manipulation |\n| `GET /swarm/valkey/shards` | Valkey cluster internal IP:port pairs | Same |\n\nAdditionally, the data-plane client (`eskipfile/remote.go:190-219`) also performs plain HTTP GET with no credentials \u2014 only an ETag header is sent \u2014 confirming that no auth capability exists in the architecture at all.\n\n\n## Mitigation\n\n1. Add authentication to all routesrv HTTP endpoints (basic auth, bearer token, mTLS, or shared secret) via flag `-route-server-filters=\"\"`\n2. Deploy Kubernetes NetworkPolicies restricting ingress to routesrv to only the data-plane skipper pod selectors\n3. Consider using mutual TLS authentication between data-plane and control-plane components\n\n\n### NetworkPolicy does not remove the missing-auth condition\nRestrictive NetworkPolicies are a valid mitigation, but they are not an application-layer authentication mechanism. The security-relevant defect remains that routesrv serves control-plane-derived data to unauthenticated callers whenever network reachability exists.\n\n### Impact framing\nThis report does **not** rely on claiming direct integrity or availability impact. The verified issue is a confidentiality-focused control-plane exposure: route definitions, backend topology, filter-chain details, and Redis/Valkey shard addresses become readable to any reachable in-cluster client.\n\n## Resources\n\n- `routesrv/routesrv.go:87-99` \u2014 handler registration (zero auth)\n- `routesrv/eskipbytes.go:134-196` \u2014 route data handler (no auth)\n- `routesrv/redishandler.go:28-41` \u2014 Redis shard handler (no auth)\n- `routesrv/valkeyhandler.go:28-41` \u2014 Valkey shard handler (no auth)\n- `dataclients/kubernetes/clusterclient.go:648-653` \u2014 `fetchClusterState()` \u2014 shows cluster-wide RBAC\n- `eskipfile/remote.go:190-219` \u2014 data-plane client also has no auth capability\n- `docs/tutorials/operations.md:108`, `docs/tutorials/ratelimit.md:137,197` \u2014 documented routesrv DNS name",
  "id": "GHSA-5587-2x54-jj6h",
  "modified": "2026-07-17T21:46:18Z",
  "published": "2026-07-17T21:46:18Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/zalando/skipper/security/advisories/GHSA-5587-2x54-jj6h"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/zalando/skipper"
    },
    {
      "type": "WEB",
      "url": "https://github.com/zalando/skipper/releases/tag/v0.27.13"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Skipper\u0027s routesrv-no-auth component: All routesrv API Endpoints Lack Authentication"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…