← Back to Feed CACHED · 2026-05-17 09:42:19 · cache_key CVE-2025-29912
CVE-2021-25740 · CWE-441 · Disclosed 2021-09-20

A security issue was discovered with Kubernetes that could enable users to send network traffic to…

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
01 · The Real Story

This is a building concierge who will carry your package anywhere if you already hold the right service badge

CVE-2021-25740 is an architectural flaw in Kubernetes Services routing: a user who can create or modify Endpoints or EndpointSlices can point a Service, shared Ingress, or LoadBalancer at backend IPs they should not normally reach. Kubernetes stated all versions are affected, and later clarified in May 2026 that this remains an unfixed design trade-off, not a bug with a normal code patch path. The practical blast radius is cross-namespace traffic forwarding and policy bypass, mainly where shared ingress/load-balancer components are trusted by NetworkPolicy or other allow rules.

The vendor's LOW 3.1 rating is basically right. In the real world this requires an attacker who is already authenticated to the Kubernetes API and already has unusually strong write permissions on endpoint objects; that means this is usually post-initial-access lateral movement, not a clean external compromise path. The 2026 clarification that the issue is unfixed matters for asset hygiene and scanner interpretation, but it does not turn this into a higher-priority emergency.

"Low by label and by reality: this is a post-auth RBAC hygiene flaw, not an internet-grade patch fire."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Get namespace write capability

The attacker needs Kubernetes API credentials plus permission to create or update Endpoints or EndpointSlices in a namespace, typically via edit, admin, or a custom Role/ClusterRole. In practice this is usually exercised with kubectl or direct Kubernetes API calls, not a specialized exploit kit.
Conditions required:
  • Authenticated access to the Kubernetes API
  • RBAC allows create/update/patch on endpoints or endpointslices
Where this breaks in practice:
  • Most enterprises do not hand endpoint-object write to random users
  • Kubernetes v1.22+ new clusters removed default Endpoints write from built-in admin/edit roles
  • Attack dies immediately if RBAC was reconciled or endpoint writes are limited to controllers
Detection/coverage: Standard vulnerability scanners are weak here because exposure depends on live RBAC, not package version. kubectl auth can-i checks and RBAC audits are the right detection path.
STEP 02

Repoint service backends

Using kubectl edit, kubectl patch, or raw API requests, the attacker modifies an Endpoints or EndpointSlice object so traffic for an allowed Service resolves to IPs of a different backend. Datadog published a working proof of concept showing this redirection model against shared ingress/load-balancer patterns.
Conditions required:
  • A target Service path exists through a shared Ingress or LoadBalancer, or another trusted proxy path
  • Attacker can identify victim backend IPs or another sensitive destination
Where this breaks in practice:
  • Backend IP discovery is not always trivial
  • Some controllers overwrite manual changes quickly
  • Many clusters are not meaningfully multi-tenant, so cross-namespace value may be limited
Detection/coverage: Kubernetes audit logs can catch unexpected update/patch events on endpoints or endpointslices. Few commercial scanners model controller overwrite behavior.
STEP 03

Abuse a trusted frontend hop

The shared Ingress, proxy, or LoadBalancer becomes the confused deputy: it has legitimate reachability to both attacker-owned and victim backends, so it forwards traffic the attacker could not send directly. This can bypass NetworkPolicy assumptions and even controls like LoadBalancerSourceRanges when those controls trust the frontend component.
Conditions required:
  • Frontend component is trusted by the victim path
  • Routing layer actually consults the modified backend object
Where this breaks in practice:
  • If each tenant has a dedicated ingress/load balancer, the cross-tenant path largely disappears
  • Well-segmented architectures avoid shared trust chokepoints
  • Some ingress implementations do not support the risky forwarding patterns the advisory warns about
Detection/coverage: Network sensors see normal frontend-to-backend traffic, so this often looks like expected service traffic unless correlated with recent endpoint-object changes.
STEP 04

Reach otherwise blocked workload

The attacker now sends requests through the allowed Service/Ingress path and receives responses from a backend in another namespace or another protected location. The usual outcome is unauthorized application-layer access or data exposure, not node takeover or cluster-admin compromise.
Conditions required:
  • Victim application accepts traffic from the trusted frontend
  • Sensitive data or actions are exposed at the application layer
