One of the things that stands out in the Java Serialization exploit is that once a server side Java application is compromised, the next step is to gain shell access on the host machine. This is known as a Remote Code Execution, or RCE for short.
The interesting thing is that Java has had a way to restrict execution and prevent RCE almost since Java 1.1: the SecurityManager. With the SecurityManager enabled, Java code operates inside a far more secure sandbox that prevents RCE.
This runs with the default security policy in
$JAVA_HOME/jre/lib/security/java.policy, which in JDK 1.8 is:
Take code like this, for example:
With the security manager enabled and using an additional policy file, it's possible to enable or disable execute privileges cleanly:
You can run this with:
Just comment out the FilePermission line, and you get an exception.
So far, so great. But it's only enabled for applets, on the client side. It's disabled on the server-side.
Why? Well, because (as you can see above) the default SecurityManager locks down the system to the point of uselessness. In order to make the system useful, it must have a custom
java.security.policy file defined.
This policy implementation has several problems. The policy file itself is archaic. The security permissions are not laid out in any kind of logical order, and some permissions have options for wildcards while others do not. You can only "allow" behavior with whitelists, not deny it. And worst of all, the longer the list, the slower the application will run. There is a tutorial and list of permissions, but it's not terribly helpful in practice. And the documentation guide was last updated in 2002.
It is possible to write a custom SecurityManager when you have untrusted code: this is what Scalatron does, for example. NOTE: This is NOT a secure implementation of a sandbox according to Ben Murphy.
However, if we want to prevent RCE, then we want a general purpose SecurityManager that allows almost everything, but can prevent scripts being run on the host. It wouldn't be a perfect defense, but it would be a decent part of a defense in depth strategy.
It turns out that someone already did this!
Now, using pro-grade with the previous example, the following policy would lock down all execution access:
Note that this is not a complete solution. I suspect you would need to deny several other permissions in addition to this to prevent code from working around this, and I don't know which ones are relevant for a blacklist. But it's a start, and it goes a long way towards hardening Java server-side applications very cheaply.
EDIT: Have done research, and you do need to set additional permissions to prevent the SecurityManager itself from being circumvented. Please see this post for the relevant permissions.
Pro-grade is very simple to set up with an appropriate policy. There's a policy generator that can show all the needed permissions for an application, and a tutorial showing all the steps needed to set it up, and a permissions debugger to catch stray permissions after that.
The example project is available at prograde-example, and pro-grade can be integrated into your project from http://mvnrepository.com/artifact/net.sourceforge.pro-grade/pro-grade/1.1.1. Most people will want Maven:
The really interesting thing about pro-grade is that it's a transparent solution. While it's nice to have a whitelist policy, using this technique you can add pro-grade onto an existing, already compiled project, and deny script execution.
With only minor modifications, pro-grade could be made to notify of violations (especially in
setSecurityManager and other seldom touched areas), and appear to work while also silently mangling the operation elsewhere. All you need to do is implement PermissionDeniedListener with an SLF4J implementation.
There's enough research in attacks, it's nice to see some progress being made in defense as well.