文章总结: 本文分析了AWSCognito多SSO用户池的安全风险,指出恶意身份提供商可通过触发器机制漏洞创建幽灵身份、实施子级拆分攻击和路由劫持。关键发现包括PreSignUp触发器检查缺失导致持久化攻击向量、用户名解析差异引发权限提升、以及自助服务标识符注册带来的域名接管风险。建议审计时全面检查触发器覆盖范围、属性映射控制和域名所有权验证。 综合评分: 82 文章分类: 云安全,漏洞分析,应用安全,安全建设,解决方案
AWS Cognito多SSO用户池风险解析
Dubito Dubito
云原生安全指北
2026年5月8日 08:35 江苏
在小说阅读器读本章
去阅读
注:本文翻译自 Doyensec 的文章《The Danger of Multi-SSO AWS Cognito User Pools》[1],可点击文末“阅读原文”按钮查看英文原文。
全文如下:
背景
经过一段小小的插曲后,云安全小贴士(CloudSecTidbits) 系列迎来了新的篇章。几天前,我们有机会在新加坡首届 DEFCON[2] 大会的 演示实验室(DemoLabs sessions)[3] 环节进行了展示。能与新加坡的安全社区交流真是太好了——再次感谢各位的接待!
往期回顾
云安全小贴士 第一季回顾:AWS SDK 凭据链滥用、Cognito 属性篡改、AWS Batch 权限提升
云安全小贴士(CloudSec Tidbits)是一个博客系列,展示了 Doyensec 在云安全测试活动中发现的有趣漏洞。
我们重点关注那些由于 Web 相关技术与云相关技术的不安全组合而导致的安全漏洞。
每篇文章都包含一个基础设施即代码(Infrastructure as Code,IaC)实验室,你可以通过 这个链接[4] 轻松部署,亲身体验所描述的漏洞。
准备好深入学习一个新的知识点吧。
一、No. 4 – 多单点登录用户池(Multi-SSO User Pools)的风险
什么是 AWS Cognito?如果你需要重温一下,可以先阅读我们之前在 第一季第2集:篡改 AWS Cognito 用户池中的用户属性(Tampering User Attributes In AWS Cognito User Pools)[5] 中对 AWS Cognito 的初步介绍。
这一次,我们抛开简单的单租户配置,来深入探讨一种正成为 SaaS 默认模式的多租户 Cognito 部署方式:一个用户池(User Pool),多个租户(tenant),并且每个租户都带入“他们自己的”外部身份提供商(external IdP)。
1.1 AWS Cognito 多单点登录(Multi-SSO)流程
通过 Cognito 用户池(User Pools),开发人员可以针对单个池注册多个外部 IdP(支持 OIDC 和 SAML 协议),并通过托管 UI(即托管登录页面)或者仍然调用托管单点登录端点(SSO endpoints)的自定义登录页面来暴露这些 IdP。
外部 IdP 是通过 CreateIdentityProvider API 来注册的。一个最简的 OIDC 注册示例如下:
当然,这种创建操作通常由平台的后端来完成,以支持其租户自定义身份提供商(IdP)设置。
1.2 引入新角色:AWS Lambda 触发器(Triggers)入门
触发器本质上是一种同步钩子(synchronous hooks),它允许开发人员将自定义逻辑嵌入到事件驱动的流程中。
就 Cognito 而言,该服务会在用户创建和通过单点登录(SSO)进行身份认证的特定阶段,调用多个触发器。这些触发器会暂停 SSO 认证流程,并允许自定义逻辑来接受、拒绝或修改该流程。在正常实现中,这些触发器最终会承载平台所需的所有“身份粘合逻辑”,以保持与其他身份约束的一致性,例如:域名白名单和所有权检查、租户限制、JIT 配置、属性标准化、令牌整形等。
理解这一机制最清晰的方式,就是梳理出 SSO 触发器的执行顺序和事件类型。下面你可以找到我们整理的指南,用于界定在众多触发器中应进行哪些身份检查。
从安全角度来看,要点如下:
- 1.
PreSignup(预注册)触发器是在 Cognito 用户池(User Pool)中实际创建用户对象之前的唯一一道闸门。一旦身份信息落入用户池中,就可以利用平台的其他功能与之交互。 - 2. 首次联邦登录(federated sign-in)和后续登录的执行顺序中,只有
TokenGeneration(令牌生成)触发器是两者共有的。如果仅在其中一个流程中施加认证限制,则可能导致在另一个流程中完全通过认证。 - 3. 一旦用户在用户池中被创建,没有自动回滚机制;清理工作必须手动处理。
- 4. 联邦登录不会调用用户池中的其他自定义认证挑战(custom authentication challenge)、迁移用户(migrate user)、自定义消息(custom message)或自定义发送方(custom sender)触发器。
1.3 如果身份提供商(IdP)是恶意的呢?完整流程示例
在下面的示例中,我们展示了一个外部 OIDC IdP 参与时的完整流程:Cognito 会执行完整的 OIDC 授权码流程,获取 /userinfo 端点信息,并根据创建时定义的设置来合并这些声明(claims)。
注:高清 SVG 文件可以在此下载[6]。
一个恶意的 IdP 可以根据具体的安全约束,以及嵌入在其中的复杂身份逻辑,以多种方式攻击依赖多 SSO Cognito 用户池的平台。
现在,我们拥有了所有要素:一个是作为恶意 IdP 与 AWS Cognito 通信的额外注入点,另一组是将迷宫般的身份约束“粘合”起来的复杂触发器。
接下来,让我们一起分析那些可能引发漏洞的反模式(anti-patterns):
1.3.1 JIT “幽灵身份”注入:有时落地就足够了
如前所述,PreSignUp_ExternalProvider 触发器是唯一一个在 Cognito 将用户记录持久化到用户池之前就会触发的触发器。
大多数情况下,获取一个幽灵身份非常直接:
- 1. 使用自助服务的 SSO 配置页面,注册一个恶意的 OIDC 服务器作为身份提供商(IdP),命名为
EvilCorp。 - 2. 使用
[email protected]这个邮箱进行联邦登录。 - 3.
PreSignUp_ExternalProvider触发器触发,但并未包含域名检查,因此 Cognito 将用户记录持久化到了用户池中。 - 4.
PostConfirmation触发器(即 JIT 配置 Lambda)触发,此时域名检查抛出了异常,当前会话被阻止,但用户记录依然存在。PreAuthentication触发器也配置了相同的检查,但 SSO 并不是与用户交互的唯一方式。
从此刻起,即使存在会将其删除的回滚机制,你仍然拥有一个可操作的时间窗口,在此期间可以利用平台的其他功能与此身份进行交互。最坏的情况包括:通过强制密码重置来获得非 SSO 的认证能力,或者冒充该用户以获取直接会话等。
提示: 意外的转义字符或其他字段中的注入手段,可能会带来各种各样的漏洞。请始终将读取身份对象的各个组件视为一个整体来进行审查。
1.3.2 触发器来源值:那些被遗忘的事件
Cognito 通过多个 event.triggerSource 值来区分用户创建和认证路径。triggerSource 是传递给自定义处理程序的命名信息,用于帮助其理解身份事件并据此执行相应操作。
存在很多这样的值,其中一些可能会被开发人员忽略或错误解读,从而引入漏洞。
与任何多 SSO 安全审查相关的核心值如下:
| triggerSource | 触发时机 / 安全风险 |
| — | — |
| InboundFederation_ExternalProvider | 在每次联邦登录时、用户记录写入之前触发,无论是新用户还是老用户;如果忽略此触发器,属性检查就会落到 PreSignUp 上,而后者仅在首次登录时触发。 |
| PreSignUp_ExternalProvider | 当首次联邦登录将创建本地用户时触发;如果在此处缺少身份检查,就会导致持久的幽灵身份。 |
| PreSignUp_AdminCreateUser | 通常在管理员或 SCIM 创建用户路径时触发。 |
| PostConfirmation_ConfirmSignUp | 在用户确认之后触发,包括首次联邦登录时的自动确认;此触发器无法阻止用户创建,只能作用于已持久化的记录。 |
| PreAuthentication_Authentication | 仅在后续登录时触发;不会在首次联邦登录时触发,因此如果只在这里部署检查,首次登录将不受保护。 |
| PostAuthentication_Authentication | 每次成功认证后触发,但无法阻止会话;仅作为检测和审计钩子,不是安全闸门。 |
| TokenGeneration_Authentication | 在 SDK/管理员认证时触发;其来源与 HostedAuth 不同,应用于其中一种来源的逻辑会在另一种来源中静默缺失。 |
包含所有可能 triggerSource 的完整参考信息,请查阅 Lambda 触发器文档[7]。
1.3.3 联邦用户名格式与子级拆分攻击(Sub-Splitting Attack)
Cognito 内部用于标识联邦用户的关键信息并不是邮箱,而是:
<ProviderName>_<sub>
这个值在触发器中显示为 event.userName,在令牌中显示为 cognito:username。其中,ProviderName 是在用户池中注册的身份提供商(IdP)名称,而 sub 则是 IdP 的主体标识符(如果 IdP 是恶意的,则该值受攻击者控制)。
身份提供商名称冲突:大小写与同形异义字(Homoglyph)
Cognito 会强制要求 ProviderName 在字节级别上唯一。但是,两个名称在视觉上相似、但字节序列不同的 IdP,仍然可以被添加到同一个用户池中。
例如:
| ProviderName | 易混淆的码点 | 可视渲染结果 | 备注 |
| — | — | — | — |
| LegitCorp | none(ASCII) | LegitCorp | 基准情况,可被接受 |
| LеgitCorp | е = U+0435(Cyrillic 小写 ie) | LegitCorp | 同形异义字字母 “e”,可被添加到同一个池中 |
这种做法很危险,因为大多数面向人工界面的地方并不会显示出这种差异:托管 UI 按钮、审计日志、命令行输出以及基于 grep 的审计,都只是正常渲染 Unicode 字符而已。更糟糕的是,如果应用程序随后进行不一致的标准化处理(如 lower()、NFKC 等),可能会导致解析器差异,最终要么使同一个 IdP 出现分裂的身份标识,要么让查询操作解析到错误的记录上。
子级拆分攻击(Sub-Level Splitting Attack)
ProviderName 的正则表达式禁止使用下划线 _。但 sub 声明中并没有这个限制。因此,完整的身份字符串可能包含多个下划线:
Corp_admin_override
如果组件 A 使用 split("_", 1) 进行解析,而组件 B 使用 split("_")[-1](或任何其他位置索引)进行解析,那么相同的输入就会产生两种不同的含义。
从恶意 IdP 发送 sub = [email protected] 会导致以下结果:
| Lambda 函数 | 代码 | 索引位置 | 看到的值 |
| — | — | — | — |
| pre_signup (唯一性守卫) | sub.split("_")[1] | 第二个 token | "noise" 不在池中,检查通过 |
| jit_provisioning (消费者) | sub.split("_")[-1] | 最后一个 token | "[email protected]" ,被存储为 custom:primaryEmail |
1.3.4 IdP 标识符与路由劫持
IdP 标识符(IdP identifiers)是 Cognito 用于重定向到 IdP 的字符串。其标准模式是基于邮箱域名的路由:用户输入 [email protected],Cognito 查找 company.com,然后浏览器被重定向到拥有该标识符的 IdP。
控制了一个标识符,就等同于控制了所有使用该标识符的用户的初始重定向。
因此,如果一个租户遗漏或未注册某个标识符,另一个 IdP 就可能在这个空档期内将其抢注。由于 AWS Cognito 并不确保域名所有权,平台本身绝不应该在不提前检查租户是否控制该标识符的情况下,就允许其认领 idp-identifier。
这是一种典型的域名接管,后果非常危险。例如,如果在一个平台中,gmail.com 可以通过自定义 IdP 配置被认领,那么你可能最终会将每一个 Google 用户重定向到一个攻击者控制的页面。
二、不要信任 IdP
多 SSO 机制改变了:
- • 哪些触发器会触发,
- • 应用程序将什么视为身份标识的关键信息,
- • 以及有多少个受攻击者控制的字符串会被你意外地当作结构来解析。
将访问控制放在了错误的触发器上,会导致幽灵身份的产生;对受攻击者控制的 sub 值进行解析,会导致权限提升;而自助服务的 IdpIdentifiers 字段,则会带来路由劫持的风险窗口。
三、给云安全审计人员的建议
在审查一个基于 Cognito 的多租户平台时,请回答以下问题:
- 1. 该用户池是否会注册外部 IdP?
- 2. 对于每个 IdP,
AttributeMapping中映射了什么内容?如果 IdP 是恶意的或被入侵的,那么其中任何内容都会受攻击者控制,无论WriteAttributes如何设置。 - 3.
PreSignUpLambda 函数是如何根据event.triggerSource进行分支处理的?它是否覆盖了PreSignUp_ExternalProvider和PreSignUp_AdminCreateUser,而不仅仅是PreSignUp_SignUp? - 4. 所有身份检查是否都同时覆盖了 JIT 和后续 SSO 登录的触发器链?如果没有,你应该检查是否存在意外的身份创建。
- 5. 是否有任何 Lambda 函数使用类似
split("_")并配合位置索引的方式来解析event.userName或cognito:username?如果是,则该解析逻辑对于包含_的sub值是脆弱的,你应该检查是否存在守卫逻辑与消费者逻辑之间的解析差异。 - 6.
IdpIdentifiers是否暴露在自助服务的 IdP 注册界面中?如果是,平台是否确保了被认领的域名标识符确实由确认了所有权的租户所拥有?如果不是,则可能发生对使用未认领域名的传入用户的任意重定向。 - 7.
AttributeMapping是否映射了任何对安全敏感的自定义属性(例如custom:tenantID、custom:role、custom:isAdmin)?即使WriteAttributes被锁定,使用AdminUpdateUserAttributes的 JIT Lambda 函数仍然会写入这些属性。
四、给开发者的建议
请在 PreSignUp 触发器内,按 triggerSource 分支来设置安全门控。 这对于多 SSO 部署来说,是效果最显著的单一改动。一个可行的模式如下:
def lambda_handler(event, context):
if event["triggerSource"] in (
"PreSignUp_SignUp",
"PreSignUp_ExternalProvider",
"PreSignUp_AdminCreateUser",
):
enforce_domain_policy(event["request"]["userAttributes"]["email"])
return event
永远不要通过 split("_") 来解析 event.userName 以提取身份信息。如果必须解析,请在所有解析的地方统一使用 split("_", 1)(即 maxsplit=1)。守卫逻辑和消费逻辑必须使用完全相同的提取逻辑,对受攻击者控制的字符串使用位置索引,这迟早会引发解析器差异漏洞。
请将安全相关的自定义属性排除在 AttributeMapping 之外。 应在触发器内部,从经过验证的邮箱域名中服务端派生 tenantID 及类似字段,而绝不要在联邦登录后从 event.request.userAttributes 中读取它们。
在 PreSignUp 中严格校验 email 字段。
对于 IdpIdentifiers: 切勿在自助服务的 IdP 注册界面中将其暴露为自由格式的输入字段。在基础设施即代码(IaC)中,应以原子方式注册这些标识符。不要在同一个 apply 操作中执行“先删除后添加”。
五、工具发布:maSSO —— 专为这项工作打造的恶意 IdP
上面描述的几乎所有滥用场景都基于同一个前提:一个受攻击者控制、且被服务提供商所信任的 IdP,以及篡改发给该 IdP 的精确令牌、SAML 断言和 /userinfo 载荷的能力。
仅仅为了测试而运行自定义 IdP 非常耗时,因此我们决定发布自己在渗透测试中使用的工具:doyensec/maSSO[8]
maSSO[8] 是一个武器化、合规的单点登录(SSO)身份提供商(IdP),用于对 OIDC 和 SAML 2.0 服务提供商进行安全测试,同时也支持 SCIM 协议。
对我们而言,这是一直缺失的、用于实际服务提供商测试的“瑞士军刀”。欢迎反馈你的使用体验!
六、动手实践:基础设施即代码(IaC)实验环境
正如本系列开篇所承诺的,我们开发了一个 Terraform(基础设施即代码,IaC)实验环境,用于部署一个存在漏洞的模拟应用程序,并让您亲身体验该漏洞:https://github.com/doyensec/cloudsec-tidbits/tree/main/lab-masso
敬请期待下一期内容!
参考资源
- • 通过第三方添加用户池登录[9]
- • Cognito 用户池的 Lambda 触发器[10]
- • Doyensec, 篡改 AWS Cognito 中不受限制的用户属性(第一季,小贴士 2)[5]
- • doyensec/maSSO, 用于服务提供商(SP)测试的武器化 OIDC/SAML 身份提供商(IdP)[8]
引用链接
[1] 《The Danger of Multi-SSO AWS Cognito User Pools》: https://blog.doyensec.com/2026/05/05/cloudsectidbits-masso-cognito-sso.html
[2] DEFCON: https://defcon.org/html/defcon-singapore/dc-singapore-index.html
[3] 演示实验室(DemoLabs sessions): https://defcon.org/html/defcon-singapore/dc-singapore-demolabs.html
[4] 这个链接: https://github.com/doyensec/cloudsec-tidbits/
[5] 第一季第2集:篡改 AWS Cognito 用户池中的用户属性(Tampering User Attributes In AWS Cognito User Pools): https://blog.doyensec.com/2023/01/24/tampering-unrestricted-user-attributes-aws-cognito.html
[6] 在此下载: https://blog.doyensec.com/public/images/multissowithlambda.svg
[7] Lambda 触发器文档: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-working-with-lambda-triggers.html
[8] doyensec/maSSO: https://github.com/doyensec/maSSO
[9] 通过第三方添加用户池登录: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-identity-federation.html
[10] Cognito 用户池的 Lambda 触发器: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html
交流群
知识库
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:云原生安全指北 Dubito Dubito《AWS Cognito多SSO用户池风险解析》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。











评论