Where this breaks in practice:
  • Application auth still may block the attacker
  • Blast radius is bounded to what the redirected backend exposes
  • This is traffic redirection, not arbitrary code execution
Detection/coverage: Look for mismatches between Service ownership and backend IP membership, plus unusual cross-namespace request patterns originating from ingress or load-balancer components.
03 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo public evidence of active exploitation found in CISA KEV, and the Kubernetes advisory asks defenders to report exploitation if seen. This looks like a known architectural abuse path, not a campaign driver.
Proof-of-concept availabilityYes, public PoC exists. Datadog Security Labs states a full proof of concept is available and walks the exploit mechanics for shared ingress/load-balancer environments.
EPSS0.00519 (~0.52%), roughly 66.9th percentile based on current third-party EPSS displays. That is low absolute probability even if the percentile is middling.
KEV statusNot KEV-listed. No CISA KEV entry as of the catalog state consulted; no known due date because it is absent from KEV.
CVSS vector reality checkCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N is directionally fair. The important part is PR:L plus AC:H: the attack is network-reachable *only after* the attacker already has API access and the right RBAC grants.
Affected versionsAll Kubernetes versions are affected. Kubernetes clarified on 2026-05-26 that this CVE remains an unfixed architectural issue across all versions, with record corrections scheduled for 2026-06-01.
Fixed versions / backportsNo upstream patched version exists. New clusters created on Kubernetes v1.22+ do not include default Endpoints write in built-in admin/edit roles, but that is a configuration hardening change, not a full vulnerability fix; upgraded older clusters can retain the legacy grants until reconciled.
Exposure populationNot internet-scannable in the usual sense. Exposure is driven by RBAC and multi-tenant shared-routing design, not by an externally fingerprintable vulnerable listener. Shodan/Censys-style counts are therefore poor prioritization signals here; the real question is who can write endpoint objects in your clusters.
Disclosure / reportingDisclosed 2021-09-20 in NVD/CVE publication flow; the public Kubernetes issue was opened 2021-07-14. The 2026 Kubernetes clarification credits QiQi Xu, Javier Provecho, and others for identifying these broader architectural risks.
04 · The Call

noisgate verdict.

Final Verdict
= UNCHANGED to LOW (3.1/10)

The single decisive factor is that exploitation requires authenticated Kubernetes API access plus endpoint-object write permissions, which makes this a post-auth RBAC abuse case rather than an internet-reachable compromise path. Even where the routing trick works, the usual impact is limited to unauthorized network reachability through shared ingress/load-balancer trust, not cluster takeover.

HIGH Attack-chain friction and severity bucket
MEDIUM How often shared-ingress multi-tenant conditions exist in enterprise fleets

Why this verdict

  • Start from the vendor baseline: Kubernetes scored it LOW 3.1, and that is already close to the real-world risk because the attack is high-friction and confidentiality-only.
  • Downward pressure from attacker position: this requires an attacker who is already authenticated to the Kubernetes API and already authorized to write Endpoints or EndpointSlices; that implies prior compromise, delegated tenant access, or unusually broad developer RBAC.
  • Downward pressure from exposure population: new clusters created on v1.22+ no longer grant default Endpoints write through built-in admin/edit roles, so the reachable population is materially smaller unless you upgraded an older cluster or reintroduced the permission.
  • Downward pressure from blast radius: successful exploitation redirects traffic through a trusted frontend path and usually yields lateral application access or data exposure, not RCE, node compromise, or cluster-admin by itself.
  • Upward pressure that keeps it from IGNORE: the issue is architectural and unfixed across all versions, and shared-ingress/multi-tenant clusters still exist; if your tenants can write endpoint objects, the control failure is real, not theoretical.

Why not higher?

This is not unauthenticated remote exploitation, and it is not a bug an internet scanner can mass-weaponize against arbitrary clusters. The chain depends on preexisting RBAC mistakes or permissive tenancy design, then on a shared frontend that is trusted to reach the victim backend. That is too much compounded friction for MEDIUM or HIGH.

Why not lower?

It is still a genuine policy-bypass primitive, not paperwork. In clusters where developers or tenants can write endpoint objects, a shared ingress or load balancer can be turned into a lateral-movement relay that defeats intended namespace isolation. Because Kubernetes has now clarified that the issue remains unfixed across all versions, defenders should keep it in RBAC hardening scope instead of dismissing it as obsolete.

05 · Compensating Control

