Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RCE gadget for Java deserialization #520

Open
aphtrinh opened this issue Feb 1, 2019 · 2 comments
Open

RCE gadget for Java deserialization #520

aphtrinh opened this issue Feb 1, 2019 · 2 comments
Labels

Comments

@aphtrinh
Copy link

aphtrinh commented Feb 1, 2019

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
  • 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.

@gbrail
Copy link
Collaborator

gbrail commented Feb 1, 2019 via email

@aphtrinh
Copy link
Author

aphtrinh commented Feb 2, 2019

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...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants