SAML:糟糕设计的分形
摘要
本文批判了SAML认证协议的设计缺陷和复杂性,主张弃用它,转而采用OpenID Connect等现代替代方案。
暂无内容
查看缓存全文
缓存时间: 2026/09/22 21:53
# "SAML:一个设计不良的分形结构"
来源:https://blog.trailofbits.com/2026/09/21/saml-a-fractal-of-bad-design/
SAML(安全断言标记语言)认证协议诞生于学术界,成长于企业IT部门,至今仍是这些机构中的基石技术。然而,是时候让它退役了。随着2000年代末软件即服务(SaaS)公司的兴起,IT部门需要一种方式让用户认证到众多新的网络服务中。SAML与当时兴起的单点登录(SSO)行业满足了这一需求。但是,SAML正被其自身的复杂性压垮。是时候废弃它,转向像OpenID Connect(OIDC)这样的现代替代方案了。在这篇文章中,我将探讨SAML由委员会设计的起源,其在学术和企业环境中的发展历程,安全研究社区如何使其逐渐瓦解,以及它(希望)被更新协议取代的前景。
“SAML入门”> SAML的险恶之处在于,它其实大部分内容都很容易理解,但它建立在沙子、骨粉和灰烬的基础上;它能工作……前提是你假设XML签名验证是可靠的。但XML签名验证是深受诅咒的,它极其复杂,以至于大多数部署的SAML实现都在封装libxmlsec这个没人愿意读的棘手C代码库。——Thomas Ptacek,2023 (https://news.ycombinator.com/item?id=37564758)
## SAML与SSO产业的诞生
维基百科告诉我 (https://en.wikipedia.org/wiki/SAML),“SAML是一种基于XML的安全断言标记语言。”它由OASIS(结构化信息标准促进组织)安全服务技术委员会(SSTC)于2002年创建。好吧,以现代标准来看,我们这个开头并不理想。尽管XML有其可取之处,但与JSON等新替代方案相比相当复杂,稍后我们会详细讨论。此外,一个由子委员会组成的委员会开会是“功能过度设计”协议(例如瀑布模型 (https://en.wikipedia.org/wiki/Waterfall_model)、前期大设计 (https://en.wikipedia.org/wiki/Big_design_up_front) 等)的温床。果然,我们现在将四种(!)基于XML的安全协议塞进了一个协议中:
> ……以下知识产权被贡献给SSTC:- Netegrity的安全服务标记语言(S2ML)- Securant的AuthXML- VeriSign的XML信任断言服务规范(X-TASS)- Jamcracker的信息技术标记语言(ITML)——SAML:历史 (https://en.wikipedia.org/wiki/SAML#History)
然而,对这种协议的渴望是毋庸置疑的。随着互联网从90年代的Web 1.0转向2000年代初的Web 2.0,用户和组织需要一种简单的方式认证到众多新的网络服务。学术界是这一运动的最大推动者,尽管并非唯一:2002年耶鲁大学的中央认证服务(CAS),2003年由研究型大学联盟Internet2(包括我的母校 (https://its.umich.edu/enterprise/wifi-networks/researchers/internet2))推出的Shibboleth身份提供商,2003年微软的ADFS,以及约2007年由与学术界关系密切的挪威国有公司Uninett推出的simpleSAMLphp (https://web.archive.org/web/20071214140857/http://rnd.feide.no/simplesamlphp)。所有这些认证项目最终都以某种方式支持了SAML。就像之前的ARPANET一样,大学处于互联网发展的最前沿,也是网络服务的最早消费者。一旦这个协议可用性和初创学术试验场的基础层建立起来,商业产业就抓住机会,发展成为一个价值数十亿美元的行业。
SSO、身份和认证提供商行业也在2000年代初期开始兴起,但真正成熟是在几年后:Ping Identity(2002年)、OneLogin(2009年)、Okta(2009年)和Duo Security(2010年)。这些公司本质上都建立在SAML协议之上,Duo例外,它在2015年才推出了其首个SSO产品,而这正是我在这个故事中的切入点。我参与了Duo的第一个本地部署访问网关产品 (https://duo.com/docs/dag)(DAG)的开发,它基于simpleSAMLphp构建,显然也使用了SAML协议。正是在那里,我深入了解了SAML协议,并花费多年时间消化其冗长的规范。我亲历了Kelby Ludwig (https://kel.bz/) 发现XML注释绕过 (https://i.blackhat.com/us-18/Thu-August-9/us-18-Ludwig-Identity-Theft-Attacks-On-SSO-Systems.pdf) 的事件,但我们将稍后讨论各种攻击和SAML的缺陷。可以说,SSO和认证提供商行业当时蓬勃发展,其中很大一部分都建立在SAML协议之上。
## 盔甲上的裂缝
XML签名包装(XSW)攻击是射向SAML脚踵的那支著名箭。尽管更早就有针对签名包装(2005 (https://dl.acm.org/doi/10.1145/1103022.1103026)、2008 (https://arxiv.org/pdf/0812.4181) 和2009 (https://lists.w3.org/Archives/Public/public-xmlsec/2009Nov/att-0019/Camera-Ready.pdf))和SAML(2008 (https://dl.acm.org/doi/10.1145/1456396.1456397))的安全研究,但我认为这一切的鼻祖是《破解SAML:随心所欲成为任何人》(2012 (https://www.usenix.org/system/files/conference/usenixsecurity12/sec12-final91.pdf))。它将理论与实践相结合,并产生了一种自动化方法 (https://github.com/CompassSecurity/SAMLRaider/blob/v2.5.2/src/main/java/helpers/XSWHelpers.java#L34-L39) 来检查XSW攻击。这是我们实现DAG时的指路明灯。这也是我们选择simpleSAMLphp作为构建模块的原因。PHP,尤其是在当时 (https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/),其安全记录并不为人称道,但simpleSAMLphp的安全记录(归功于《破解SAML》)本身就很出色:simpleSAMLphp在当时几乎没人知道XSW是什么的时候,就对XSW攻击具有韧性。
尽管XSW是2012年论文的焦点,但它至今仍然存在 (https://portswigger.net/research/the-fragile-lock)。如果我们知道这个漏洞类别,为什么不能修复它?但在我们讨论SAML的缺陷之前,我们首先必须考虑它建立其上的不稳定基础:XML。
XML在安全记录方面(或缺乏记录)也绝非小事。对于90年代的开发者来说,这些漏洞类别 (https://cheatsheetseries.owasp.org/cheatsheets/XML_Security_Cheat_Sheet.html) 可能更熟悉,但它们在今天的XML中仍然存在:XXE、实体扩展(“十亿笑”攻击)、DTD检索(SSRF)、XPath/XQuery/XInclude/XSLT/CDATA注入等等。一个SAML库甚至在处理实际的SAML功能之前,就需要处理所有这些漏洞类别。
除了安全漏洞类别,XML与JSON等相比,其本身的复杂性也是巨大的。在XML中,你有标签、元素、属性、注释、命名空间、标记与内容、模式、CDATA、DOCTYPE等等。在JSON中,你基本上只有键、值、对象和列表。复杂性通常与安全相悖,这是我认为SAML是一个设计不良的分形结构的原因之一。
SAML为学习协议设计提供了充足的机会。在本节中,我将介绍我认为对SAML作为认证协议的长期可行性是致命缺陷的五个问题。这些缺陷也可用于设计新的认证协议。也就是说,你可以绕开这些缺陷,或者取其反面,尝试将其内置于协议中。
### 建立在XML之上
如上所述,SAML建立在XML之上,而XML是复杂的,但这并不是委员会的错 (https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExMXBhN2doeGV5eXBoamUwZDJ1M2owcW9ndDJvY2c3NnlkMzF4ZDBxbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Dvw2lJqlTuJmo/giphy.gif)。XML是他们当时有的东西,也是人们使用的东西。JSON在2001年被“发现” (https://inkdroid.org/2012/04/30/lessons-of-json/),但这正好是SAML委员会开会的时候,他们不太可能围绕一种实验性的新格式设计认证协议。尤其是当它迎合JavaScript而他们又写了很多Java的时候。
可以设计一个定量的复杂性度量来比较XML与JSON或SAML与JWT/OIDC(例如,规范/RFC字数,规范/RFC规范性 (https://datatracker.ietf.org/doc/html/rfc2119) 字数等),但这将需要一篇独立的博文或论文。为了紧扣主题,我将在此避免这样做,只需说明XML比JSON这样的东西复杂得多。
### 规范化
规范化 (https://www.w3.org/TR/xml-exc-c14n/) (C14N) 是当你想要处理XML这种混乱结构,计算其哈希值并获得一致结果时所做的事情。换句话说,如果服务提供商(SP)和身份提供商(IdP)无法就XML数据的一致表示达成一致,那么字节就不匹配,签名就不匹配,你的认证就会失败。然而,说起来容易做起来难。
规范化漏洞在2018年促成了Kelby的XML注释绕过:
XML规范化(归功于《身份盗窃》)规范化通常是解析器差异和/或“往返”漏洞的前兆,而这些正是大多数现代SAML攻击所使用的:
- Go标准库中XML往返漏洞的协调披露 (https://mattermost.com/blog/coordinated-disclosure-go-xml-vulnerabilities/)(2020年)
- 保护整个网络的XML实现 (https://mattermost.com/blog/securing-xml-implementations-across-the-web/)(2021年)
- 利用libxml2怪癖绕过GitHub企业版的SAML认证 (https://repzret.blogspot.com/2025/02/abusing-libxml2-quirks-to-bypass-saml.html)(2025年)
- 冒充任何人:使用解析器差异绕过SAML SSO认证 (https://github.blog/security/sign-in-as-anyone-bypassing-saml-sso-authentication-with-parser-differentials/)(2025年)
- SAML轮盘赌:黑客总是赢家 (https://portswigger.net/research/saml-roulette-the-hacker-always-wins)(2025年)
- 脆弱的锁:SAML认证的新绕过方式 (https://portswigger.net/research/the-fragile-lock)(2025年)
### 信封式签名
信封式签名 (https://www.w3.org/TR/xmldsig-core1/#sec-EnvelopedSignature) 问题是规范化的近亲。简而言之,如果你试图将签名插入到你正在签名的数据载荷中,你就会遇到麻烦。让我们比较和对照一下JWT和SAML:
JWT与SAML签名对比(归功于jwt.io和samltool.io)在上面的JWT示例中,蓝色签名*分离*于JSON载荷,并用句点(“.”)在JWT中分隔。在SAML示例中,`Signature`元素被插入(“信封式”)在`Assertion`元素中。这里的问题是,当你*也在修改它*时,要获得数据逐字节、规范化等价的表示是非常困难的!当涉及像XML这样复杂的格式和复杂的规范化规则时,就更加困难了。
### “功能过度”设计
这种设计缺陷本质上转译为“你不会需要它”(YAGNI)。说它不公平是因为SAML规范中实际使用的部分在过去20年中发生了变化(抱歉,SOAP和artifact绑定 (https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf))。然而,事实是,99%的现代SAML实现使用的是非常相似的数据形状和规范子集。你在野外遇到的任何SAML认证可能都避开了90%的规范。这为基本未使用的功能增加了显著的复杂性。
> 如果我要为某个新项目添加SAML支持,我会考虑在所有标准SAML检查之外,还拒绝任何与Okta、Onelogin、Google或Shibboleth生成的消息形状不同的消息。——Thomas Ptacek,2021 (https://news.ycombinator.com/item?id=28080553)
### 僵化
SAML是在不同时代为不同时间设计的,并且没有得到必要的更新。这些问题通常具有实际影响而非理论意义。我所说的僵化大致可以枚举如下:
1. **OIDC假设使用HTTP (https://datatracker.ietf.org/doc/html/rfc6749),而SAML与传输无关。** 当然,存在SAML HTTP绑定且最常用,但它们不是必需的。这赋予了SAML一定程度的灵活性,但也意味着这种灵活性必须被很好地定义,在某处实现,并且可能包含错误。SAML出现于HTTP + TLS尚未成为网络服务通信主要骨干的时代,它从未与现代格局相协调。HTTPS允许OIDC将加密、可信的通信卸载到传输层。
2. **OIDC通常假设连接的网络拓扑,而SAML则不然。** 最常见的OIDC流程(授权码)假设OpenID Provider(OP)和Relying Party(RP)可以直接通信(OIDC OP/RP == SAML IdP/SP)。当然,存在使用表单的OIDC隐式流程 (https://auth0.com/docs/get-started/authentication-and-authorization-flow/implicit-flow-with-form-post),但今天已非常少见。此外,SAML有用于直接通信的artifact绑定,但也非常罕见。关键在于,如果OP和RP可以直接通信,这就减轻了认证响应载荷包含做出认证和授权决策所需全部信息的压力。这减少了载荷大小和复杂性。OIDC OP和RP可以在后台通道交换信息。
3. **OIDC多年来有机增长,而SAML很大程度上是前期设计的。** OIDC包含了数十个在多年间有机增长的规范和RFC。这些文档通常是为了解决特定需求而创建的,而不是试图预测所有未来需求并提前构建。这类似于敏捷方法与瀑布方法的对比,如第一节所述。考虑以下非详尽的时间线:1. OpenID Connect 1.0规范发布(2014年)2. JOSE协议栈通过RFC 7515-7519为JW{S,E,K,A,T}定稿(2015年)3. PKCE通过RFC 7636发布(2015年)4. 针对移动/原生应用的PKCE通过RFC 8252发布(2017年)5. 针对IoT设备的设备授权授权通过RFC 8628发布(2019年)6. 针对MFA工作流的持有证明(DPoP)通过RFC 9449发布(2023年)7. 针对SPA的PKCE通过RFC 10017发布(2026年)
我也觉得历史背景很有趣。例如,SAML成熟于VPN和网络分化的时代,因此有了上面的第(2)点。如果IdP或SP位于公司防火墙后面,并且无法直接与另一端通信,那么整个部署就会停滞,该供应商也会失去销售机会。SAML需要无缝地应对这种情况。谷歌的BeyondCorp模型 (https://research.google/pubs/beyondcorp-a-new-approach-to-enterprise-security/) 和零信任架构在2014年颠覆了这一观念。此外,SAML未能预见移动、SPA和IoT革命,当这些技术在2000年代末登上历史舞台时,它毫无准备。尽管SAML仍然被广泛使用。
相似文章
@NikkiSiapno:SSO vs OAuth vs OIDC vs SAML 𝗦𝗦𝗢 是一种用户体验,而非协议。它让用户登录一次即可访问多个…
作者解释了SSO、OAuth、OIDC和SAML之间的区别,阐明SSO是一种用户体验,OAuth用于授权,OIDC在OAuth之上提供身份验证,而SAML是一种较旧的企业级协议。
停止使用 JWT
一篇观点文章,反对在身份验证和会话管理中使用 JSON Web Token(JWT),并指出了其安全性和设计上的问题。
关于钓鱼的吐槽:这不是用户的错(也不是DNS的错)
本文通过指出分散的认证流程和不当的URL处理方式如何让用户难以区分合法网站与骗局,批评了常见的钓鱼漏洞,倡导使用一致的域名以提高安全性。
Web 安全太难了
Eric Lawrence 批评了 Cloudflare 新的 Wallet 注册流程,指出它与同意式钓鱼攻击极为相似,说明了为什么 Web 安全如此困难。
CLI 认证的正确方式
本文批评了许多 CLI 工具常用的 OAuth 回环认证模式,该模式在无头机器上无法工作,并提倡使用自 2019 年起已成为标准的设备码流等替代方法。