Lucene search

K
seebugMy SeebugSSV:97346
HistoryJun 19, 2018 - 12:00 a.m.

ColdFusion RCE(CVE-2018-4939)

2018-06-1900:00:00
My Seebug
www.seebug.org
200

0.972 High

EPSS

Percentile

99.8%

In October 2017 I published an overview and video proof-of-concept of a Java RMI/deserialization vulnerability affecting the Flex Integration service of Adobe ColdFusion. I held off on publishing all of the details and exploit code at the time because I spotted an additional exploit payload that could be used against a patched server.

A further security update has now been released by Adobe, so read on for more details.

RMI and java.lang.Object

The Java remote method invocation (RMI) protocol is almost 100% Java serialization. When an object is requested from an RMI registry service and methods are invoked on that object, the data transmitted over the network is in the Java serialization format. ColdFusion’s Flex integration RMI service exposes an object of the following class:

coldfusion.flex.rmi.DataServicesCFProxy

This class can be found in the file “libs/cfusion.jar” in the ColdFusion installation directory, and looks like the following:

Java

package coldfusion.flex.rmi;

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;
import java.util.Map;

public abstract interface DataServicesCFProxy extends Remote
{
  public abstract List fill(String paramString, Object[] paramArrayOfObject, Map paramMap) throws RemoteException;
  public abstract List sync(String paramString, List paramList, Map paramMap) throws RemoteException;
  public abstract Object get(String paramString, Map paramMap1, Map paramMap2) throws RemoteException;
  public abstract Integer count(String paramString, Object[] paramArrayOfObject, Map paramMap) throws RemoteException;
  public abstract boolean fillContains(String paramString, Object[] paramArrayOfObject, Object paramObject, Boolean paramBoolean, Map paramMap) throws RemoteException;
}

Each of these methods can be called with any Java object as a parameter. Note that containers such as List and Map can contain any Java object. No authentication is required to interact with this RMI service, so anyone who can reach the service over a network can supply arbitrary Java objects to it in an attempt to exploit a Java deserialization attack (e.g. by supplying ysoserial payloads as method parameters).

Unfortunately none of the ysoserial payloads worked against this entry point.

In my previous post about ColdFusion CVE-2017-11283 and CVE-2017-11284 I talked about how I modified a ysoserial payload to successfully exploit this entry point and gain remote command execution using the Mozilla Rhino JavaScript library. In this case, the technique and entry point remain the same, but we’re targeting the ROME library which comes bundled with ColdFusion (see “libs/rome-cf.jar”).

Exploitation – The Easy Way

The following is a simple RMI client program that retrieves a ColdFusion DataServicesCFProxy object from an RMI registry service, then calls the remote count() method with null parameters:

package nb.barmie.demo;

import coldfusion.flex.rmi.DataServicesCFProxy;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class CFRMIDemo {
  public static void main(String[] args) throws Exception {
    Registry reg = LocateRegistry.getRegistry(args[0], Integer.parseInt(args[1]));
    DataServicesCFProxy obj = (DataServicesCFProxy)reg.lookup("cfassembler/default");
    obj.count(null, null, null);
  }
}

The second parameter to the count() method is an array of java.lang.Object meaning we can supply arbitrary objects in that parameter and they will be deserialized on the server. We can use ysoserial at runtime to generate an arbitrary payload object and pass that to the count() method, however the ysoserial ROME payload is not compatible with the version of ROME that’s bundled with ColdFusion. If we try this, the server will complain that the server-side class is incompatible with the serialized object that was sent over the network. This is due to the serialVersionUID fields not matching, similar to how I described with the previous exploit that targeted Mozilla Rhino.

The easiest way to exploit the ColdFusion RMI service, prior to the April 2018 update, is to rebuild ysoserial. Instead of building ysoserial against rome 1.0 (Maven dependency), build it against “libs/rome-cf.jar” from the ColdFusion installation directory. Having done so, the following code can be used to generate and deliver payloads:

Java

package nb.barmie.exploit.standalone;

import coldfusion.flex.rmi.DataServicesCFProxy;
import ysoserial.payloads.ROME;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class CFRMIExploit {
  public static void main(String[] args) throws Exception {
    Registry reg = LocateRegistry.getRegistry(args[0], Integer.parseInt(args[1]));
    DataServicesCFProxy obj = (DataServicesCFProxy)reg.lookup("cfassembler/default");
    obj.count(null, new Object[] {
      new ROME().getObject(args[2])
    }, null);
  }
}

Run the exploit with the parameters: host, port, command.

Exploitation – The BaRMIe Way!

