
CVE-2020-2551 POC para usar en Internet
POC de prueba (se puede usar en redes externas)
python CVE-2020-2551.py [HOST] [IP]
Intento de modificar el codebase para cargar clases remotas (fallido)
python CVE-2020-TEST.py [HOST] [IP]
(Publicado originalmente en Anquanke, enlace original)
Para estudiar esta vulnerabilidad se necesitan algunos conocimientos previos, como CORBA y RMI.
Breve resumen:
CORBA es un estándar técnico definido por OMG para aplicaciones distribuidas, que utiliza IDL para soporte multilenguaje y se comunica entre cliente y servidor mediante el protocolo IIOP.
RMI es otra tecnología para aplicaciones distribuidas; en Java se puede simplificar su uso con JNDI, y la comunicación cliente-servidor utiliza el protocolo JRMP. Sin embargo, en weblogic, RMI usa el protocolo T3, sobre el cual ya se han reportado varias vulnerabilidades.
RMI-IIOP combina las ventajas de RMI y CORBA, desplegando aplicaciones RMI sobre el protocolo IIOP.
La documentación oficial también menciona:
Los objetos servidor RMI pueden usar el protocolo IIOP y comunicarse con objetos cliente CORBA escritos en cualquier lenguaje.
Dejando de lado weblogic por ahora, primero veamos cómo escribir una instancia de RMI-IIOP:
El código cliente se puede referir al proyecto de prueba en el artículo Java: RMI, JNDI, LDAP, JRMP, JMX, JMS (Parte 1). Se puede compilar HelloClient y HelloServer manualmente, o usar los ya compilados en el proyecto de prueba.
Iniciar el servidor de nombres desde la línea de comandos (incluido con Java):
start orbd -ORBInitialPort 1050
Iniciar el servidor HelloServer desde la línea de comandos con depuración remota. Para saber cómo hacer depuración remota con IDEA, consulta el método mencionado al inicio de este artículo.
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 HelloServer
Por supuesto, también se puede ejecutar directamente sin depuración remota y ver el resultado:
java HelloServer
Iniciar el cliente desde la línea de comandos:
Java HelloClient
En ese momento aparecerá la calculadora. Si la depuración remota funciona correctamente, se puede ver la siguiente pila de llamadas:
Ejecución del comando en EvilMessage.readObject()

Nota al margen: artículos sobre la instalación y depuración de weblogic.
¿Y qué hay del RMI-IIOP en weblogic? En el artículo Acerca de RMI-IIOP en Java se menciona la explotación de RMI-IIOP en weblogic. Sobre esa base se realizaron algunas investigaciones. Using WebLogic’s RMI over IIOP explica varias formas de usar clientes RMI-IIOP en weblogic, incluyendo:
La diferencia entre los dos primeros métodos parece ser solo la configuración de JNDI_FACTORY.

Anteriormente, al estudiar la deserialización T3 en weblogic, desplegamos una aplicación HelloServer en weblogic que tenía un método sayhello() explotable. Se intentó llamarlo con dos configuraciones de JNDI_FACTORY, y con la segunda se logró invocar exitosamente sayHello().
Entonces modificamos el POC del protocolo T3 de weblogic, básicamente cambiando RMI por IIOP, y se constató que la cadena de explotación jtaTransactionManager se ejecutó correctamente, enviando una solicitud JRMP al jrmplisten local.

Observando el tráfico, al invocar el método remove(), se envía una solicitud remove__java_lang_Object con datos maliciosos en el tráfico, pero no se encontró el magic header "aced".

Se supone que en el servidor se realiza un análisis especial antes de deserializar los datos. Observando la pila de llamadas, se ve que la segunda mitad de la cadena de ejecución es muy similar a la del RMI-IIOP nativo; la diferencia es que la primera parte se dispara desde CDRInputStream.read_value(), mientras que aquí se dispara desde weblogic.IIOPInputStream.read_value(). (Este punto read_value también fue mencionado en la charla de 2019).

