mozilla / rhino

Rhino is an open-source implementation of JavaScript written entirely in Java
https://rhino.github.io
Other
4.17k stars 846 forks source link

RCE gadget for Java deserialization #520

Open aphtrinh opened 5 years ago

aphtrinh commented 5 years ago

There exists in Rhino a so-called 'gadget' for the java deserialization vulnerability that could be exploited to gain RCE on a vulnerable application. A similar issue could be found in the 3.2.2 update of the Commons Collections library (https://commons.apache.org/proper/commons-collections/release_3_2_2.html).

Reproduction:

If you consider this a security issue, let me know and I can give further details as to how the object is constructed and provide the stacktrace to get to RCE.

gbrail commented 5 years ago

Thank you for bringing this to our attention.

All Rhino objects are serializable and have been since forever. There have been some fixes submitted recently to fix serialization bugs, so at least someone somewhere is relying on this functionality.

Short of eliminating serializability entirely (which is certainly an option that we could add to Rhino) it'd be great to understand what mitigating steps we can take to prevent this, so any other information is appreciated.

On Thu, Jan 31, 2019 at 11:06 PM An Trinh notifications@github.com wrote:

There exists in Rhino a so-called 'gadget' for the java deserialization vulnerability that could be exploited to gain RCE on a vulnerable application. A similar issue could be found in the 3.2.2 update of the Commons Collections library ( https://commons.apache.org/proper/commons-collections/release_3_2_2.html).

Reproduction:

  • Get the demo application rhino_gadget.zip https://github.com/mozilla/rhino/files/2820415/rhino_gadget.zip
  • mvn install
  • Run the payload generator: java -cp rhino_gadget-1.0-SNAPSHOT-fatjar.jar Payload. By default this generates a payload with the command 'gnome-calculator' and write the object to /tmp/ser.bin.
  • Run the vulnerable app which deserializes /tmp/ser.bin: java -cp rhino_gadget-1.0-SNAPSHOT-fatjar.jar VulnerableApp. It would then execute 'gnome-calculator'.

If you consider this a security issue, let me know and I can give further details as to how the object is constructed and provide the stacktrace to get to RCE.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/mozilla/rhino/issues/520, or mute the thread https://github.com/notifications/unsubscribe-auth/AAf0a1of14xf_8vIbDgFZLfRF_Bgtn4Fks5vI-eRgaJpZM4adxWc .

aphtrinh commented 5 years ago

From what I see, mitigations seem a little complicated.

Here's the call trace of the gadget. It goes through two paths, first is a helper to instantiate the Context and second is the main one to invoke arbitrary method.

NativeJavaObject.readObject()
  JavaAdapter.readAdapterObject()
    ObjectInputStream.readObject()
      ...
        NativeJavaObject.readObject()
          JavaAdapter.readAdapterObject()
            JavaAdapter.getAdapterClass()
              JavaAdapter.getObjectFunctionNames()
                ScriptableObject.getProperty()
                    ScriptableObject.get()
                      ScriptableObject.getImpl()
                        Method.invoke() - *limited
                          Context.enter()
    JavaAdapter.getAdapterClass()
      JavaAdapter.getObjectFunctionNames()
        ScriptableObject.getProperty()
          NativeJavaArray.get()
            NativeJavaObject.get()
              JavaMembers.get()
                Method.invoke()
                  TemplatesImpl.getOutputProperties()
                    ...

The main idea is NativeJavaObject on deserialization will store all the members of an object class. The call to NativeJavaObject.get(String x,...) will search the member with the name 'x' and invoke it if it's a getter. TemplatesImpl.getOutputProperties(), a native jre getter call which can lead to the construction of class from malicious bytecodes, is used here. So we need to control 'x' in NativeJavaObject.get(String x,...), and JavaAdapter.getObjectFunctionNames() does that.

One more thing, a researcher has published another gadget different from this for quite some time (dated 2016, still works with the latest version I think). This gadget and his have one thing in common, they need to call Context.enter() before anything else, and apparently the only way to do that early in the deserialization chain is through this call. So one way to break both gadgets is to make Context.enter() a little harder to call, e.g. by forcing to provide an arg, making it non-static...