前言

研究某产品反序列化EXP时,搜集到的POC只有一段16进制字节序列难以利用,遂有下文对Java序列化和反序列化的学习。
大致内容如下:
  1. 序列化和反序列化示例
  2. 序列化数据组成解构
  3. 反序列化漏洞形成原理

序列化和反序列化

序列化:将程序运行时所需要的Java对象转化为字节序列并存储在文件系统中,一般为.ser后缀的文件,ObjectOutputStream.writeObject()方法可以将对象序列化。
反序列化:将存储在文件系统的字节序列转化成对象供程序使用,ObjectInputStream.readObject()方法可以将字节序列转化成对象。
可序列化的类必须继承 java.io.Serializable;序列化机制使对象得以脱离程序之外独立存在。
示列:
Employ.java
import java.io.Serializable;//该类必须实现java.io.SerializablepublicclassEmployimplementsSerializable{ public String name;publicint age;}
test.java

import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;publicclasstest {publicstaticvoidmain(String[] args){//将e转化为字节序列存储于/tmp/1.ser Employ e = new Employ(); e.name = "zhangyida"; e.age = 15;try { FileOutputStream fops = new FileOutputStream("/tmp/1.ser"); ObjectOutputStream obos = new ObjectOutputStream(fops); obos.writeObject(e); obos.close(); System.out.println("Serialized data is saved in /tmp/1.ser"); }catch (IOException i){ i.printStackTrace(); } }}
desEmploy.java

import java.io.ObjectInputStream;publicclassdesEmploy {publicstaticvoidmain(String[] args) { Employ e = new Employ();try { FileInputStream fis = new FileInputStream("/tmp/1.ser"); ObjectInputStream obis = new ObjectInputStream(fis); e = (Employ) obis.readObject(); }catch (IOException i){ i.printStackTrace();return; } catch (ClassNotFoundException ex) { System.out.println("Employ class not found!"); ex.printStackTrace();return; } System.out.println(e.name); System.out.println(e.age); }}
运行test.java,生成一个字节序列存储于/tmp目录下。

运行desEmploy.java,将存储在1.ser内的字节序列转化为对象。

字节序列数据格式

字节序列格式:
一个java对象序列化后的字节序列由三部分组成(magic、version、contents),其中magic和version是常量,magic表示内容类型,version则是版本号,contents是被序列化对象的属性、状态等内容。java序列化stream的特征aced 0005及aced 0005编码后的字符串。
stream:magicversion contents
字节序列中的contents,可能由一个content组成也可以有多个content。

contents content contents content
content由一个或多个的object(对象)、blockdata(数据块)组成。

contentobject blockdata
object(对象),序列化的Stream中常见的对象有newObject、newClassDesc、newString。

objectnewObjectnewClassnewArraynewStringnewEnumnewClassDescprevObjectnullReferenceexceptionTC_RESET
newObject,表示序列化对象是一个普通object对象;标识符为TC_OBJECT;classDesc表示一个
ObjectStreamClass对象,其保存着className、序列化ID、类字段等信息;newHandle是其句柄值,类似对象ID;classdata[],保存类实例化对象属性。
newObject:TC_OBJECTclassDesc newHandle classdata[]
newString,表示序列化对象是一个字符串常量对象。

newString:TC_STRINGnewHandle
newClassDesc,表示序列化对象是一个
ObjectStreamClass对象,TC_CLASSDESC是其标识符;className是其类名;serialVersionID是其序列化版本ID,当对字节序列被反序列化会将此值与本地相应实体类的serialVersionUID比较,一致则反序列化,不一致则抛出异常;classDescINFO保存着类的序列化属性。
classDescINFO:
classDescFlags(0x02 - SC_SERIALIZABLE or 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLE),0x02表示类实现了Serializable接口但并未重写readObject方法,0x03表示类即实现了Serializable接口又重写了readObject方法。当值为0x03时,反序列化该字节序列时会调用重写的readObject方法。
fields:类的属性
classAnnotation:类注解,一般为TC_ENDBLOCKDATA
superClassDesc:被序列化对象的类的父类是否可序列化,可序列化时则写入父类的classDesc,不可序列化时则为TC_NULL。
newClassDescTC_CLASSDESCclassName serialVersionUID newHandle classDescInfo*classDescInfo          classDescFlags fields classAnnotation superClassDesc 
使用SerializationDumper可以查看其序列化之后的相关信息:Employ类的实例对象,Int属性age值为15,String属性name值为zhangyida;Employ类未重写readObject方法。

用途及使用场景

用途:
1、将对象的字节序列永久保存在硬盘以便使用。
2、网络传输。
使用场景:
使用场景丰富,简单列举常用场景。
1、服务器启动后,将用户session信息永久保存在硬盘中,当服务器出现问题需要重启时可以直接从硬盘中读取字节序列还原用户seesion。
2、JNDI、RMI远程代码调用
3、xml Xstream、XMLDecoder等(HTTP body:Content-Type:applicatin/xml)、json(Jackson、fastjson)http请求中包含。
4、...

反序列化命令执行漏洞原理

被序列化对象的类重写了readObject方法,且重写的readObject方法存在问题可执行java代码造成反序列化命令执行漏洞。
示例:
Employ类重写readObject方法,添加一个命令执行的方法
Employ.java
import java.io.ObjectInputStream;import java.io.Serializable;import java.io.IOException;publicclassEmployimplementsSerializable{public String name;//public int age;privatevoidtest(String name){ System.out.println(name); }privatevoidreadObject(ObjectInputStream objin){try { objin.readObject(); Runtime.getRuntime().exec("open /System/Applications/Calculator.app"); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }}
重新生成字节序列后再反序列化字节序列将执行系统命令弹出计算器,故Java反序列化漏洞就是当前类重写readObject方法时可调用执行系统命令的函数造成远程命令执行的漏洞。因此真实环境中的java反序列化漏洞需要去寻找重写了readObject方法的且可利用的类能通过调用Runtime.getRuntime().exec()或者其他函数来达到执行系统命令目的,这个可利用的类称之为gadget,调用过程称为gadget chain。

E
N
D
Tide安全团队正式成立于2019年1月,是新潮信息旗下以互联网攻防技术研究为目标的安全团队,团队致力于分享高质量原创文章、开源安全工具、交流安全技术,研究方向覆盖网络攻防、系统安全、Web安全、移动终端、安全开发、物联网/工控安全/AI安全等多个领域。
团队作为“省级等保关键技术实验室”先后与哈工大、齐鲁银行、聊城大学、交通学院等多个高校名企建立联合技术实验室。团队公众号自创建以来,共发布原创文章400余篇,自研平台达到31个,目有18个平台已开源。此外积极参加各类线上、线下CTF比赛并取得了优异的成绩。如有对安全行业感兴趣的小伙伴可以踊跃加入或关注我们
继续阅读
阅读原文