导语:SAML协议出现漏洞,攻击者利用该漏洞可以欺骗SAML系统,在不知道其他用户密码的情况下以该用户身份进行认证。
背景
SAML(Security Assertion Markup Language)即安全声明标记语言,它是一个基于XML的标准,用于在不同的安全域(security domain)之间交换认证和授权数据。SAML是用于单点登录系统的标准。
认证原理
服务提供商(SP)处理SAML响应的过程如下:
用户认证IdP,IdP会生成带签名的SAML响应。用户浏览器转发该响应到SP; SP验证SAML响应签名的有效性; 如果签名有效,SAML响应中的字符串id(比如nameid)就会识别出认证那个用户。
下面是一个简化的SAML响应示例:
<SAMLResponse> <Issuer>https://idp.com/</Issuer> <Assertion ID="_id1234"> <Subject> <NameID>[email protected]</NameID> </Subject> </Assertion> <Signature> <SignedInfo> <CanonicalizationMethod Algorithm="xml-c14n11"/> <Reference URI="#_id1234"/> </SignedInfo> <SignatureValue> some base64 data that represents the signature of the assertion </SignatureValue> </Signature> </SAMLResponse>
上面示例的SAML响应消息中比较重要的元素有Assertion和Signature。
Assertion会说,“我是Identity Provider,认证用户[email protected]”,随后就会为上面的assertion生成一个签名,并保存在Signature元素中。
正常情况下,Signature元素应该保护NameID以防被修改。因为SP可能会用NameID来决定应该认证哪些用户,而签名可以预防攻击者把Assertion从[email protected]变成[email protected]。
如果攻击者在不改变签名的有效性的情况下修改NameID,那么就可以发起攻击。
XML Canononononicalizizization
另外一个跟XML签名相关的方面是XML canonicalization,XML canonicalization允许两种逻辑等价的XML文档有同样的字节表示,比如:
<A X="1" Y="2">some text<!-- and a comment --></A> 和 < A Y="2" X="1" >some text</ A >
两个文档有不同的字节表示,但是表达了同样的信息。
Canonicalization是在签名之前就应用到XML元素中的。这就预防了XML文档中毫无意义的变化引起的数字签名不同的问题。这是非常重要的一点,也就是说“多个不同但是相似的XML文档可以用完全相同的签名”。而XML文档中不同的部分是在canonicalization算法中说明的。
在上面的SAML响应中,CanonicalizationMethod规定了在对文档签名之前使用哪个canonicalization方法。在XML签名说明中有很多的签名算法,最常用的算法是http://www.w3.org/2001/10/xml-exc-c14n#(下文中简写为exc-14n)。
http://www.w3.org/2001/10/xml-exc-c14n#WithComments是exc-14n的一个变种。该变种不会省去注释,所以上面的不同的XML文档就会有不同的canonical表示。
XML APIs
引起该漏洞的一个重要原因是Python的 lxml和Ruby的REXML这样的XML库的一个特别的行为。请看下面的XML元素NameID:
<NameID>kludwig</NameID>
如果想要从该元素中提取用户识别符,Python代码应该是这样的:
from defusedxml.lxml import fromstring payload = "<NameID>kludwig</NameID>" data = fromstring(payload) return data.text # should return 'kludwig'
.text方法会提取NameID元素的内容。
如果我们给元素中加入注释会怎么样呢?
from defusedxml.lxml import fromstring doc = "<NameID>klud<!-- a comment? -->wig</NameID>" data = fromstring(payload) return data.text # should return ‘kludwig’?
你以为返回的结果与没有加入注释是一样的吗?NO,.text API返回的是 klud。可是为什么呢?
如果把XML看做是树形的,那么XML文档应该是这样的:
element: NameID |_ text: klud |_ comment: a comment? |_ text: wig
Lxml在第一个节点结束后就不读取内容了。没有注释的节点的表述是这样的:
element: NameID |_ text: kludwig
另一个有同样行为的XML库是Ruby的REXML。
[get_text]返回第一个子Text节点,该方法返回的是真实的Text节点而不是字符串的内容。如果所有的API或库都在第一个子节点之后停止文本提取,也没有问题,但是有些XML库处理文本提取的方式是不同的:
import xml.etree.ElementTree as et doc = "<NameID>klud<!-- a comment? -->wig</NameID>" data = et.fromstring(payload) return data.text # returns 'kludwig'
在这种方法中,只会提取节点的第一个孩子的内部文本(inner text)。
漏洞分析
根据上面的描述,一共有三种方法引发该漏洞:
SAML响应含有能够识别要认证的用户的字符串; XML canonicalization在签名验证时移除注释的情况,也就是说在SAML中增加注释不会影响签名的有效性; 当响应中含有注释时,XML文本提取只返回XML元素中文本的子字符串时。
所以,攻击者可以在SP处理时修改SAML assertion,将NameID从[email protected]修改为[email protected]。在SAML响应中加入7个字符,就有了下面的payload:
<SAMLResponse> <Issuer>https://idp.com/</Issuer> <Assertion ID="_id1234"> <Subject> <NameID>[email protected]<!---->.evil.com</NameID> </Subject> </Assertion> <Signature> <SignedInfo> <CanonicalizationMethod Algorithm="xml-c14n11"/> <Reference URI="#_id1234"/> </SignedInfo> <SignatureValue> some base64 data that represents the signature of the assertion </SignatureValue> </Signature> </SAMLResponse>
对依赖SAML的服务的影响
SAML IdPs和SP通常是可配置的,所以关于该漏洞造成的影响有很大的变化空间。比如,使用邮箱地址并用白名单来验证域名的SAML SP被利用的可能性就比用任意字符作为用户id的SAML SP被利用的可能性低很多。
在IdP端,允许用户开放注册账户也会增加问题带来的影响。而手动的用户验证过程可以让利用变难。
修复
修复该漏洞的关键在于系统和服务于SAML之间的关系
如果在运行或维护IdP或IP,那么最佳的方式就是确保SAML处理库不受该漏洞的影响。下面是研究人员发现的一些含有该漏洞的SAML库,研究人员认为还有一些受该漏洞影响的库还没有被发现。研究人员发现该漏洞影响的产品有:
OneLogin - python-saml - CVE-2017-11427 OneLogin - ruby-saml - CVE-2017-11428 Clever - saml2-js - CVE-2017-11429 OmniAuth-SAML - CVE-2017-11430 Shibboleth - CVE-2018-0489 Duo Network Gateway - CVE-2018-7340
另一种修复的方法就是选择一种默认的canonicalization算法。当攻击者加入注释时,canonicalization算法会让签名发生变化。但是这种修复方法需要IdP和SP的支持,所以通用性不强。
如果SAML SP使用了双因子认证,那么该漏洞的影响就更小了,因为该漏洞只允许绕过用户的第一个因子。如果IdP负责第一个因子和第二个因子的认证,那么该漏洞就可以绕过双因子认证。
如果是维护SAML处理库
那么要确保有注释时,SAML库提取了给定XML元素中的全部文本。许多SAML库都有单元测试,那么很容易可以修改测试用例来测试在签过名的文档中加入注释后提出特征的情况。如果测试不通过,那么可能就受到该漏洞的影响。
另外一个修复方法是修改XML标准。研究人员没有发现有正确交互行为的标准,因此,这些相关的标准是如何交互的就非常重要。