AMF arbitrary code execution

Jun 14, 2011

This vulnerability is a design flaw in how Adobe LiveCycle, Adobe BlazeDS and GraniteDS handle the AMF protocol, that can be exploited resulting in arbitrary code execution.

The basis of how the deserialization of AMF data works, is that the data contains a class name, names of properties, and values for those properties. The application then creates an instance of that class by calling its constructor (without arguments), and using the Java beans conventions for getters and setters to set the properties in that newly created object.

That basic idea is already the security vulnerability. It does not put any limitation on which classes a client can cause to be instantiated (besides that they need a constructor without arguments), and which getters and setters on it may be called. It can be classes that have nothing to do with BlazeDS/GraniteDS or the services being exposed, but just happen to be available on the same server. For most classes this is safe. Constructors, getters, and setters usually don't have extra side-effects outside of the objects they're being called on. But there is no rule anywhere saying that that must always be the case; and in many applications there are classes available where calling the right setters leads to running arbitrary code.

Affected

  • Adobe LiveCycle Data Services 3.1, 2.6.1, 2.5.1 and earlier versions
  • Adobe LiveCycle 9.0.0.2, 8.2.1.3, 8.0.1.3 and earlier versions
  • Adobe BlazeDS 4.0.1 and earlier versions
  • GraniteDS Data Services 2.2.0 and earlier versions

Solution

Note that for BlazeDS upgrading to the new version is not enough. You also need to configure the DeserializationValidator (as explained in blz401_hf_21287.txt included in the hotfix), otherwise you are still vulnerable.
For BlazeDS 3.x, Adobe will not release a fix, but the changes have been backported, so you can grab BlazeDS 3 nightly build 3.3.0.20931.

Theoretical example exploit

Suppose in some application or library, there is a class with the following code:

public class Foo {
   public void setBar(String bar) throws IOException {
      Runtime.getRuntime().exec(bar);
   }
}

That class itself is not a security problem. In any Java application not using BlazeDS, this is perfectly safe code. But if it is available in the classpath of BlazeDS, it can be abused to execute any command, by sending (the binary AMF representation of) an object of this class with a "bar" attribute.

That's a forced example. In the real world, such obvious exploits usually aren't available. It just takes a bit more effort...

JFrame exploit

This exploit does not result in arbitrary code execution, but it's a cute little example, in the Java library itself, just to show you a real-world case.

JFrame is the base class for windows shown on the screen in Swing. Sending an object with class "javax.swing.JFrame" and setting "visible" to "true" shows an actual window on the server running BlazeDS. As a bonus, the "defaultCloseOperation" could be set to 3 (EXIT_ON_CLOSE) so that if the window would be closed, the BlazeDS server exits. Some other stuff could be put in this window, but what can be shown and the behaviour the attacker can choose is quite limited (because of only being able to use getters and setters).

Sample Flex exploit code:

[RemoteClass(alias="javax.swing.JFrame")]
public class JFrame {
   public var title:String = "Gotcha!";
   public var defaultCloseOperation:int = 3;
   public var visible:Boolean = true;
}

Arbitrary code execution

There exists an exploit that will work in many BlazeDS/GraniteDS applications, that leads to arbitrary code execution. But I'm not making that one public yet.

[Update 2016-06-11: Triggered by renewed interest, making the exploit public]
To be quick I'll just copy-paste exactly what I sent to adobe/graniteds in 2011. I didn't test how much of this still works today. Since the fix relies on configuration, it would not surprise me if there are still a bunch of (up-to-date but not with the right config) installations out there.
Code available here: blazeds-graniteds.tgz

Summary

