此文章描述了一个影响使用SAML的单点登录(SSO)系统的新型漏洞。 此漏洞可允许那些经过身份验证的攻击者对SAML系统进行欺骗,以便在不知道受害者用户密码的情况伪造该用户进行登录。
Duo Security
的高级研究团队Duo Labs发现了多个受此漏洞影响的产品:
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
我们建议依靠基个人用户使用基于SAML的SSO进行软件的更新以用来修补受到影响的软件。
如果用户运行Duo Network Gateway(DNG)
的Duo Security,请在此处查看我们的产品安全通报。
SAML(The Security Assertion Markup Language)是单点登录系统中使用的流行标准。 Greg Seador撰写了一篇关于SAML的详细教学指南,如果你不熟悉它我强烈推荐这个参考。
为了讲述此漏洞,我们要掌握的最重要的概念是SAML响应对服务提供者(SP)的意义以及如何处理它。
响应处理需要注意许多细节之处,但我们可以简洁的进行表述:
用户向身份提供者(IdP)(例如Duo或GSuite)进行身份验证,生成已签名的SAML响应。 然后,用户的浏览器将此响应转发给SP,例如Slack或Github
。
SP验证SAML响应签名。
如果签名有效,则SAML响应中的字符串标识符(例如,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>
此示例省略了大量信息,但省略的信息对于此漏洞并不太重要。 上面的XML blob
中的两个基本元素是Assertion
和Signature
元素。 Assertion元素最终说“嗨,我是身份提供者,验证用户[email protected]
”。 为该Assertion元素生成签名并将其存储为Signature元素的一部分。
如果上述内容完成,Signature元素会阻止用户修改NameID。 由于SP可能使用NameID来确定应对哪些用户进行身份验证,因此签名可防止攻击者使用NameID
“[email protected]”更改自己的断言为“[email protected]”。 如果攻击者可以在不使签名失效的情况下修改NameID,那就大事不妙了!
XML签名的下一个相关内容是XML规范化。 XML规范化允许两个逻辑上等效的XML文档拥有相同的字节表示。 例如:
<A X="1" Y="2">some text<!-- and a comment --></A>
和
< A Y="2" X="1" >some text</ A >
这两个文档具有不同的字节表示,但传达相同的信息(即它们在逻辑上是等价的)。
规范化过程在签名之前应用于XML元素。 这可以防止XML文档中由于不存在差异而导致的不同数字签名情况出现。这是一个重点,因此我将在此强调:多个相似的XML文档可以具有相同的签名。 在大多数情况下这是有意的,因为规范化算法指定区别了重要位置的差异。
正如我们在上面的SAML响应中已注意到的,CanonicalizationMethod
会在签署文档之前指定哪种规范化方法。 XML签名规范中列出了几种算法,但实际中最常见的算法是http://www.w3.org/2001/10/xml-exc-c14n#
(简称为to exc-c14n
)。
exc-c14n
的变体具有标识符http://www.w3.org/2001/10/xml-exc-c14n#WithComments
。 exc-c14n
的这种变化不会省略注释,因此上面的两个XML文档将不具有相同的规范表示。 这两种算法之间的区别在以后很重要。
导致此漏洞的原因之一是XML库(如Python的lxml或Ruby的REXML)的一种意外情况。考虑以下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’?
如果你不希望添加评论,你会期待拥有完全相同的结果。 但是,lxml
中的.text API
会返回klud!
这是为什么?
我认为lxml在这里所做的事情在技术上是正确的。 如果我们使用树的形式来表示XML文档,则XML文档如下所示:
element: NameID
|_ text: klud
|_ comment: a comment?
|_ text: wig
lxml
只是在第一个文本节点结束后才读取文本。 将其与未注释的节点进行比较,该节点将表示为:
element: NameID
|_ text: kludwig
在这种情况下停在第一个文本节点是完全合理的!
另一个表现出类似行为的XML解析库是Ruby的REXML
。 其get_text
方法的文档提示了这些XML API出现此行为的原因:
[get_text] returns the first child Text node, if any, or nil otherwise. This method returns the actual Text node, rather than the String content.
如果所有XML API都以这种方式运行,那么在第一个子项之后停止的文本提取虽然不直观,但并没有没问题。 不幸的是,情况并非如此,一些XML库具有几乎相同的API,但处理文本提取的方式不同:
import xml.etree.ElementTree as et
doc = "<NameID>klud<!-- a comment? -->wig</NameID>"
data = et.fromstring(payload)
return data.text # returns 'kludwig'
我还看到了一些不利用XML API的实现,但是通过仅提取节点的第一个子节点的内部文本来手动进行文本提取的操作方法。 这只是同精确子字符串文本提取类似的另一种方法。
所以现在我们有三个步骤来实现这个漏洞:
SAML响应包含标识身份验证用户的字符串。
XML规范化(在大多数情况下)将删除注释作为签名验证的一部分,因此向SAML响应添加注释不会使签名无效。
当存在注释时,XML文本提取可能仅返回XML元素中文本的子字符串。
因此,作为可以访问帐户[email protected]
的攻击者,我可以修改自己的SAML
断言,以便在SP处理时将NameID
更改为[email protected]
。 现在,通过简单的七字符添加到之前的SAML Response
中,我们有了现在的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 IdP
和SP通常是可配置的,因此有很大的空间来增加或减少影响范围。
例如,使用电子邮件地址并针对白名单验证其域的SAML SP
比允许任意字符串作为用户标识符的SP更不可能被利用。
在IdP方面,允许用户注册帐户是解决此问题的一种方法。 手动用户供应过程可能会增加进入的困难,使得利用更加不可行。
此问题的补救在某种程度上取决于用户与SAML的关系。
Duo已在1.2.10版本中发布了Duo Network Gateway的更新版本。 如果用户将DNG用作SAML服务提供商且版本不是1.2.10或更高版本,我们建议用户进行升级。
最佳补救措施是确保用户的SAML处理库不受此问题的影响。我们发现了几个SAML库,这些库利用了这些XML API
进行了错误的手动文本提取,但我确信有更多的库不能很好地处理XML节点中的注释。
另一种可能的补救措施可能是默认为规范化算法,例如http://www.w3.org/2001/10/xml-exc-c14n#WithComments
,它在规范化过程中不会遗漏注释。这种规范化算法会导致攻击者添加注释从而使签名无效,但规范化算法标识符本身不得受到篡改。但是这种修改需要IdP和SP支持,所以这不是通用的。
此外,如果用户的SAML服务提供商实施双因素身份验证,这将有很大帮助,如此此漏洞只允许绕过用户的第一个身份验证因素。请注意,如果用户的IdP负责第一因素和第二因素身份验证,则此漏洞可能会绕过这两者!
这里最明显的补救措施是确保用户的SAML库在存在注释时提取给定XML元素的全文内容。 我发现的大多数SAML库都拥有某种形式的单元模块,并且很容易更新提取NameID
等属性的测试,只需添加注释到预签名文档即可。
另一种可能的修补方法是更新库,以便在签名验证后对文本提取等任何处理使用规范化的XML文档。 这可以防止此漏洞与XML规范化问题引入的其他漏洞。
就个人而言,我认为受此漏洞影响的库的数量表明许多用户也认为XML内部文本API不受漏洞影响,因此这可能是改变API行为的激励因素。
另一种可能的补救途径是改进XML标准。 通过我的研究,我没有制定正确行为的标准,以及如何进行相关操作。
我们的披露的漏洞可以在这里找到https://www.duo.com/labs/disclosure
。 在这种情况下,由于漏洞影响了多个供应商,我们决定与CERT/CC
合作来协调披露。 以下是漏洞时间表:
本文为2018年十大网络黑客技术题名文章,欢迎读者来阅。
本文为翻译稿件,来自:https://duo.com/blog/duo-finds-saml-vulnerabilities-affecting-multiple-implementations