Aquí la solicitud primero es manejada por clusterableServerRef.invoke(), que según el invocador llama a this.invoker.invoke(). Luego se invoca a Mejb_dj5nps_HomeImpl_WLSkel.invoke(), y como es "remove", entra en el caso 6 y llama a IIOPInputStream.readObject(). En el método read_value() se analizan los datos de IIOPInputStream y se dispara la deserialización. Este es el POC que explota el método remove().
El análisis del maestro Lucifaer en su [artículo](https://lucifaer.com/2020/02/25/WebLogic WLS核心组件RCE分析(CVE-2020-2551)/?from=timeline&isappinstalled=0#2-2-Weblogic解析流程) menciona el uso del método bind() para la explotación, que también es la forma principal utilizada en Internet. Sigamos la pila de llamadas.

Al igual que antes, la solicitud primero es manejada por clusterableServerRef.invoke(), que según el invocador llama a this.invoker.invoke(). Aquí se invoca a CobraServerRef.invoke(), y luego en _NamingContextAnyImplBase._invoke(), como va1 es "bind_any", entra en el caso 0 y llama a IIOPInputStream.read_any(). Más adelante también se llama a IIOPInputStream.read_value() para disparar la deserialización. Antes se dijo que no se veía el magic header "aced" en el tráfico, porque en IIOPInputStream hay un sistema de análisis propio. La representación hex-value de IIOPInputStream es la siguiente, que incluye el nombre de la clase e información de los campos:

Finalmente se invoca al método readObject() de la clase maliciosa.

Observando el parche, se encontró que está en el mismo lugar que el parche de 2015 para la explotación de deserialización T3.

En las pruebas del Análisis de WebLogic CVE-2020-2551, se vio que la clase filtrada por CVE-2020-2551 también está en la clase weblogic.iiop.Utils.

Sin embargo, en las pruebas locales con weblogic 10.3.6 con el parche de 2015, no se activó la función isBlacklisted() (aunque en MsgAbbrevInputStream e InboundMsgAbbrev sí se llama a isBlacklisted() para verificar la lista negra, curioso...).

El parche de CVE-2020-2551 agregó el método verifyclassermitted() en weblogic.iiop.Utils.LoadClass() para filtrar.

La lista negra filtra las clases maliciosas, incluyendo la clase padre de JtaTransactionManager: com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager, que viene con weblogic y es muy peligrosa. Mientras revisaba el parche, se me ocurrió una idea: como la validación en la línea 606 se realiza después de LoadClass(), si al cargar className se carga la clase y se ejecuta un bloque estático malicioso, ¿no se podría eludir la defensa? Esto lo veremos más adelante.
El POC escrito en Java tiene problemas de red; funciona si se ataca el servicio weblogic local, pero no contra contenedores Docker o máquinas externas. Artículos que analizan este problema:
Guía paso a paso para resolver el problema de red del POC para WebLogic CVE-2020-2551
Discusión sobre WebLogic-CVE-2020-2551
A continuación, depuramos el POC. Se puede consultar el anterior de remove, o el de Y4er. Los dos artículos anteriores mencionan dos métodos de solución:
Probé ambos. Al reempaquetar weblogic, se produjo el error java.lang.NoSuchMethodError: weblogic.security.subject.SubjectManager.installCESubjectManager, pero no encontré solución.
Entonces intenté simular el protocolo IIOP. Primero, puse un punto de interrupción en el POC para depurar.

Se descubrió que al hacer new InitialContext(env), se invoca EndPointImpl.sendReceive() que envía y recibe dos paquetes.

LocateReply contiene información IOR. Es necesario entender qué es IOR. Su función es proporcionar, cuando el cliente RMI-IIOP interactúa con un objeto servidor mediante el protocolo IIOP, el host y puerto necesarios para la comunicación IIOP, y la parte del recuadro rojo Object_key se usa para distinguir diferentes objetos en el servidor.

Al simular el protocolo IIOP, hay que prestar especial atención a Object_key. El host y la IP realmente no afectan. Al principio de las pruebas, simplemente reenvié todos los paquetes, pero al enviar resolve_any, se devolvía location forward.

La documentación oficial de GIOP explica que location forward indica que Object_key puede cambiar; el Object_key devuelto en diferentes solicitudes puede ser diferente (aquí Object_key se refiere a la dirección clave en el paquete de datos). Como se mencionó antes, este valor se usa en el protocolo IIOP para distinguir con qué objeto comunicarse, y debe obtenerse dinámicamente en LocateReply.

Finalmente, no se eligió simular remove(), sino el método bind() para emitir la solicitud IIOP, porque tiene menos solicitudes. Veamos el paquete de datos de una explotación local normal.

Se envía LocateRequest, se reciben datos, y mediante una expresión regular se obtiene la dirección clave de LocateReply.

Se configura manualmente la dirección del servidor JRMP malicioso (rmi://...) y se envía el paquete bind_any con un intervalo de 1 segundo. Como la cadena de explotación aquí no usa DGCClient para la solicitud JRMP, no se ve afectada por JEP290 y se puede explotar a través de jrmplisten.

El POC se probó con éxito en un entorno Docker. Se puede usar el entorno vulhub SSRF, configurando la IP del host local, y Docker obtiene con éxito la solicitud JRMP del host. El código específico está en Github.

Anteriormente se mencionó la idea de usar el codebase para cargar código remoto y eludir la detección. Quien haya estudiado ataques JNDI sabrá que el codebase se puede usar para especificar la ubicación de clases remotas. Si el codebase es controlable y el programa permite cargar clases de forma remota, se puede cargar una clase maliciosa remota y ejecutar código malicioso en el bloque estático.
Leyendo el código: weblogic.iiop.Utils.loadClass() tiene un segundo parámetro que representa el codebase. Este parámetro se lee en IIOPInputStream.read_value(), que es el parámetro var8. En la línea 1659 se llama a readIndirectingRepositoryId(var8), que finalmente llama a weblogic.iiop.Utils.loadClass(). Para ejecutar las líneas 1644 y 1659, se necesita (va4r & 1)=1 y (va4 & 6)=2, por lo tanto var4 debe ser 3.
La pila de llamadas desde readIndirectingRepositoryId hasta getClassFromId finalmente ejecuta loadClass() en la línea 304.

Observando el paquete bind_any, en realidad se compone de un GIOP Header y un GIOP Request. GIOP Request incluye key address (igual que en LocateReply), ServiceContextList y stub_data. El valor de var4 es \x7f\xff\xff\x02 en stub_data, por lo tanto (va4r & 1)=0 y (va4 & 6)=2, no se ejecuta la línea 1644 para establecer el codebase.

Modificamos el primer recuadro \x7f\xff\xff\x02 por \x00\x00\x00\x03, y en el segundo recuadro agregamos la longitud y el valor del codebase. El código específico está en Github. Se puede observar también una operación de alineación, que es un punto problemático: antes de leer la información de la clase siguiente, se verifica si la posición del siguiente byte es múltiplo de 4. Si no lo es, se ignoran algunos bits. Por ejemplo, si la siguiente posición es 1, se ignoran 3 bytes y se empieza a leer desde la posición 4. Esta posición es relativa a todo el paquete bind_any. Si el byte no es múltiplo de 4, se rellena con ceros.

Otro problema: observando la pila de llamadas desde readIndirectingRepositoryId hasta getClassFromId, pasa por la función findClassInfo(). Aquí, si ya se ha cargado una clase, la información del ID de la clase se guarda en caché, y findClassInfo() devuelve directamente la información de la clase sin entrar en la función weblogic.iiop.Utils.getClassFromID().

Por lo tanto, al hacer la prueba, cada vez se debe cambiar el nombre de la clase.

De todos modos, al final se logró simular con éxito el protocolo IIOP, modificar el valor del codebase y ejecutar la función weblogic.iiop.Utils.getClassFromId().
Desafortunadamente, al obtener RMIURLClassFinder se devolvió NULL, y RMIEnvironment.getEnvironment().isNetworkClassLoadingEnabled() devolvió false.

La razón es que el parámetro _NetworkClassLoadingEnable en ServerMBeanImpl tiene valor False.

Se intentó buscar en qué archivo de configuración de weblogic se establece este parámetro, pero no se encontró.
En el proceso de estudiar esta vulnerabilidad, se descubrió que se necesitan muchos conocimientos previos, como deserialización de Java, RMI, JNDI, etc. Para aprendizaje relacionado, se puede consultar esta columna de artículos. Aunque al final el intento de explotación mediante la modificación del codebase falló, se aprendió mucho.