What to do — in priority order.

  1. Audit endpoint-object writers — Enumerate every subject that can create, update, patch, or delete endpoints and endpointslices across all namespaces, then remove those rights from broad user-facing roles. For a LOW verdict there is no SLA (treat as backlog hygiene), but because there is no patch path, this should still be handled in your next normal Kubernetes access-control review.
  2. Reconcile legacy system:aggregate-to-edit grants — If the cluster was created before v1.22 or upgraded from older releases, manually reconcile the built-in aggregate role so admin/edit no longer inherit Endpoints write by default. There is no SLA (treat as backlog hygiene) for LOW, but upgraded clusters are where this issue most often survives unnoticed.
  3. Create purpose-built roles for controllers only — If some automation genuinely needs endpoint-object writes, carve that into tightly scoped Roles or ClusterRoles bound only to the specific service accounts that require it. This prevents human users and general tenant roles from inheriting a lateral-routing primitive while preserving application behavior.
  4. Review shared ingress and ExternalName patterns — Validate whether your ingress/load-balancer design lets one tenant's path reach backends owned by another, and disable risky forwarding patterns such as unneeded ExternalName handling where supported. In LOW/backlog terms this is architecture hygiene, but it meaningfully reduces the blast radius of any remaining RBAC slip.
  5. Alert on endpoint mutations — Add Kubernetes audit detections for unexpected writes to endpoints and endpointslices, especially by human users, CI identities, or tenant service accounts. There is no SLA (treat as backlog hygiene) for LOW, but this control is your best shot at catching exploitation because version scanners do not model live RBAC.
What doesn't work
  • NetworkPolicy alone doesn't solve this when the target already trusts the shared ingress/load-balancer path; the advisory explicitly calls out that the frontend can become the bypass channel.
  • Container image scanning is irrelevant because this is not a vulnerable package version you replace in an image; it is a control-plane design and RBAC problem.
  • Perimeter WAFs won't help much because the abuse usually rides legitimate frontend-to-backend application traffic inside the cluster after routing has already been manipulated.
06 · Verification

Crowdsourced verification payload.

Run this from an auditor workstation or admin jump host that already has kubectl configured for the target cluster. Invoke it as python3 cve_2021_25740_check.py; it needs cluster-wide read access to RBAC objects (ClusterRole, Role, ClusterRoleBinding, RoleBinding) and should ideally run with cluster-admin or equivalent read permissions.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/usr/bin/env python3
# CVE-2021-25740 exposure check for Kubernetes
# Checks for non-system subjects bound to roles that can write Endpoints or EndpointSlices.
# Output: VULNERABLE / PATCHED / UNKNOWN
# Exit codes: 1 vulnerable, 0 patched, 2 unknown

import json
import subprocess
import sys
from typing import Dict, List, Tuple

WRITE_VERBS = {"create", "update", "patch", "delete", "deletecollection", "*"}
TARGETS = [
    ("", "endpoints"),
    ("discovery.k8s.io", "endpointslices"),
]


def run_kubectl(args: List[str]) -> dict:
    cmd = ["kubectl"] + args + ["-o", "json"]
    try:
        p = subprocess.run(cmd, capture_output=True, text=True, check=False)
    except FileNotFoundError:
        print("UNKNOWN: kubectl not found")
        sys.exit(2)
    if p.returncode != 0:
        print(f"UNKNOWN: kubectl failed: {' '.join(cmd)} :: {p.stderr.strip()}")
        sys.exit(2)
    try:
        return json.loads(p.stdout)
    except json.JSONDecodeError:
        print(f"UNKNOWN: invalid JSON from kubectl: {' '.join(cmd)}")
        sys.exit(2)


def rule_is_risky(rule: dict) -> bool:
    api_groups = set(rule.get("apiGroups", []))
    resources = set(rule.get("resources", []))
    verbs = set(rule.get("verbs", []))
    for api_group, resource in TARGETS:
        if (api_group in api_groups or "*" in api_groups) and (resource in resources or "*" in resources):
            if WRITE_VERBS & verbs:
                return True
    return False


