┌───────────────────────┐
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
└───────────────────────┘
KSECRETS — picoCTF 2026
~ Imattas aka Zemi
 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.