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,并提取相关的用户名和其他字段。现在,你可以看所有你想看的图片了!
那个签名秘钥听起来很吓人!你的本能可能是不惜一切代价保护该密钥。密钥值得保护,但是对你的SAMLIDP安全最大的可信威胁不是拥有你的SAML服务器的攻击者。
攻击SAML的方法
攻击SAML的方法有很多!尽管独特的签名密钥可以解决其中的一些问题,但这并不是万能的。
受众限制问题(Audiencerestrictionissue)
这是我在这篇文章中关注的问题,稍后我将更详细地讨论它。不出意料,惟一签名密钥解决了这类问题。
IDP签名密钥被盗
确实,能够访问你的IDP的人可以获得签名密钥的副本,并以任何人的身份登录到与你集成的任何网站。如果这是你所关心的威胁,则仅提供签名预言的硬件支持的密钥是正确的防御措施。
XML和XML安全库
如果可以的话,你应该使用一个内存安全的库。也就是说,这对第三方的断言验证没有实际影响,而且如果使用惟一签名密钥,你的安全状态也不会改变。
XML处理问题
XML安全性是本世纪初出现的一种内联签名格式,当时没有人提出要求,需要它的人也更少。但用的人多了,问题就出现了,这些问题包括,忽略用户名中的XML注释、签名格式本身忽略对XML解析器有影响的注释,以及不检查你验证的签名是否实际覆盖了你信任的所有数据。
你可以通过使用惟一的签名密钥来减小这些问题的影响范围。你只需要关心单个应用程序的内部权限,而不是允许用户登录任何与你集成的服务(授权与否)。
解决方案
核心问题是缺乏受众限制验证,换句话说,SP没有检查断言是否针对它。SAML的设计思想是你的IDP将只有一个签名密钥,你可以将它分发给与你集成的每个人。考虑到SAML的学术背景,它应该是一个合作协议,组织之间密切合作。现代企业SAML忽略了所有这些有趣的特性,因为它们是巨大的安全和配置噩梦。
当你的IDP签署一个断言时,它包含两个供SP验证的字段:SP的实体ID和断言要发送到的URL。SP可以悄悄地忽略这些字段,而你对此无能为力。
作为负责签名密钥的IDP,你如何保护自己免受不可避免的弱SP攻击?
处理一堆签名钥匙
相比依赖协议的某些部分,唯一可扩展的方法是强制你的断言仅在一个SP上有效,而且是通过具有唯一签名每个SP的密钥。
之所以可行,是因为几乎每个SAMLSP实现都包含三个部分。他们将从请求中提取用户名,在断言中验证签名,并拒绝无效的断言签名,其他所有内容都应视为可选内容。
虽然SP可以忽略你的签名,但是它的测试超级简单,而且这种事情很容易被漏洞赏金报告人员发现。与更深奥的受众限制测试不同,这里不涉及任何复杂性。
如何管理这么多密钥?
过去,我通过编写一堆Ruby自动生成相关XML来处理每个SP的唯一签名密钥,从而为Shibboleth管理了多个密钥。每当我遇到另一个错误处理断言的SP时,我都会感谢为减少这个我们不得不担心的问题而付出的努力。
在理想的情况下,我们不会使用SAML。SAML是一种繁琐的协议,可让你创建带有身份验证内联签名的身份提供者的网状网络,其中XML中的空格确定签名是否有效。但是SAML以及OAuth2.0和不完美的OIDC都将保留下来。鉴于SAML是事实上的企业单一登录协议,我们将忽略它。
如果你的IDP不支持此功能(请参见下文),则应向他们打开功能请求!这是你的IDP应该支持的重要安全控制。
如果你的IDP确实支持这个功能,为你的新应用程序发出每个sp的签名密钥。使用旧的证书迁移应用程序需要做很多工作,但是如果你有特别敏感的应用程序,则值得这样做。
所有SaaSIDP都应在没有任何用户干预的情况下生成每个应用程序的签名密钥,默认情况下,每个SP密钥的使用率极高,可以悄悄地提高与这些提供商签约的每个企业的安全性。截至2020年3月,唯一获得此权限的提供商是AzureAD。
自托管的IDP应确保它们支持按SP的签名密钥,并具有启用此功能的文档。理想情况下,共享签名密钥的配置不太明显,因此管理员默认情况下选择每个SP的签名密钥。
虽然最终要由SSO管理员做出正确的SSO选择,但是我们作为安全行业的责任是使正确的选择变得容易。
IDP支持多个签名密钥
没有实施指南,最佳做法将无济于事。这是截至2020年3月我已测试的各种主要IDP(包括SaaS和自托管选项)的列表。如果你的首选IDP不在此列表中或条目不正确,请与我们联系。
AzureAD–SaaS
AzureAD自动为每个"企业应用程序"生成一个新密钥,并且无法在控制台中的应用程序之间共享证书。你可以手动上传自己的证书和私钥,但这并不容易,我也不鼓励这样做,AzureAD应该是所有其他SaaSIDP的模型。
Shibboleth-Java(自托管)
你必须编写大量的XML才能使它工作,如果你花了几个小时绞尽脑汁地研究SpringXML配置,就不会出现任何问题。我已经包括了基本的需求。要点是,你需要创建单独的签名凭据,包括安全配置中的签名凭据,然后从单独的SP引用该安全配置。
另外,我确实喜欢Shibboleth是全java的状态,即没有内存损坏!,可以在本地自己的服务器上运行,并且采用非常符合标准的方法,从而降低了被奇怪的XML问题影响的可能性。
conf/relying-party.xml的示例配置(Shibboleth文档):