That is exactly what we will try to do.
The rest of this post will use client code that looks like this (full source in our repo).
That is, create a Submission instance, serialize it and send it to the server.
The interesting stuff, which we'll talk about now, is the contents of the makeExploitCollection method.
First, we should note that the server will need to call whatever custom code we put into our exploit Collection.
In this case, a Collection method is called by the server that we can override.
Note that the server calls Submission::toString which looks like this.
The for-each syntax above, for (String entry : values), calls Collection::iterator behind the scenes.
Therefore if we implement Collection::iterator with custom code, the server should run it.
This is simple enough if we extend ArrayList<String> and override that one method.
Note that ArrayList implements Serializable so our extension will as well.
The following code should launch calculator when the server runs it.
However, we hit a snag if we try to send this exploit to the server.
The server prints an exception with a stacktrace.
The name com.
Client$1 was given to the anonymous class we created in our client.
This is the server saying that it can't find the bytecode for com.
Let’s take a look at what we sent to the server.
This is a String rendering of the exploit attempt.
We can see the references to classes we use, along with a little bit of data.
The bytecode for these classes wasn’t sent along though.
Java deserialization uses classloaders to try finding the bytecode for these classes.
In this case, a java.
URLClassLoader instance is searching through jar files on the classpath to find com.
It throws the above exception when it fails to find the class.
This means our exploit won’t work in its current form.
The server needs to be able to access and execute our exploit code for it to work.
This can be accomplished by using classes that the server already has.
In the next section we’ll see how reflection can make the exploit work in this manner.
Polymorphic and Reflective ExploitGoing forward, we will assume that the following dependency is present in the server.
This is a little contrived, but not unreasonable considering that modern web apps are usually library soup.
Additionally, bringing the dependency in will make this section much simpler.
The idea here is to use reflection to implement our exploit Collection in a manner similar to the reflection recap section.
Therefore, the exploit creation method will look something like this.
Here the Collection is reflectively implemented by java.
This should work because Proxy implements Serializable, and it will be on the server's classpath (all of the Java standard library is — usually).
We still need an InvocationHandler implementation though.
Remember that we can’t just make our own implementation of one.
We can only use code on the server for this exploit.
This is where the groovy-all dependency comes in.
It contains two very useful classes: org.
ConvertedClosure and org.
ConvertedClosure implements InvocationHandler, and it facilitates the reflective implementation of a class method with a Closure (like a Java lambda) you construct it with.
MethodClosure provides a Closure implementation for running a system command (like launching calculator).
They both implement Serializable.
Now, our reflective Collection implementation, with a custom Collection::iterator method, can be constructed like this.
Note that we are not creating new code for the server to execute.
We are combining classes it already has.
All the demo code is in our repo.
If you run the demo, the server will launch calculator.
When you run it, there is another exception printed to the server logs even though the exploit works.
An attacker would need a better exploit to avoid the exception printing (if they cared about stealth).
Server Code ImprovementsWe have seen the path to successfully exploit the demo server.
After going through an exercise like this, we can better understand what would have stopped or made the attack more difficult.
We’ll go through some possible changes here and briefly describe how they could benefit the demo server.
Validate User InputThe big sin committed in the server code was not validating user input.
Generally, this isn’t something that you want to do yourself.
Using a library or framework will give much better results as there will be edge cases that you don’t think of.
However, in this scenario a couple things that might help are:Only accept one specific collection implementation.
Ensure the Collection implementation and Submission are declared final in their class definitions, so they can not be extended.
Don’t use generics in the definition of concrete classes that will be serialized.
We didn’t see why in this exercise, but you can probably figure it out after reading about Java type erasure.
This list is not exhaustive by any means.
These suggestions focus on preventing an attacker from supplying a class of their own design.
Input validation is an extremely important measure to take in general though.
Proper input validation can safe guard against other common attacks (e.
Avoid Java SerializationThis ties into validating user input.
Java Serialization is a really powerful serialization technique with many features.
It is often overkill, and a more restrictive serialization method (e.
JSON) would usually work just as well.
Using and validating against a more restrictive serialization standard gives an attacker less wiggle room.
In the demo, a JSON document containing an array would allow us to accept a collection of Strings in a much safer manner.
Additionally, it looks like this will be required sooner or later, as Java maintainers want to remove Java serialization.
Better Manage DependenciesIn the demo, we used classes from groovy-all to craft our exploit.
This was an unnecessary dependency for our server, which means it should be removed.
Removing unnecessary dependencies gives an attacker less to work with.
You can even remove parts of the Java standard library, starting in Java 9, by creating a custom Java runtime.
If a dependency is needed, then it should be kept up to date.
Generally, the latest bug fix release will do, as long as the major version being used is still supported.
This also applies to the groovy-all dependency.
Newer versions contain safeguards so ConvertedClosure and MethodClosure can't be abused like in the demo.
You can read about the groovy changes here.
Use Minimal PermissionsIf you run the demo and look at a process tree listing, then it will look something like this.
Calculator is launched by the server, and it is running as the same user the server runs as.
In this case, it is my personal account, so an attacker could do as much damage as I personally can.
If the server were running as root, the attacker could do more.
If the server had its own dedicated account, then the attacker would be able to do much less.
Originally published at github.
com/photos/vasenka/.. More details