JSTGTECH
← Back to blog

Exposed Kubernetes API Servers Are an RBAC Time Bomb

4 min read

You don’t need a zero-day to take over a Kubernetes cluster. You need an API server that answers on the public internet and an anonymous-auth flag nobody turned off. Aqua Nautilus researchers spent three months scanning for exactly that combination and found it everywhere: hundreds of organizations with clusters already under active attack, most of it opportunistic cryptomining riding in through anonymous access that had somehow been granted real privileges (CSO Online, Aqua Nautilus). This isn’t a rare edge case — Shadowserver’s internet-wide scans have repeatedly found north of 380,000 Kubernetes API servers responding publicly, the large majority in the US and Western Europe (Shadowserver, The Register). Exposure alone doesn’t mean compromise, but it’s the precondition every campaign in this space depends on, and the misconfigurations that turn “reachable” into “owned” are boringly common.

Root cause

Kubernetes has two places where “no credentials” quietly becomes “some credentials.” The first is the API server and kubelet’s anonymous-auth setting, which historically defaulted to enabled and maps unauthenticated requests to the system:anonymous user (or the system:unauthenticated group). By itself that’s supposed to be harmless — anonymous requests should have no RBAC permissions bound to them. In practice, Aqua’s kubelet research found operators binding that anonymous identity to real roles, sometimes admin-level ones, usually as a shortcut to get some internal tool or health check working without wiring up proper auth (Aqua Security). Once that binding exists, anyone who can reach the API server or the kubelet’s HTTPS port (10250) has exactly the access that role grants — no login required. Aqua’s kube-hunter tool has a dedicated check for this pattern precisely because it recurs so often across audited clusters (Aqua Security / kube-hunter).

The second gap is the default service account. Every pod gets one mounted automatically unless a manifest explicitly sets automountServiceAccountToken: false, and that token grants whatever RBAC role is bound to the account — which in a lot of real clusters is broader than anyone intended, because default service accounts accumulate permissions over time as people bind roles the fast way instead of the correct way. Layer that onto an internet-reachable control plane — sometimes because of a misconfigured cloud load balancer, sometimes because someone ran kubectl proxy --address=0.0.0.0 --accept-hosts='.*' on a bastion and forgot about it, a specific misconfiguration Aqua called out by name in its research — and a single unauthenticated HTTP request is enough to start probing.

Blast radius

The API server isn’t just another service — it’s the thing that can schedule code onto every node in the cluster. Anonymous or default-account access that includes pod-create permission lets an attacker launch a pod with a hostPath mount or privileged: true, which is a direct path off the container and onto the underlying node. From the node, the attacker can read the kubelet’s credentials and every service account token scheduled there, pivoting sideways into other namespaces the original access point never touched. In cloud-hosted clusters, pods often carry workload-identity credentials (IRSA on EKS, Workload Identity on GKE) or can reach the instance metadata service directly, so cluster compromise routinely becomes cloud-account compromise. This is also the exact class of risk that made CVE-2018-1002105 so severe a few years back — a bug that let unauthenticated requests reach the API server’s proxy layer and come out the other side with cluster-admin, a reminder that “you’re talking to the API server” and “you have privileged access” are one and the same threat model even without a misconfiguration involved (Tenable). In practice, most of what Aqua and others observe on these exposed clusters is quieter than that: TeamTNT- and Kinsing-style campaigns that drop XMRig miners across every node they can reach, plus at least one documented “RBAC buster” campaign that used the access specifically to plant a persistent backdoor role rather than just mine coins (Tigera, CSO Online).

Remediation

  • Disable anonymous auth. Run kubelets with --anonymous-auth=false and route real authentication through --client-ca-file or a token webhook; never bind system:anonymous or system:unauthenticated to any ClusterRole, including “harmless-looking” ones.
  • Stop exposing the control plane. Put the API server behind a private endpoint and security-group/authorized-network restrictions (EKS private endpoint access, GKE authorized networks, or equivalent) instead of a public load balancer with no source restriction.
  • Audit RBAC for least privilege, especially bindings on default service accounts — Kubernetes’ own docs have a good-practices guide for this and it’s worth running against every namespace, not just the obviously sensitive ones (Kubernetes docs).
  • Set automountServiceAccountToken: false on any pod that doesn’t actually call the Kubernetes API, so a compromised pod doesn’t automatically hand over a credential.
  • Lock down the kubelet port (10250) with network policies and firewall rules — it shouldn’t be reachable from outside the cluster network, full stop.
  • Turn on audit logging and alert on anonymous or unexpected-identity requests hitting the API server; that signal is cheap to collect and catches this exact pattern early.

The bigger lesson

None of this requires a novel exploit — every campaign cited here rode in on defaults nobody explicitly locked down. Kubernetes was built to be flexible for cluster operators first and secure-by-default second, and that tradeoff means the burden of hardening anonymous access, RBAC bindings, and network exposure sits entirely on whoever stood the cluster up. Attackers aren’t finding your cluster through cleverness; they’re running the same internet-wide scans Shadowserver publishes for free and checking whether you did the hardening step or skipped it. Assume they’ve already scanned you, and go verify which answer they got.

Related posts