AWS SSO引入新的PKCE认证机制:安全加固与潜在风险分析

本文深入探讨了AWS SSO新引入的PKCE(Proof Key for Code Exchange)认证流程,通过对比旧有的设备码认证方式,分析了其在防御钓鱼攻击方面的原理与优势,并提供了具体的Python实现代码示例,同时也指出了目前仍存在的安全挑战。

AWS SSO的新PKCE认证带来了希望(大体上)

2021年,我曾撰文介绍攻击者如何利用AWS SSO设备码进行钓鱼攻击,使得FIDO认证或身份提供商的设备状态等现代安全控制措施失效:通过AWS SSO设备码认证钓鱼获取AWS凭证。在这篇文章中,我们将更详细地审视新发布的、用于AWS SSO认证流程的PKCE支持。

设备码钓鱼简史

  • Sebastian Mora撰写了一篇博客并发布了“awsssome_phish”,这是一款旨在将设备码钓鱼攻击付诸实践的攻击性工具。
  • Chaim Sanders发表文章,讨论了AWS生态系统中设备码钓鱼带来的风险。
  • Matthew Garrett指出,设备码认证实际上绕过了近年来实施的许多高级安全机制。
  • Rami McCarthy发表了一篇观点鲜明的指南,概述了AWS为解决此问题可能采取的步骤。
  • Chris Norman发布了一款开源浏览器扩展,可以帮助用户防御设备码钓鱼攻击。

虽然我未听闻有关在真实环境中利用此技术的报告,但至少直接收到过两个红队的反馈,称设备码钓鱼极其有效。

re:Invent 2024——新的希望

在re:Invent大会前几天,AWS宣布发布“基于PKCE的SSO授权”,该功能现已可用,并在AWS CLI 2.22.0及更高版本中默认启用。虽然PKCE(Proof Key for Code Exchange)这个术语听起来可能有些晦涩,但它本质上是标准OAuth 2.0授权码流程的一种实现。

基于PKCE的新认证流程界面

这是一个概念上描述该流程的序列图:

![PKCE认证流程图描述]

该流程与设备码认证的根本区别在于,成功认证后,AWS会将用户重定向到本地主机(localhost)。在此处,客户端应用程序(本例中为AWS CLI)通过启动一个临时Web服务器来获取授权码。关键的含义在于,创建恶意认证链接的攻击者无法获取授权令牌来冒充受害者。即使受害者成功通过AWS SSO进行认证,他们也将被重定向到本地主机并遇到浏览器错误,从而使攻击失效。

这些钓鱼网站怎么从来都不管用!

具体实现细节如何?

AWS CLI的实现应该能给你一个好思路(更具体地说是SSOTokenFetcherAuth类)。我在下面也重现了一个简单且略显粗糙的Python实现。

示例Python实现

 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
"""注意:你需要一个较新版本的boto3。请确保也替换下面的SSO起始URL。"""
SSO_START_URL='https://d-1234.awsapps.com/start'

import boto3
import jwt
import json
import urllib.parse
import secrets
import base64
import hashlib


def generate_code_verifier_and_challenge():
    # 生成一个加密随机的code_verifier
    code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b'=').decode('utf-8')
    
    # 根据code_verifier计算code_challenge
    digest = hashlib.sha256(code_verifier.encode('utf-8')).digest()
    code_challenge = base64.urlsafe_b64encode(digest).rstrip(b'=').decode('utf-8')
    
    return code_verifier, code_challenge

sso_oidc = client = boto3.client('sso-oidc')
redirect_uri = "http://127.0.0.1"

response = client.register_client(
    clientName='pkce-demo',
    clientType='PUBLIC',
    scopes=["sso:account:access"],
    redirectUris=[
       redirect_uri
    ],

    grantTypes=["authorization_code", "refresh_token"],
)

client_id = response['clientId']
client_secret = response['clientSecret']
code_verifier, code_challenge = generate_code_verifier_and_challenge()

url = "https://oidc.us-east-1.amazonaws.com/authorize?response_type=code"
url += f"&client_id={client_id}"
url += f"&redirect_uri={urllib.parse.quote(redirect_uri)}"
url += "&state=dontcare"
url += "&code_challenge_method=S256"
url += "&scopes=sso%3Aaccount%3Aaccess"
url += f"&code_challenge={code_challenge}"

