Kubernetes Under Siege — 13 798 Probes Across 9 K8s Ports in 7 Days
Our Kubernetes honeypot on sensor3 (slim, public IPv4) emulates a
single-node K8s 1.26 cluster across nine ports: kube-apiserver
(insecure 8080 + secure 6443), kubelet read-write (10250) and read-only
(10255), etcd (2379 / 2380), kubelet-internal Cadvisor (4194), nodeport
range (30000), and the Prometheus scrape endpoint (9090). In 7 days we
logged 13 798 hits from 1 072 unique source
IPs — one of the most active surfaces in the fleet on
a per-port basis. The etcd port leads, the apiserver is a close
second, and the kubelet sees a mix of read-only enumeration and
POST /run-style command-execution probes.
The nine ports, ranked by traffic
| Port | Component | What attackers want | 7-day hits | Unique IPs |
|---|---|---|---|---|
2379 |
etcd Cluster store | Read every secret in the cluster directly | 4 453 | 105 |
6443 |
apiserver Secure | List pods, exec into them, deploy attacker pods | 3 885 | 187 |
10250 |
kubelet RW | POST /run, POST /exec, container-escape primitives |
3 455 | 141 |
8080 |
apiserver Insecure (deprecated) | Unauthenticated cluster takeover — the classic K8s blunder | 640 | 285 |
10255 |
kubelet Read-only | Pod/node enumeration without auth | 620 | 72 |
30000 |
NodePort base | Internal services exposed by misconfigured Service objects | 452 | 77 |
9090 |
Prometheus | Cluster metrics — pod names, node IPs, label-based recon | 220 | 85 |
2380 |
etcd peer | Lateral movement between etcd members | 51 | 28 |
4194 |
cAdvisor | Container resource metrics — container ID enumeration | 3 | 2 |
Per-port — what the payloads actually look like
The dominant payload shape is
GET /version followed
by GET /v2/keys/?recursive=true — that’s
the etcd v2 API path that, if exposed without auth on a real
cluster, dumps every key/value (including Kubernetes Secrets
objects). We also see GET /v3/kv/range with a
base64-encoded range query targeting /registry/secrets/
— that’s the etcd v3 grpc-gateway equivalent and
the more current shape.
Our honeypot returns a believable etcd 3.5.7 version string and a small, plausible key-space (cluster bootstrap keys, no real secrets) so the attacker thinks they hit a tiny cluster.
system:anonymous on a misconfigured cluster can still
read non-trivial things, and (b) the TLS handshake itself is a
useful fingerprint (Kubernetes apiserver TLS cert has a recognisable
CN). The top-line shape:
GET /api/v1/namespaces/default/pods with no token,
POST /apis/rbac.authorization.k8s.io/v1/clusterrolebindings
attempts that try to create system:masters bindings,
and GET /api/v1/secrets?limit=500 mass-enumeration
attempts.
The 187 unique IPs (vs 105 on etcd) is the broadest spread of any K8s port — many scanners hit apiserver as a discovery step without ever following up on the other ports. Banner-grab class.
kubelet.authentication.anonymous.enabled=true),
an attacker can call POST /run/<namespace>/<pod>/<container>
with a command to execute inside any running container on the
node — effectively container escape onto the host filesystem
via nsenter-class tricks. Our honeypot mostly sees
GET /pods (the kubelet’s API surface),
GET /runningpods, and reach-in attempts to the
POST /exec/<ns>/<pod>/<container>
endpoint.
Real-world finding: a public cluster with this port reachable and anonymous=true loses every workload running on the node within hours of being indexed by Shodan. We log when actors hit
/exec or /run as a separate
high-confidence class.
The K8s-specific subset is small but high-quality:
GET /api/v1/namespaces, GET /healthz,
and the same clusterrolebindings creation attempts
we see on 6443 but without the TLS overhead. Anyone who
connects here and continues with K8s-shaped paths is operating
specifically — not just banner-grabbing.
GET /pods, GET /spec/
(Cadvisor pod-spec dump), GET /metrics, and
GET /stats/summary. None of these execute code,
but they hand the attacker the full topology of every running
workload on the node — image names, environment variables,
mounted secrets paths, service-account tokens. From there it’s
a short hop to pivot.
GET /api/v1/query?query=kubelet_running_pods
response gives them the entire pod inventory of the cluster.
Top probes are GET /metrics, GET /api/v1/targets,
and GET /api/v1/label/__name__/values.
Cross-vector signal — the Oracle-cloud cluster
One operator cluster surfaces across multiple HoneyLens hunts. The
66.132.0.0/16 Oracle Cloud Infrastructure range was the
primary kube-apiserver+kubelet pounder we documented during
the CVE-2026-0300 T+9 review — the same operator that ran
captive-portal scanning against PAN-OS surfaces in May. This week
the same range showed up on the
NGINX Rift honeypot
on port 8443 with banner-grab shape. Same operator, three
attack surfaces in 30 days: K8s clusters, PAN-OS captive
portals, and now NGINX builds. Their target list keeps growing.
That cross-vector signal is the actual point of the K8s honeypot. The per-port traffic shape is interesting but normal; the who’s knocking on multiple doors question is the one that tells you something an IDS rule can’t.
If you’re running K8s on the internet
- Don’t expose etcd. If you can’t justify a Kubernetes node having a public-IP etcd port reachable, just don’t. 4 453 hits in 7 days from 105 distinct attackers is what it costs.
- Force kubelet authentication.
--anonymous-auth=falseon the kubelet,--authorization-mode=Webhook, mTLS client certs. If you can’t do that, at least firewall 10250 to the apiserver subnet only. - Insecure apiserver port stays off. Even with
K8s ≤ 1.26 in production, set
--insecure-port=0explicitly. The 285 unique scanners hitting 8080 in 7 days include everyone from Shodan to the targeted Oracle-cloud cluster we tracked above. - Lock Prometheus scrape. If a metrics endpoint is reachable from the public internet, you’ve published your cluster topology.
- Watch the cross-vector signal. The operators running these scans don’t stop at one CVE per quarter. If your captive-portal log shows the same source IP as your K8s apiserver log, that’s an actor profile, not two unrelated probes.