利用XML签名攻击绕过SAML 2.0 SSO
Posted 安全客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用XML签名攻击绕过SAML 2.0 SSO相关的知识,希望对你有一定的参考价值。
最近,我们注意到一种趋势:许多新西兰的网站计划实现SSO(Single Sign-On,单点登录)以替代密码登录,这其中也包括了许多政府类网络服务。目前最流行的SSO标准是SAML 2.0,它支持多个开发框架,同时也支持多种开发语言。通常情况下,SAML 2.0标准在将验证用户身份的SAML响应数据返回给浏览器的过程中,使用数字签名以防止数据被篡改。但不幸的是,许多开发人员并未恰当地验证SAML响应数据,这就给了攻击者可乘之机,攻击者可以绕过身份认证。
1. SAML 2.0 SSO简介
安全声明标记语言(Security Assertion Markup Language,SAML)是一个基于XML的标准,用于在不同的安全域(Security domain)之间交换认证和授权数据。当使用SAML 2.0标准登录一个站点时,一共有三方参与进来:服务提供商SP(Service Provider,用户打算登录的站点),当事人(Principal,准备登录的用户)和身份提供商IdP(Identity Provider,提供认证服务的权威机构,如Google等)。我们要实现的目标是:IdP通过一种可信的方式告诉SP当事人的身份是谁。
登录时,SP将用户重定向到IdP,同时提交一个SAML请求。一旦IdP确认了用户的身份,就会向SP返回一个SAML响应。发送SSO消息主要有以下三种方式,在标准中称之为“绑定”:
HTTP重定向绑定:将SAML消息直接包含在URL中。
HTTP POST绑定:将SAML消息包含在POST请求体中。
HTTP Artifact绑定:发送一个随机令牌作为标识,通过反向通道获取文档。
前两种绑定方式在实际使用过程中存在一些比较严重的问题。
2. 识别SAML响应
如前所述,通常情况下,SAML响应消息或者通过URL进行传递,如下图所示:
或者包含在一个POST请求体中,如下图所示:
以上两种方式中的SAML消息都要经过浏览器,因此都能被攻击者操纵。但是,另一方面,当通过如下图所示的SAML Artifact方式时:
攻击者可能就束手无策了。因为这些随机令牌被解析进原始消息中了,然后通过一个反向通道被取回,所以除非攻击者有权限访问目标所在的私有网络(比如存在SSL/TLS漏洞刚好被利用),否则这些令牌对攻击者而言并无利用价值。
3. 保护消息传输过程
这里的问题在于,不管是通过HTTP重定向绑定还是通过HTTP POST绑定,IdP返回的验证用户身份信息的数据都要经过用户的浏览器,所以在传输过程中很可能被篡改。而HTTP Artifact绑定则不存在这种问题。
如果SAML响应消息缺乏相应的保护机制,则攻击者可以通过篡改响应数据冒充他人身份。例如,我以Tim的身份登录IdP然后通过简单地修改SAML响应消息声称自己是Emmanuel。实事上,我可以伪造整个响应消息,然后冒充Emmanuel的身份。
当然,SAML标准的开发者并非不够严谨而让错误消息通过认证。相反,他们非常努力地修复这个问题。标准中所采用的方案是:在每个响应消息体中附加一个XML签名,以防止数据被篡改。
4. XML签名标准
XML签名标准是一头庞大的、复杂难懂的野兽,它由一个成员包含许多大牛的工作组所设计,设计初衷是作为一个放之四海而皆准的、用于构建可抵抗篡改攻击的XML文档的解决方案。不幸的是,“放之四海而皆准”最终却变成了“什么也不适合”。
在标准的数字签名应用中,我们对需要签名的文档执行密码散列函数计算出HASH,然后对该HASH值应用数字签名算法得到数字签名。如果被接收的文档和原始文档是完全一致的,那么通过同样的算法对接收文档计算得到的数字签名和接收到的数字签名是完全一致的;然而,两个数字签名只要有一位数据不同,那么就认为接收到的数字签名是无效的,文档也就会被接收方所拒绝。
不幸的是,XML有一个致命的弱点——XML签名标准允许我们只对文档的一部分而不是整个文档进行签名,然后将这个签名嵌入同一个需要验证的文档——这就是所谓的内联签名(Inline signatures)。为实现内联签名,需要在被签名文档中包含对文档局部的引用,通常是引用一个XML元素的ID属性,但理论上允许使用任何符合XPath标准的对象作为表达式。例如,理论上,我可以在一个文档内的任意位置写入指向“倒数第三个<foo>元素”的签名,或者类似的模糊表达式。
当验证一个XML签名时,仅仅确认“这是一个来自签名者的有效的签名吗?”是不够的。我们也必须确认“签名存在吗?如果存在的话,这是指向文档的正确位置的、遵守了正确的标准的、由期望的签名者所签发的、有效的签名吗?”但是经常出现的情况是,至少有一项未被验证。
5. SAML Raider入门
本文所描述的所有攻击方法并不需要很多工具就能够实现,不过通常情况下,Burp Suite的SAML Raider插件是一个很有用的辅助测试工具。
6. 检测
如前所述,签名可能出现在SAML消息中的多个位置并且覆盖整个消息的多个部分。通过保留消息内容,向其中增加新的部分,并且修改剩余部分的结构,我们可以手工构造出一个新的消息,这个新消息从技术上讲仍然是被合法签名的,但是可能被SAML库解析为包含了已签名的关键内容,尽管实际上该关键内容并不存在。
无论何时,SP在进行验证时,有一定的机率验证失败或者进行了不正确的验证,这都给了我们绕过签名验证的机会。打开Burp的拦截功能,拦截SAML请求报文,然后尝试这些转换。每次尝试都要针对一个新的、有效的登录动作,因为通常会有一个Nonce(如随机令牌)阻止我们重复发送相同的请求报文。
在反复测试的过程中,按照如下设置Burp的Proxy,每次仅仅拦截SSO登录请求,将会帮你省去不少额外动作:
签名是必需的吗?
SAML标准要求所有经过非安全信道(如用户的浏览器)进行传输的消息都要有数字签名。不过,如果消息通过安全信道(如SSL/TLS反向通道)进行传输的话,签名就不是必需的了。但是正因为这一点,我们发现SAML使用者经常这样做:如果有任何签名存在,就进行验证;如果签名被移除,则跳过验证。软件基本上假定我们已经检查了来自非安全信道的消息已签名,而真实情况并非如此。
这造成的影响就是,我们能够简单地删除签名,并篡改响应,就好像签名不存在一样。使用SAML Raider插件可以很容易完成该操作。
签名得到验证了吗?
验证XML签名是一个非常复杂的过程,因为XML签名标准期望在签名验证之前先进行一系列的转换和规范化的操作(如忽略掉大量的空白符)。这就导致如果没有一个功能齐全的XML签名库在背后做支撑,那么验证签名就极其困难。这造成的影响有:
开发者普遍对签名验证的内在过程理解不到位。
一些中间组件,如WAF等,并不知道签名是否有效。
签名库可能存在一些配置选项,如允许的规范化方法列表,这些选项对开发者来说却毫无意义。
实现签名标准并不容易,再加上签名标准自身也存在几分晦涩难懂的特性,这就导致了我们现在所遇到的问题。
首先,测试一个签名是否有效是很简单的——改变被签名内容中的某些数据,然后看看是否会导致中断。
签名是来自正确的签名者吗?
另外一个障碍就是接收方是否验证了签名者的身份。我们无法看到这一步是否正确,但使用SAML Raider插件可以很容易地进行测试。
将签名证书复制到SAML Raider的证书商店:
保存并且对此证书进行自签名,我们便得到了同一证书的一个自签名版本。
这时我们就可以使用这个自签名证书对原始请求进行重新签名,可以对整个消息进行签名,也可以只签assertion部分。
你可以确认一下应用正常采用哪种签名方式,也可以两种方式都试一下。
是对响应消息的正确部分进行签名的吗?
XSW攻击原理
SAML标准所允许的签名存在的位置仅有两处:
签名位于<Response>标签中,对<Response>标签及其子节点进行签名。
签名位于<Assertion>标签中,对<Assertion>标签及其子节点进行签名。
SAML标准对于签名允许存在的位置以及允许被签的内容都有明确的描述。
然而,没有人为了仅仅使用SAML就完整地实现复杂的XML签名机制。签名标准是通用的,标准的实现以及为此所开发的软件库也是如此。结果,就有了如下所示的“职责分离”:
XML签名库根据XML验证标准验证签名,它允许从任何地方签名任何内容。
SAML库期望XML签名库告诉它响应消息是否有效。
在两个组件之间往往缺少相关规则去规定哪些内容必须签名的。结果就是,我们经常可以针对文档的不同部分进行签名,而在接收方看来签名依然有效。
通过拷贝文档被签名的部分,并保证签名数据指向这些拷贝部分,我们可以将XML签名库验证的内容和SAML库需要的内容分离开来。
自动化进行XSW攻击
SAML Raider插件可以帮你实现最常见的XSW攻击。
可以尝试下拉框中的每一个选项,然后点击“Apply XSW”以发送请求数据。如果没有出现错误,就改变SAML XML中所有的用户名或者其他用户标识符然后重复这个动作。
7. SAML Raider的局限性
尽管SAML Raider插件可以对常见的情况进行测试,但仍然有一些攻击需要更深一层的理解:
生成针对XML Schema进行验证的响应(需要在可能包含xs:any的元素中隐藏影子副本(shadow copy))。
当Response和Assertion都被签名和验证时,如何绕过验证。
在非SAML上下文中绕过XML签名,如在使用WS-Security扩展的SOAP Endpoints中。
手动XSW
如果SAML Raider插件自带的选项都不起作用,你可以尝试手动测试方法:
解码经过Base64编码的内容以获取SAML响应XML。
检查签名的<Reference>标签是否包含被签名元素的ID。
拷贝文档中其他部分被签名的内容(一般情况下,放在<Response>标签的末尾是可以的;如果还要验证XML Schema,那就放在不会破坏XML Schema的位置)。
从XML签名中删除副本,将其保留在原始文档中。这是有必要的,因为XML封装签名标准去掉了将被验证的签名。在原始文档中,这就是所包含的签名,所以我们必须将其从副本中删除)。
改变原始签名内容的ID为其他值(如改变其中一个字母)。
改变原始assertion的内容。
对上述内容重新进行Base64编码,将其放回请求报文中,然后提交请求。
如果签名验证指向副本,那么你所做的上述改动将被忽略。在实际操作过程中,如果服务器严格限制了请求时间,你应该快速地完成这些步骤。
SAML渗透测试检查表
SAML响应通过浏览器了吗?
响应内容是否被签名了?如果没有签名,尝试改变其内容。
如果删除签名数据,响应内容是否被接受?
如果我们使用其他证书重新进行签名,响应内容是否被接受?
使用SAML Raider自带的8种转换方式生成的结果是否被接受?
如果你更改此类响应,更改后的响应是否被接受?
是否遇到了上文所述的SAML Raider的局限性?如果是,你需要尝试手动测试方法。
以上是关于利用XML签名攻击绕过SAML 2.0 SSO的主要内容,如果未能解决你的问题,请参考以下文章
CVE-2022-23131——绕过 SAML SSO 身份验证