Before diving into the details, a summary: You can send a message that tells the BlazeDS server it has to serve/run files controlled by the attacker:

  • Send a single http request that does two things:
    • Changes the base dir to an attacker specified path by abusing "Embedded".
    • Automatically makes the server run the web application in that base dir. It does that by abusing StandardPipeline and AccessLogValve to create an empty context.xml file, which automatically causes the server to redeploy the application. Redeploying here means undeploying the old (victim's) application and deploying the one on the new base dir instead.
  • The attacker has to serve the web application he wants the server to run on that path, e.g. with a webdav share.

Part 1: setting the base dir

This exploit results in arbitrary command execution, and can be used on any application using Catalina. This is a nice example because, just like the JFrame one, uses a class that is available in many applications using BlazeDS, including the Turnkey example from Adobe. "org.apache.catalina.startup.Embedded" is a class used by Catalina when starting the server. The part we'll abuse is its "setCatalinaBase" method, which will set the "catalina.base" system property, that is the directory the server is running from.

One easy way to abuse this class is to just send it without any properties and making BlazeDS send it back. When BlazeDS sends it back, it will contain the current value of "catalina.base" (and "catalina.home"). This is just a minor security problem, exposing the path where the server is installed. To make it send back an object, the "clientId" property of AbstractMessage (from which RemotingMessage inherits) can be used. That is an identifier that the server always sends back to the client.

To run the exploit, send the attached "getCatalinaBase.http" file as an http request (again, using netcat or any other tool). In the response, you'll again have the "No destination with id 'foo' is registered with any service" error message you can ignore, but notice it also contains the full path of where the server is running. In the example Flex application used to make this amf request, this is the "echo class" button (and the result can be seen using some sniffing tool or httpfox for example).

To further exploit this, we change this catalina.base directory of the server. Use the "setCatalinaBase.http" file, which sets it to "C:\exploit". Or, in the example Flex application use the "set catalina dirs" button.

Now that the path is changed, the server isn't immediately going to start using it. It does use it when redeploying an application. One way to trigger that, is by making a modification. For example in the Turnkey example, edit tomcat/webapps/samples/WEB-INF/web.xml (add a space, or just changing the last changed date of the file is enough). Wait a while (the polling interval is 10 seconds), and see in the console where the server was started from that it is reloading the /samples context (or an error if that dir doesn't exist in C:\exploit). It is now serving the files in C:\exploit\webapps\samples on http://localhost:8400/samples. Just like the original content there, this can contain jsp files with code in them that the server will execute when viewed (if configured like that in C:\exploit\webapps\samples\WEB-INF\web.xml, e.g. copying the one from the samples).

Here we've set the catalina.base to another directory on the *same* server. To truly exploit this remotely, if the server is running on Windows, this can be a path to a share like "\\attacker\myshare". And using the WebDAV mini-redirector (as seen in MS10-045), the attacker doesn't have to be on the same local network.

Part 2: creating files

Using two other classes still from Catalina, you can let it open any file for writing. It creates the file (and any parent directories if needed) or makes it empty if it already exists. It doesn't allow you to actually write anything in the file though.
This in itself is already quite dangerous: it can be disruptive, and with the right file (some ".htaccess" file or equivalent) it might be useable to elevate privileges somewhere...

Combined with the basedir exploit above, it avoids the need to wait for the application to be redeployed: by overwriting a file that Catalina watches (WEB-INF/web.xml or META-INF/context.xml seem to work) it triggers the needed redeploying of the web application.

Details:

  • org.apache.catalina.core.StandardPipeline.setBasic calls .start() on its argument if it implements the "Lifecycle" and "Valve" interfaces.
  • org.apache.catalina.valves.AccessLogValve implements both, and its start() method opens a file for writing, of which the directory and filename can be chosen. The "directory" attribute can be absolute, or relative to our catalina.base directory. Setting the "rotatable" property to false disables the adding of the timestamp in the filename. The "prefix" and "suffix" properties are combined to make the filename.

The order is a bit counter-intuitive, but the easiest way to exploit is to first write the file to trigger the redeploy using a relative (to the base dir) path (this file won't be noticed immediately anyways), and then set the new catalina.base dir. Both can be done together with just one http request.

In the attachment see the "writeAndSetBase.http" file that contains a dump of a http request that writes to webapps/ROOT/META-INF/context.xml and sets the base dir to C:\exploit.
(Tip: If during your testing you want to try the exploit a second time, you'll have to delete that empty context.xml file again to be able to restart the server.)

[Edit 2011-06-17: added note about the fix for BlazeDS]