def collect_risky_roles(clusterroles: dict, roles: dict) -> Tuple[Dict[str, dict], Dict[Tuple[str, str], dict]]:
    risky_clusterroles = {}
    risky_roles = {}

    for item in clusterroles.get("items", []):
        if any(rule_is_risky(r) for r in item.get("rules", [])):
            risky_clusterroles[item["metadata"]["name"]] = item

    for item in roles.get("items", []):
        if any(rule_is_risky(r) for r in item.get("rules", [])):
            ns = item["metadata"]["namespace"]
            name = item["metadata"]["name"]
            risky_roles[(ns, name)] = item

    return risky_clusterroles, risky_roles


def subject_is_non_system(subject: dict) -> bool:
    kind = subject.get("kind", "")
    name = subject.get("name", "")
    namespace = subject.get("namespace", "")

    if kind == "Group" and name.startswith("system:"):
        return False
    if kind == "User" and name.startswith("system:"):
        return False
    if kind == "ServiceAccount" and namespace == "kube-system":
        return False
    return True


def format_subject(subject: dict) -> str:
    kind = subject.get("kind", "?")
    name = subject.get("name", "?")
    ns = subject.get("namespace")
    return f"{kind}:{ns + '/' if ns else ''}{name}"


def main() -> int:
    # Basic connectivity test
    version = subprocess.run(["kubectl", "version", "--request-timeout=10s"], capture_output=True, text=True)
    if version.returncode != 0:
        print(f"UNKNOWN: cannot talk to cluster: {version.stderr.strip()}")
        return 2

    clusterroles = run_kubectl(["get", "clusterroles"])
    roles = run_kubectl(["get", "roles", "-A"])
    crbs = run_kubectl(["get", "clusterrolebindings"])
    rbs = run_kubectl(["get", "rolebindings", "-A"])

    risky_clusterroles, risky_roles = collect_risky_roles(clusterroles, roles)

    findings = []

    for item in crbs.get("items", []):
        ref = item.get("roleRef", {})
        if ref.get("kind") == "ClusterRole" and ref.get("name") in risky_clusterroles:
            for subj in item.get("subjects", []):
                if subject_is_non_system(subj):
                    findings.append({
                        "binding": f"ClusterRoleBinding/{item['metadata']['name']}",
                        "role": f"ClusterRole/{ref.get('name')}",
                        "subject": format_subject(subj),
                    })

    for item in rbs.get("items", []):
        ns = item["metadata"].get("namespace", "")
        ref = item.get("roleRef", {})
        risky = False
        role_label = ""
        if ref.get("kind") == "ClusterRole" and ref.get("name") in risky_clusterroles:
            risky = True
            role_label = f"ClusterRole/{ref.get('name')}"
        elif ref.get("kind") == "Role" and (ns, ref.get("name")) in risky_roles:
            risky = True
            role_label = f"Role/{ns}/{ref.get('name')}"
        if risky:
            for subj in item.get("subjects", []):
                if subject_is_non_system(subj):
                    findings.append({
                        "binding": f"RoleBinding/{ns}/{item['metadata']['name']}",
                        "role": role_label,
                        "subject": format_subject(subj),
                    })

    if findings:
        print("VULNERABLE: non-system subjects can write Endpoints or EndpointSlices")
        for f in findings[:25]:
            print(f"- {f['subject']} via {f['binding']} -> {f['role']}")
        if len(findings) > 25:
            print(f"- ... {len(findings) - 25} additional risky bindings omitted")
        return 1

    print("PATCHED: no non-system subject bindings to Roles/ClusterRoles with endpoint-object write permissions were found")
    return 0


if __name__ == "__main__":
    sys.exit(main())
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning, treat this as Kubernetes RBAC hygiene, not a fleetwide emergency patch event. There is no noisgate mitigation SLA and no noisgate remediation SLA for a LOW verdict, and there is no upstream patch anyway; fold it into your next platform access-control review, audit who can write Endpoints/EndpointSlices, reconcile legacy pre-1.22 role grants, and close the gap in the next normal maintenance cycle rather than burning an out-of-band patch window.

Sources

  1. Kubernetes GitHub issue #103675
  2. Kubernetes security advisory discussion
  3. NVD CVE-2021-25740
  4. Kubernetes blog: Reconciling the Past: Correcting Records for Unfixed Kubernetes CVEs
  5. Kubernetes RBAC documentation
  6. CISA Known Exploited Vulnerabilities Catalog
  7. Datadog Security Labs: Unpatchable Vulnerabilities of Kubernetes: CVE-2021-25740
  8. Wiz vulnerability database entry for CVE-2021-25740
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.