SAML是一种目前应用非常广泛的单点登录协议,如果你运行SAML服务器并与许多其他站点集成,那么几乎可以肯定你使用的是不安全的设置。SAML安全面临的最大威胁不是怪异的XML边缘案例或黑客窃取你的签名密钥,而是低质量的第三方实现,这允许你的用户登录到你认为他们无法访问的应用程序。要确保SAML断言只适用于正确的应用程序,请为每个应用程序或服务提供者使用惟一的签名密钥。
这个问题并不是SAML独有的,签名的JWT和其他SSO的使用(比如OIDC中的使用)也可能遇到类似的问题,即缺少令牌验证。
SAML是如何工作的?
从较高的层次上讲,SAML是一种登录用户的方式,它使用两个系统之间的浏览器内部通信,否则它们之间不能相互通信。当用户想要登录到他们最喜欢的SaaS时,SaaS应用程序(SP或服务提供商)会将一些关于登录请求的数据发送到你的IDP。这包括诸如惟一请求ID和数据(如你试图访问的原始页面)之类的内容。理论上,身份验证请求可以指定IDP应该返回哪种类型的用户名和名称之类的字段,但实际上这会被忽略。这些请求也可以签名,但实际上在SP旋转其密钥时,大多数情况都会使事情中断。安全性几乎没有好处,因为在任何现代系统中,SAML交换都是通过TLS进行的。
SAML是一种目前应用非常广泛的单点登录协议,如果你运行SAML服务器并与许多其他站点集成,那么几乎可以肯定你使用的是不安全的设置。SAML安全面临的最大威胁不是怪异的XML边缘案例或黑客窃取你的签名密钥,而是低质量的第三方实现,这允许你的用户登录到你认为他们无法访问的应用程序。要确保SAML断言只适用于正确的应用程序,请为每个应用程序或服务提供者使用惟一的签名密钥。
这个问题并不是SAML独有的,签名的JWT和其他SSO的使用(比如OIDC中的使用)也可能遇到类似的问题,即缺少令牌验证。
SAML是如何工作的?
从较高的层次上讲,SAML是一种登录用户的方式,它使用两个系统之间的浏览器内部通信,否则它们之间不能相互通信。当用户想要登录到他们最喜欢的SaaS时,SaaS应用程序(SP或服务提供商)会将一些关于登录请求的数据发送到你的IDP。这包括诸如惟一请求ID和数据(如你试图访问的原始页面)之类的内容。理论上,身份验证请求可以指定IDP应该返回哪种类型的用户名和名称之类的字段,但实际上这会被忽略。这些请求也可以签名,但实际上在SP旋转其密钥时,大多数情况都会使事情中断。安全性几乎没有好处,因为在任何现代系统中,SAML交换都是通过TLS进行的。
一旦你的IDP接收到身份验证请求,IDP将验证你是否已登录(可能是密码、可能是客户端证书),然后签署一个断言。断言是SP将验证并用于登录你的内容。然后,你的IDP将此断言发送回SP。SP验证密码签名,验证该断言是否应该发送到特定的SP,并提取相关的用户名和其他字段。现在,你可以看所有你想看的图片了!
那个签名秘钥听起来很吓人!你的本能可能是不惜一切代价保护该密钥。密钥值得保护,但是对你的SAML IDP安全最大的可信威胁不是拥有你的SAML服务器的攻击者。
攻击SAML的方法
攻击SAML的方法有很多!尽管独特的签名密钥可以解决其中的一些问题,但这并不是万能的。
受众限制问题(Audience restriction issue)
这是我在这篇文章中关注的问题,稍后我将更详细地讨论它。不出意料,惟一签名密钥解决了这类问题。
IDP签名密钥被盗
确实,能够访问你的IDP的人可以获得签名密钥的副本,并以任何人的身份登录到与你集成的任何网站。如果这是你所关心的威胁,则仅提供签名预言的硬件支持的密钥是正确的防御措施。
XML和XML安全库
如果可以的话,你应该使用一个内存安全的库。也就是说,这对第三方的断言验证没有实际影响,而且如果使用惟一签名密钥,你的安全状态也不会改变。
XML处理问题
XML安全性是本世纪初出现的一种内联签名格式,当时没有人提出要求,需要它的人也更少。但用的人多了,问题就出现了,这些问题包括,忽略用户名中的XML注释、签名格式本身忽略对XML解析器有影响的注释,以及不检查你验证的签名是否实际覆盖了你信任的所有数据。
你可以通过使用惟一的签名密钥来减小这些问题的影响范围。你只需要关心单个应用程序的内部权限,而不是允许用户登录任何与你集成的服务(授权与否)。
解决方案
核心问题是缺乏受众限制验证,换句话说,SP没有检查断言是否针对它。SAML的设计思想是你的IDP将只有一个签名密钥,你可以将它分发给与你集成的每个人。考虑到SAML的学术背景,它应该是一个合作协议,组织之间密切合作。现代企业SAML忽略了所有这些有趣的特性,因为它们是巨大的安全和配置噩梦。
当你的IDP签署一个断言时,它包含两个供SP验证的字段:SP的实体ID和断言要发送到的URL。SP可以悄悄地忽略这些字段,而你对此无能为力。
作为负责签名密钥的IDP,你如何保护自己免受不可避免的弱SP攻击?
处理一堆签名钥匙
相比依赖协议的某些部分,唯一可扩展的方法是强制你的断言仅在一个SP上有效,而且是通过具有唯一签名每个SP的密钥。
之所以可行,是因为几乎每个SAML SP实现都包含三个部分。他们将从请求中提取用户名,在断言中验证签名,并拒绝无效的断言签名,其他所有内容都应视为可选内容。
虽然SP可以忽略你的签名,但是它的测试超级简单,而且这种事情很容易被漏洞赏金报告人员发现。与更深奥的受众限制测试不同,这里不涉及任何复杂性。
如何管理这么多密钥?
过去,我通过编写一堆Ruby自动生成相关XML来处理每个SP的唯一签名密钥,从而为Shibboleth管理了多个密钥。每当我遇到另一个错误处理断言的SP时,我都会感谢为减少这个我们不得不担心的问题而付出的努力。
在理想的情况下,我们不会使用SAML。 SAML是一种繁琐的协议,可让你创建带有身份验证内联签名的身份提供者的网状网络,其中XML中的空格确定签名是否有效。但是SAML以及OAuth 2.0和不完美的OIDC都将保留下来。鉴于SAML是事实上的企业单一登录协议,我们将忽略它。
如果你的IDP不支持此功能(请参见下文),则应向他们打开功能请求!这是你的IDP应该支持的重要安全控制。
如果你的IDP确实支持这个功能,为你的新应用程序发出每个sp的签名密钥。使用旧的证书迁移应用程序需要做很多工作,但是如果你有特别敏感的应用程序,则值得这样做。
所有SaaS IDP都应在没有任何用户干预的情况下生成每个应用程序的签名密钥,默认情况下,每个SP密钥的使用率极高,可以悄悄地提高与这些提供商签约的每个企业的安全性。截至2020年3月,唯一获得此权限的提供商是Azure AD。
自托管的IDP应确保它们支持按SP的签名密钥,并具有启用此功能的文档。理想情况下,共享签名密钥的配置不太明显,因此管理员默认情况下选择每个SP的签名密钥。
虽然最终要由SSO管理员做出正确的SSO选择,但是我们作为安全行业的责任是使正确的选择变得容易。
IDP支持多个签名密钥
没有实施指南,最佳做法将无济于事。这是截至2020年3月我已测试的各种主要IDP(包括SaaS和自托管选项)的列表。如果你的首选IDP不在此列表中或条目不正确,请与我们联系。
Azure AD – SaaS
Azure AD自动为每个“企业应用程序”生成一个新密钥,并且无法在控制台中的应用程序之间共享证书。你可以手动上传自己的证书和私钥,但这并不容易,我也不鼓励这样做,Azure AD应该是所有其他SaaS IDP的模型。
Shibboleth - Java(自托管)
你必须编写大量的XML才能使它工作,如果你花了几个小时绞尽脑汁地研究Spring XML配置,就不会出现任何问题。我已经包括了基本的需求。要点是,你需要创建单独的签名凭据,包括安全配置中的签名凭据,然后从单独的SP引用该安全配置。
另外,我确实喜欢Shibboleth是全java的状态,即没有内存损坏!,可以在本地自己的服务器上运行,并且采用非常符合标准的方法,从而降低了被奇怪的XML问题影响的可能性。
conf/relying-party.xml的示例配置(Shibboleth文档):
conf/credentials.xml的示例配置(Shibboleth文档):
PingOne—SaaS
这不是默认的,因为在默认情况下,PingOne使用一个共享签名密钥。有一个单独的证书页面,你可以在其中创建新的证书。添加一些内容后,你可以将每个SAML“应用程序”配置为使用唯一的签名密钥。
OneLogin—SaaS
这不是默认的,因为在默认情况下,OneLogin使用一个共享签名密钥。你可以尝试在单个SAML配置的设置中更改此秘钥,但不能添加新的秘钥。你必须导航到单独的“证书”页面以创建新证书,但是一旦完成,就可以为每个SP创建唯一的签名密钥。
一旦添加了“证书”(实际上是一个签名密钥),就可以将它分配给任意的SP。
Okta—SaaS
是的,但是需要在API中进行修改。Okta为每个SP使用不同的实体ID,但是默认情况下,使用完全相同的凭据对声明进行签名。无法在Okta控制台中上传自定义私钥或旋转签名凭证。
但是,你可以创建一个新的签名密钥,并通过两个API调用将密钥与应用程序关联。
GSuite SAML - SaaS
GSuite的SAML配置允许你在给定的时间内拥有两个签名证书,这样你就可以旋转过期的签名证书。很明显,GSuite可以支持其他证书,但它不支持。
Auth0——SaaS
尽管支持唯一的OAuth 2.0客户端机密,但Auth0在所有SAML“应用程序”之间共享一个签名证书!也没有选择旋转你的SAML签名凭据。鉴于你正在动态配置每个SP,因此没有理由不生成每个SP的签名凭据。
ADFS - Microsoft Windows Server(自托管)
Windows Server 2019版的ADFS不支持每个“依赖方”(我们称之为SP)的唯一“签名令牌”。在运行一些PowerShell以禁用自动旋转之后,你可以手动添加一个用于旋转的备用证书,但它对其他任何东西都没有用处。
在每个SP上运行一个ADFS服务器并在每个服务器上使用单独的签名令牌在技术上是可行的。这将是痛苦的管理,更不用说Windows许可成本,所以我不认为这是一个好的建议。
Gluu——自托管
Gluu的用户界面未提供任何将特定签名身份与SAML SP相关联的方法,也无法创建新的签名身份。
Duo Access Gateway –自托管
根据通用SP配置的文档,整个DAG服务器只有一个证书。你可以重新创建证书,但这似乎会影响到没有唯一密钥选项的每个SP。
SimpleSAMLphp—自托管
SimpleSAMLphp的IDP支持单个服务提供者的唯一密钥,一旦你知道要查找什么,它就很简单了。在“SP远程元数据”参考中,通过signature.certificate和signature.privatekey可以为每个SP指定一个单独的密钥。
虽然它确实很好地支持惟一秘钥,但如果可以的话,你最好不要使用这个软件。该项目有一个经典的PHP webapp漏洞。
总结
1. 有一些深奥的功能,例如动态ACS(断言消费者服务)URL,还有可能会被误用的功能,例如通过未加密的HTTP提供元数据,但是同样,在现代公司SAML中,TLS至关重要。
2. 我不相信libxmlsec1库,尤其是libxml2库。这两个C库都非常常用,没有真正的替代方法。如果你认为使用Ruby,PHP或Python SAML库是安全的,那么你就错了,它们都依赖于libxmlsec1。
3. 尽管C库为我们服务了很多年,但到2020年,由于内存损坏问题严重,它将成为安全负担。libxml2的漏洞历史可以追溯到2004年(16年前!)。虽然libxmlsec1没有相同的记录历史,但我怀疑只是由于缺少必要的报告,而不是真的没有内存安全问题。通过对已知漏洞进行相对快速的修补来积极地维护这些库,可以在一定程度上缓解这种危险,但是如果可以的话,我不会使用这些库。
4. 就我个人而言,我认为对于安全界的外行来说,进入的门槛是相当高的!你必须与第三方进行有效的SSO集成,并且必须能够访问私钥(我们已经不太可能使用它了)或能够更改部分断言(如用户名)。大多数IDP都不愿意让你更改字段,因为它们是从你无权访问的中央目录中提取的。即使确实满足所有这些条件,这些漏洞通常也只能让你在现有组织中横向移动,而不能完全以其他帐户身份登录。
5. 接受不同组织的断言实际上是一件非常可怕的事情,因为作为IDP,你几乎无能为力。保护自己不受攻击的最好方法是测试SP是否有这种行为。不过,我在本文中没有深入探讨这个问题,
6. Gluu是基于Shibboleth的,因此你可以手动设置一个工作配置,这可能会破坏UI。