# 在不验证签名的情况下解码client_secret中的JWT
claims = jwt.decode(client_secret, options={"verify_signature": False})

# 交互式读取令牌
print("请在浏览器中打开此URL:")
print(url)
print()
print("当你被重定向到本地主机时,请在下方粘贴OAuth代码:")
code = input("OAuth代码:")

response = client.create_token(
    grantType='authorization_code',
    clientId=client_id,
    clientSecret=client_secret,
    redirectUri=redirect_uri,
    codeVerifier=code_verifier,
    code=code
)
access_token = response['accessToken']

print("成功获取AWS SSO访问令牌。")
print("可用的AWS账户:")
sso_client = boto3.client('sso')
response = sso_client.list_accounts(accessToken=access_token)
for account in response.get('accountList', []):
    print(f" - {account['accountName']} ({account['accountId']})")

"""
现在生成一些凭证!
response = sso_client.get_role_credentials(
    roleName='account-admin',
    accountId='111111',
    accessToken=access_token
)
print(response)
"""

示例输出:

1
2
3
4
5
6
7
8
9
请在浏览器中打开此URL:
https://oidc.us-east-1.amazonaws.com/authorize?response_type=code&client_id=icBsllQ8y8kJWfyMjKH3cnVzLWVhc3QtMQ&redirect_uri=http%3A//127.0.0.1&state=dontcare&code_challenge_method=S256&scopes=sso%3Aaccount%3Aaccess&code_challenge=wJYlfWmU3ejXGVbUbiXhahQHUaexSrlcLSQahygGCkk

当你被重定向到本地主机时,请在下方粘贴OAuth代码:
OAuth代码:eyJr...TB2d5pEmNPNJ6GMNypXkAr
成功获取AWS SSO访问令牌。
可用的AWS账户:
- Sandbox (12345678901)
- Honeypots (1111111111)

攻击者能否使用恶意重定向URL窃取授权令牌?

OAuth实现中最常见的漏洞之一是允许攻击者制作重定向到攻击者控制域的身份验证URL。成功后,这使得攻击者能够拦截授权令牌并冒充受害者。

在这种情况下,AWS通过将重定向URL限制为本地主机来降低此风险。此限制确保由AWS CLI启动的临时Web服务器是授权码的唯一接收者。我尝试使用各种技术来绕过此验证,包括嵌入IPv4组件的IPv6地址和DNS重绑定。然而,所有尝试均未成功——这是一个好迹象,表明其实施是健壮的(或者我是个糟糕的渗透测试员)。

这真的不能用于钓鱼目的吗?

绝对不行——至少在设计上不行,这与天生容易受到钓鱼攻击的设备码认证不同。唯一潜在的风险是,如果AWS的实现存在漏洞,例如攻击者能够绕过重定向URL允许列表。如果你是一名熟练的应用安全人员,我鼓励你试一试:这将是在HackerOne上提交给AWS新的VDP计划的绝佳机会!

那么……我们安全了吗?

不幸的是,尽管基于PKCE的认证是正确的方向,但设备码认证仍然可用,并且无法禁用它。因此,此新版本并未关闭它本应缓解的攻击向量。

以对向后兼容性的坚定承诺而闻名的AWS,很可能提前发布此功能以推动采用并逐步扩展,并可能计划在未来允许组织强制执行基于PKCE的认证。

你应该做什么?

  • 升级你的AWS CLI到较新版本(2.22.0+),它默认使用基于PKCE的流程(注意:aws-vault中的支持即将到来)。
  • 鼓励你组织中的每个人过渡到基于PKCE的流程。
  • 使用CloudTrail来检测和警报旧设备码流程的使用。你可以通过匹配具有以下属性的CloudTrail日志来创建警报:
    • eventName = CreateToken
    • eventSource = sso.amazonaws.com
    • requestParameters.grantType = urn:ietf:params:oauth:grant-type:device_code
  • 如果你有EDR或代理的访问权限,你也可以在网络层面阻止device.sso.<region>.amazonaws.com。这将确保没有人可以从企业设备使用设备码认证。
  • 如果设备码钓鱼是你关心的问题,请让你的AWS TAM知道,以便他们可以在内部进行倡导。

感谢阅读!你可以在LinkedIn和Bluesky上找到我。

参考资料

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计