GHSA-CWXQ-RC9X-2JVV

Vulnerability from github – Published: 2026-07-17 18:14 – Updated: 2026-07-17 18:14
VLAI
Summary
Skipper: Unbounded Request Body Read in Admission Webhook Causes Memory Exhaustion DoS
Details

Summary

The Kubernetes admission webhook handler reads the entire request body using io.ReadAll(r.Body) without any size limit. Any client that can reach the webhook port within the cluster can send a multi-GB payload, causing the skipper process to exhaust memory and be OOM-killed. This disrupts all Kubernetes admission control, potentially blocking all pod creation and updates.

Vulnerable Code

// dataclients/kubernetes/admission/admission.go:76
body, err := io.ReadAll(r.Body)  // <-- NO SIZE LIMIT
if err != nil {
    log.Errorf("Failed to read request: %v", err)
    w.WriteHeader(http.StatusInternalServerError)
    invalidRequests.WithLabelValues(admitterName).Inc()
    return
}

var review admissionReview
err = json.Unmarshal(body, &review)

For comparison, the OPA filter has a body size limit:

// filters/openpolicyagent/openpolicyagent.go:68-70
const DefaultMaxRequestBodySize = 1 << 20 // 1MB

// OPA uses a bufferedBodyReader with size limits

Attack Path

  1. Attacker identifies the admission webhook endpoint (default: :9443/admission or configured path)
  2. Attacker sends: POST /admission HTTP/1.1, Content-Type: application/json with a multi-GB request body
  3. io.ReadAll(r.Body) allocates unbounded memory for the entire body
  4. Skipper process is OOM-killed by the Kubernetes kubelet

Permission Boundary Analysis

  • Attacker: Any client with network access to the admission webhook port within the Kubernetes cluster
  • Boundary crossed: Memory safety — unbounded allocation from attacker-controlled input
  • Preconditions: Admission webhook endpoint must be network-reachable (default Kubernetes deployment exposes it within cluster network)
  • Comparison: OPA filter has DefaultMaxRequestBodySize (1MB) and semaphore-based memory limit; admission handler has neither

Evidence

File Lines Description
dataclients/kubernetes/admission/admission.go 76 io.ReadAll(r.Body) without size limit
filters/openpolicyagent/openpolicyagent.go 68-70 OPA filter has DefaultMaxRequestBodySize = 1MB
filters/openpolicyagent/openpolicyagent.go 1333-1336 OPA uses bufferedBodyReader with size limits

Tests

  • dataclients/kubernetes/admission/admission_test.go exists but does not test body size limits

Impact

The admission webhook handler reads the entire request body using io.ReadAll(r.Body) without a size limit. An attacker with in-cluster network access and a valid Kubernetes client certificate can send a multi-GB payload to the webhook endpoint, causing the skipper process to exhaust memory and be OOM-killed. This disrupts admission control for Ingress and RouteGroup resources until the process is automatically restarted by the kubelet.

Scope of impact: Ingress and RouteGroup admission only — not pod creation or other admission controllers.

Recovery: Kubernetes automatically restarts the OOM-killed process, limiting downtime.

Prerequisites: (1) In-cluster network access to the webhook port, (2) valid Kubernetes client certificate.

