Description
We have a kubernetes cluster setup and the flag is in the secrets. You think you can get it?
Setup
Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Set up kubectl with the provided kubeconfig
ObservationThe challenge hands you a kubeconfig for a remote cluster. Point kubectl at it and confirm the API connection before enumerating anything.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=./kubeconfigbashkubectl get namespacesbash# If certificate error:bashkubectl get namespaces --insecure-skip-tls-verifybashkubectl get secrets --insecure-skip-tls-verifyWhat didn't work first
Tried: Running kubectl get namespaces without setting KUBECONFIG and getting a connection refused error.
Without KUBECONFIG set, kubectl falls back to the default config, which either does not exist or points at some other cluster, and the connection is refused. Export it before any kubectl call.
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 2List Kubernetes secrets
ObservationThe description says the flag lives in Kubernetes secrets, so list every secret across every namespace.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 --all-namespaces -o go-template='{{range .items}}{{range $k, $v := .data}}{{$v | base64decode}}{{"\n"}}{{end}}{{end}}' | grep picoCTFWhat 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 come back base64-encoded, so the flag prefix never appears in the raw YAML. Grep the key names instead, and decode the value before expecting to read it.
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 3Retrieve the flag secret and decode it
ObservationThe listing turns up a ctf-secrets entry in the picoctf namespace. Kubernetes stores secret values base64-encoded, so pull the flag key with jsonpath and decode it.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 -dExpected output
picoCTF{ks3cr375_41n7_s4f3_...}What 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, its metadata and kind and apiVersion, is plain text rather than base64, so decoding the whole document produces garbage or an invalid-input error. Extract the single key value with jsonpath first.
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 ctf-secrets -n picoctf -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.
Interactive tools
- Regex TesterTest regular expressions against a string with live match highlighting, flag toggles, and common CTF pattern shortcuts.
- Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
Flag
Reveal flag
picoCTF{ks3cr375_41n7_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.