Description
We have a kubernetes cluster setup and the flag is in the secrets. You think you can get it?
Setup
Launch the challenge instance and download the kubeconfig from the challenge page.
Point kubectl at the downloaded file with: export KUBECONFIG=./kubeconfig
Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Set up kubectl with the provided kubeconfigObservationI noticed the challenge provides a kubeconfig file for a remote Kubernetes cluster, which means all interaction depends on pointing kubectl at that file and having a working API server connection before any enumeration can begin.Download the kubeconfig file from the challenge page. Set KUBECONFIG or use --kubeconfig to point kubectl at it. If it fails with a certificate error, add --insecure-skip-tls-verify to your kubectl commands.bashexport KUBECONFIG=./kubeconfig.yamlbashkubectl get namespacesbash# If certificate error:bashkubectl get namespaces --insecure-skip-tls-verifybashkubectl get secrets --insecure-skip-tls-verifyExpected output
picoCTF{k3y_s3cr3ts_4r3n7_s4f3_...}What didn't work first
Tried: Running kubectl get namespaces without setting KUBECONFIG and getting a connection refused error.
Without KUBECONFIG pointing at the downloaded file, kubectl defaults to ~/.kube/config which either does not exist or points at a different cluster. The command exits with 'The connection to the server localhost:8080 was refused.' Setting 'export KUBECONFIG=./kubeconfig.yaml' before any kubectl call directs it to the challenge cluster.
Tried: Trusting the TLS certificate error and abandoning kubectl instead of passing --insecure-skip-tls-verify.
The challenge cluster uses a self-signed certificate that kubectl cannot verify by default, producing 'x509: certificate signed by unknown authority'. Appending --insecure-skip-tls-verify to every kubectl command (or setting 'insecure-skip-tls-verify: true' in the kubeconfig) bypasses the TLS check and lets the connection succeed.
Learn more
Kubernetes (k8s) is an open-source container orchestration platform. It organises containerised workloads into pods (groups of containers), and groups pods into namespaces (logical isolation boundaries).
kubectlis the command-line client that communicates with the Kubernetes API server.A kubeconfig file (typically at
~/.kube/config) stores cluster connection details, credentials, and the current context (which cluster and namespace you are targeting). Thekubectl config current-contextcommand shows which context is active.kubectl auth can-i --listshows all actions the current user is permitted to perform - this is the first step in Kubernetes privilege assessment during a penetration test.In cloud environments, Kubernetes clusters are commonly managed by cloud providers (Amazon EKS, Google GKE, Azure AKS). Each pod can be assigned a service account with associated permissions. When a pod is compromised, attackers often find the service account token at
/var/run/secrets/kubernetes.io/serviceaccount/tokenand use it to authenticate to the API server.Step 2
List Kubernetes secretsObservationI noticed the challenge description states the flag is stored in Kubernetes secrets, which suggested enumerating all secrets across all namespaces to locate the one holding the flag value.Run kubectl get secrets first to see the names. If the flag-bearing secret isn't obvious from the name, dump every secret as YAML and grep for the picoCTF prefix in the base64-decoded data.bashkubectl get secretsbashkubectl get secrets --all-namespacesbash# If the relevant secret isn't obviously named, search every value:bashkubectl get secrets -o yaml | grep -i flagbash# Or decode every value to find the one that starts with picoCTF{:bashkubectl get secrets -o jsonpath='{range .items[*]}{.metadata.name}{": "}{range .data.*}{@}{"\n"}{end}{end}' | while read line; do echo "$line"; doneWhat didn't work first
Tried: Running 'kubectl get secrets' in the default namespace and seeing no secrets listed, then assuming the cluster has no flag.
The flag secret lives in the 'picoCTF' namespace, not 'default'. Without '--all-namespaces' or '-n picoCTF', kubectl only queries the namespace specified in the kubeconfig context, which is typically 'default'. The fix is 'kubectl get secrets --all-namespaces --insecure-skip-tls-verify' to enumerate every namespace.
Tried: Grepping the raw 'kubectl get secrets -o yaml' output for 'picoCTF{' and finding nothing.
Secret values are stored base64-encoded in the YAML output, so the literal string 'picoCTF{' never appears in the raw dump. Grepping for 'flag' in the key names is more reliable at this stage; the actual value must be piped through 'base64 -d' before the CTF prefix becomes visible.
Learn more
Kubernetes Secrets are API objects designed to store sensitive data like passwords, tokens, and certificates. They are stored in etcd(the cluster's distributed key-value store) and can be mounted into pods as files or exposed as environment variables. The built-in Secrets object provides namespacing and RBAC-controlled access, but it does not provide encryption by default - values are stored base64-encoded, not encrypted.
The key Kubernetes RBAC permission for this challenge is
getandliston thesecretsresource. A cluster that grants broad secret-read permissions to any service account is a common misconfiguration. Real-world Kubernetes security incidents like the Tesla cryptojacking breach (2018) and several CI/CD supply chain attacks exploited overly permissive service account tokens to exfiltrate secrets.Kubernetes also supports integration with external secret management systems like HashiCorp Vault (via the Vault Agent Injector or CSI Driver), AWS Secrets Manager (via the External Secrets Operator), and Sealed Secrets (which encrypts secrets as SealedSecret CRDs that are safe to commit to git). These provide encryption at rest and fine-grained access control beyond what native Kubernetes Secrets offer. See the Linux CLI guide for more on enumerating cluster state from the command line.
Step 3
Retrieve the flag secret and decode itObservationI noticed the listing revealed a secret named 'ctf-secrets' in the 'picoCTF' namespace, and knowing Kubernetes stores secret values as base64-encoded strings suggested extracting the specific 'flag' key with jsonpath and piping it through base64 -d to obtain the plaintext flag.Get the secret in the picoCTF namespace. The value is base64-encoded - pipe it through base64 -d to decode.bashkubectl get secrets --insecure-skip-tls-verify -n picoCTFbashkubectl get secret ctf-secrets --insecure-skip-tls-verify -n picoCTF -o yamlbashkubectl get secret ctf-secrets --insecure-skip-tls-verify -n picoCTF -o jsonpath='{.data.flag}' | base64 -dWhat didn't work first
Tried: Piping the entire 'kubectl get secret -o yaml' output through 'base64 -d' instead of extracting just the flag field first.
The YAML envelope (metadata, kind, apiVersion) is plain text, not base64. Passing the whole document to 'base64 -d' produces garbled output or a 'invalid input' error because the decoder encounters non-base64 characters. The correct approach is to extract the single key value with '-o jsonpath={.data.flag}' before decoding.
Tried: Using 'kubectl get secret flag-secret -n picoCTF' but getting a 'not found' error because the secret is named 'ctf-secrets', not 'flag-secret'.
The secret name must match exactly what the cluster has. Running 'kubectl get secrets --all-namespaces --insecure-skip-tls-verify' first reveals the real name 'ctf-secrets' in the 'picoCTF' namespace, which avoids guessing and a misleading 'not found' response.
Learn more
Kubernetes Secret values are stored base64-encoded in etcd and in the YAML output of
kubectl get secret. This encoding is not encryption - it is purely to allow arbitrary binary data to be stored in the YAML format. Anyone with read access to the secret can trivially decode the value withbase64 -d. This is why access control on thesecretsresource is critical.The
-o jsonpathflag applies a JSONPath expression to the API response and extracts just the requested field.{.data.flag}navigates to thedatamap and then theflagkey. Piping throughbase64 -ddecodes the value in one command. The equivalent usingjqiskubectl get secret flag-secret -o json | jq -r '.data.flag' | base64 -d.Encryption at rest for Kubernetes Secrets requires configuring the API server with an
EncryptionConfigurationthat specifies an encryption provider (AES-GCM, AES-CBC, or a KMS provider backed by an external key management system). Without this configuration, secrets in etcd are only base64-encoded and readable by anyone with direct etcd access. Most managed Kubernetes services (EKS, GKE) enable encryption at rest by default; self-managed clusters must configure it explicitly.
Flag
Reveal flag
picoCTF{k3y_s3cr3ts_4r3n7_s4f3_...}
Use kubectl with --insecure-skip-tls-verify and the provided kubeconfig. Get secrets in the picoCTF namespace, then decode the base64-encoded flag value.