Mitigation

  1. Add http.MaxBytesReader or equivalent body size limit before io.ReadAll
  2. Follow the OPA filter pattern: define DefaultMaxRequestBodySize and use a buffered reader with size limits
  3. Add a configurable --admission-max-body-size flag
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/zalando/skipper"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.26.22"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-54247"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-770"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-17T18:14:08Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "## Summary\n\nThe Kubernetes admission webhook handler reads the entire request body using `io.ReadAll(r.Body)` without any size limit. Any client that can reach the webhook port within the cluster can send a multi-GB payload, causing the skipper process to exhaust memory and be OOM-killed. This disrupts all Kubernetes admission control, potentially blocking all pod creation and updates.\n\n## Vulnerable Code\n\n```go\n// dataclients/kubernetes/admission/admission.go:76\nbody, err := io.ReadAll(r.Body)  // \u003c-- NO SIZE LIMIT\nif err != nil {\n    log.Errorf(\"Failed to read request: %v\", err)\n    w.WriteHeader(http.StatusInternalServerError)\n    invalidRequests.WithLabelValues(admitterName).Inc()\n    return\n}\n\nvar review admissionReview\nerr = json.Unmarshal(body, \u0026review)\n```\n\nFor comparison, the OPA filter has a body size limit:\n\n```go\n// filters/openpolicyagent/openpolicyagent.go:68-70\nconst DefaultMaxRequestBodySize = 1 \u003c\u003c 20 // 1MB\n\n// OPA uses a bufferedBodyReader with size limits\n```\n\n## Attack Path\n\n1. Attacker identifies the admission webhook endpoint (default: `:9443/admission` or configured path)\n2. Attacker sends: `POST /admission HTTP/1.1, Content-Type: application/json` with a multi-GB request body\n3. `io.ReadAll(r.Body)` allocates unbounded memory for the entire body\n4. Skipper process is OOM-killed by the Kubernetes kubelet\n\n## Permission Boundary Analysis\n\n- **Attacker**: Any client with network access to the admission webhook port within the Kubernetes cluster\n- **Boundary crossed**: Memory safety \u2014 unbounded allocation from attacker-controlled input\n- **Preconditions**: Admission webhook endpoint must be network-reachable (default Kubernetes deployment exposes it within cluster network)\n- **Comparison**: OPA filter has `DefaultMaxRequestBodySize` (1MB) and semaphore-based memory limit; admission handler has neither\n\n## Evidence\n\n| File | Lines | Description |\n|------|-------|-------------|\n| `dataclients/kubernetes/admission/admission.go` | 76 | `io.ReadAll(r.Body)` without size limit |\n| `filters/openpolicyagent/openpolicyagent.go` | 68-70 | OPA filter has `DefaultMaxRequestBodySize` = 1MB |\n| `filters/openpolicyagent/openpolicyagent.go` | 1333-1336 | OPA uses `bufferedBodyReader` with size limits |\n\n## Tests\n\n- `dataclients/kubernetes/admission/admission_test.go` exists but **does not test body size limits**\n\n## Impact\n\nThe admission webhook handler reads the entire request body using io.ReadAll(r.Body) without a size limit. An attacker with in-cluster network access and a valid Kubernetes client certificate can send a multi-GB payload to the webhook endpoint, causing the skipper process to exhaust memory and be OOM-killed. This disrupts admission control for Ingress and RouteGroup resources until the process is automatically restarted by the kubelet.\n\nScope of impact: Ingress and RouteGroup admission only \u2014 not pod creation or other admission controllers.\n\nRecovery: Kubernetes automatically restarts the OOM-killed process, limiting downtime.\n\nPrerequisites: (1) In-cluster network access to the webhook port, (2) valid Kubernetes client certificate.\n\n## Mitigation\n\n1. Add `http.MaxBytesReader` or equivalent body size limit before `io.ReadAll`\n2. Follow the OPA filter pattern: define `DefaultMaxRequestBodySize` and use a buffered reader with size limits\n3. Add a configurable `--admission-max-body-size` flag",
  "id": "GHSA-cwxq-rc9x-2jvv",
  "modified": "2026-07-17T18:14:08Z",
  "published": "2026-07-17T18:14:08Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/zalando/skipper/security/advisories/GHSA-cwxq-rc9x-2jvv"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/zalando/skipper"
    },
    {
      "type": "WEB",
      "url": "https://github.com/zalando/skipper/releases/tag/v0.26.22"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Skipper: Unbounded Request Body Read in Admission Webhook Causes Memory Exhaustion DoS"
}



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…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Loading…