Having done a lot of work on RMI security, I opted to implement an exploit in my RMI enumeration and attack tool, BaRMIe. It’s a more complex exploit but also more powerful. I’ll release this version of the exploit in the near future, but for now I’ll explain how it works for those who are interested!

RMI Background

A remote method invocation involves two network services and two distinct network connections. The first network service is the RMI registry service, usually found on TCP port 1099, which is essentially a directory service where Java object references are bound to names. Line 4 of the following code connects to an RMI registry service on 10.0.0.30:1099 and requests a reference to the object that is bound to the name “Foo”:

Java

public class RMIList {
  public static void main(String[] args) throws Exception {
    Registry reg = LocateRegistry.getRegistry("10.0.0.30", 1099);
    SomeClass obj = (SomeClass)reg.lookup("Foo");
  }
}

The second network service is used to communicate with the object itself. When the object is bound to a given name in the RMI registry, the host and port where the object can be found are stored in the registry. When we retrieve an object reference from the RMI Registry, the data returned by the RMI registry service includes the host and port of the object’s network service. Line 5 in the following code connects to the RMI object service and invokes the method someMethod():

Java

public class RMIList {
  public static void main(String[] args) throws Exception {
    Registry reg = LocateRegistry.getRegistry("10.0.0.30", 1099);
    SomeClass obj = (SomeClass)reg.lookup("Foo");
    obj.someMethod("String Param");
  }
}

Manning the Middle

While I was building BaRMIe, I wanted to include as many exploit payloads (POP gadget chains) as possible without having to battle with dependencies and multiple versions of dependencies. The way I achieved this was to have hard-coded static payloads and generate dynamic sections on the fly (e.g. command strings and corresponding length fields). The problem was that there’s no way to natively pump arbitrary bytes down an RMI connection in such a way that the receiving server would deserialize those bytes. To achieve this, I built a proxy framework that allowed me to man-in-the-middle both connections. It works as follows:

  • Start an RMI registry proxy that forwards connections on to the target RMI registry
  • Call LocateRegistry.getRegistry() with the host and port of the RMI registry proxy, not the host and port of the RMI registry service
  • Call Registry.lookup() to retrieve a remote object reference via the RMI registry proxy (which forwards the request to the real RMI registry service)
  • When the RMI registry proxy detects a remote object reference being returned:
    • It starts an RMI method proxy to forward connections on to the real RMI object service
    • It modifies the remote object reference to point at the new RMI method proxy rather than the real RMI object service
  • When a method is invoked on the remote object reference, the connections now go through the RMI method proxy

With remote method invocations going through a proxy, I have complete control over the protocol and I can manipulate things that the Java Virtual Machine would otherwise prevent me from manipulating. A great example of this is that a remote method which expects a parameter of type java.lang.String cannot be supplied with an arbitrary object parameter, however if we modify the outbound remote method invocation at the network level using a proxy then we can supply an arbitrary object and the server will deserialize it.

Using an RMI method proxy, we can issue a call to a remote method in the normal way but with a placeholder parameter instead of a payload object. When the method proxy detects the bytes representing that placeholder object, it can replace those bytes with a stream of bytes representing an arbitrary deserialization payload

Fixing the Payload

When I first spotted the file “libs/rome-cf.jar” in the ColdFusion installation directory, the first thing I did was create an exploit in BaRMIe that used an RMI method proxy to inject two payloads based on the ROME payload from ysoserial. Neither of these were successful, however the server’s response stated that the local class was incompatible and gave me the serialVersionUID of the server-side class. By modifying my payload several times so that the serialVersionUID values in the payload matched those on the server, I achieved remote command execution against ColdFusion again.

Bonus: Attacking “Internal” Services

All this proxying might seem like a lot of effort given the easy exploit I detailed above but actually the supporting functionality was already present in BaRMIe so it only took half an hour to develop this exploit.

There’s an additional bonus to controlling things outside of the restrictions of the Java Virtual Machine that I have touched on before, but which is definitely worth another mention. A lot of “internal” RMI services aren’t actually internal. You can bind objects to an RMI registry and be under the impression that they are bound to, for example, 127.0.0.1 or 10.0.0.30 or even host.yourbusiness.local. If an external attacker retrieves a reference to any of those objects using the easy exploit above then they won’t be able to attack the RMI service because they cannot access those internal addresses.

By default, however, RMI object services bind to all network interfaces. Let’s say the external address of the target is 8.8.8.9 and the RMI registry returns an object reference pointing at the internal address of the target, 10.8.8.9:30001. By default, the same object can be accessed by connecting to the same port on the external address at 8.8.8.9:30001. By proxying the RMI registry connection, we can detect this and automatically modify RMI connections to enable attacks against objects that appear to be bound internally.