The Java-Sandbox

The java-sandbox allows to securely execute untrusted code, such as third-party or user generated code from within your application. It allows to specify resources and classes that may be used by the code, thus, separating the execution from the application's execution environment.

License

The java-sandbox is open source. The java-sandbox is available under the LGPL 3.0.

State of the Library

We see the library as experimental as we currently not have much experience with it. Thus, the API and functionality might change with future versions. To push this library into the right direction, we are happy for any feedback you might have. You can contact us via email or the ReportServer forum.

Resources

Sandboxing GroovyA tutorial describing how to use the java-sandbox to create secure environments for executing groovy scripts.

Background and Motivation

Why Develop a Sandbox for Java

Why develop a sandbox for java, you may ask. Is there not already a library that does what you want? Regrettably, we found that this is not the case. There are several questions out there asking for something like a sandbox to execute untrusted code but the answer usually is: "this is not trivial, you might want to implement a custom SecurityManager or implement your own ClassLoader and add ProtectionDomains and what not on the class to then use the builtin (and very inflexible) SecurityManager.

We had a very specific use-case in mind. We wanted to execute user generated code with different sets of permissions and furthermore these permission sets should be easily configurable during run-time of the application. For this we found java's builtin SecurityManager (which was introduced to secure applets in browsers) very bulky to work with and not flexible enough. On top, we wanted to specify permissions on a very fine grained level, that is on per class rather than per package. Thus, we developed the java-sandbox library.

If you've tried out the library and have any constructive (positive or negative) feedback please let us know. The easiest to reach us is via the ReportServer forum.

Use cases

Our main motivation was that we wanted to secure ReportServer. ReportServer processes user generated code at several stages. For example, advanced filters can be specified using the unified expression language (specified in JSR-245; we use JUEL as implementation). Such expressions look harmless enough: for example, a user might use ${today.firstDay()} to specify the first day in the current month. However, behind the scenes JUEL evaluates this using lots of reflection and with a little experimenting its easy to access resources you shouldn't have:

The above calls System.exit(). Something you wouldn't want. The java-sandbox allows us to use JUEL and only permit users to access whitelisted objects and functionality.

Groovy

Besides JUEL we wanted to use groovy. This is usually nothing an end user would get access to but at times it is nice to provide users with a simple text field to input some lines of code to, for example, manipulate strings instead of having a bulky GUI which in the end does not implement the functionality users would really like to use. Running groovy, of course, is a huge security problem, as users practically get access to the system having the same permissions as the application. One way out might be to plug into the compilation and syntax tree generation of groovy (or use a custom domain specific language instead of groovy). However, domain specific languages are time consuming to specify build and maintain and to fiddle with groovy's byte code generation may be error-prone. The java-sandbox allows to run groovy scripts in a restricted environment checking policy violations at run-time.

JasperReports

JasperReports Library is a reporting engine for generating print ready reports and is also available in ReportServer. A well known feature (bug?) of the JasperReports library is that of code injection (see, for eaxmple, here). Reports in jasper are defined using a custom XML dialect. From this file a java class (or groovy class, depending on the type of report) is generated to handle certain expressions. Thus, anybody who is allowed to manipulate reports can easily take over the entire system. Note that, to the best of our knowledge, this also applies to JasperSoft's very own business intelligence solution JasperReport Server.

Eclipse BirtEclipse Birt is a reporting engine quite similar to JasperReports library. Birt allows its users to enhance reports by scripts written in JavaScript (and internally executed using Rhino). Rhino makes it easy to access classes from the application scope and thus, again, any report designer can easily take over the entire system.

How the Java Sandbox works in just a few words

The java-sandbox basically consists of two components (an implementation of SecurityManager and a custom ClassLoader) and a service class which allows to access the functionality. A sandbox can be started only using the security manager or using the classloader together with the security manager. We will briefly describe the difference between the two modes. For further information on how the java sandbox works internally have a look at the examples and explanations further down.

Assume service is an instance of the java-sandbox's service class. Then the easiest to create a sandbox is to call

With this setup only the security manager is enabled to supervise the execution of the untrusted code. The security manager is asked before certain system resources can be used or, for example, whether or not the System.exit() may be called (more information on the capabilities of security managers and the permission concept can be found here, and here). The context object of type SandboxContext allows to configure which permissions are given.

One problem with the above setup is that it is hardly possible to restrict access to classes and packages. Although security managers have a concept of being asked whether or not classes from certain packages may be loaded the security manager is asked exactly once by the classloader. Thus, if a package has been accessed outside the sandbox it is also cleared for use within the sandbox (at least in the eyes of the corresponding classloader).

To solve this problem we load the untrusted code with a custom classloader. The java-sandbox provides several easily accessible methods to do this. Usually you will setup a sandbox implementing the SandboxedEnvironment interface (which is similar to the Callable interface). The untrusted code then goes into the execute method.

The runSandboxed methods accept a SandboxedEnvironment class object as well as a SandboxContext object as input. The environment is then loaded by a custom classloader while the context object describes the permissions available for sandboxed code. It then creates a sandbox and calls the environment's execute method. By loading the SandboxedEnvironment object in a custom classloader we have full control over which classes are loaded. The custom classloader also not only informs the security manager on packages but on classes that are to be loaded thus allowing to fine tune the sandbox to the specific needs.

Documentation

The documentation for the latest version can be found here. Documentation of the sandcastle extension can be found here. Go back to the top of the page for documentation for previous versions.

FilePrefixPermission#testPermission() also does not behave correctly. Some files passed to testPermission() on Windows might look like "\C:\foo\file\-" or "\C:\foo\file\*" (see the docs: http://docs.oracle.com/javase/7/docs/api/java/io/FilePermission.html).

How integral is the transloader portion to the sandbox? What I'm really looking for is a stripped down version of what you've provided where I can still add the package, class, file, etc. permissions and run in a sandboxed security manager/class loader, but locally (no remote running required) and with only that functionality and no more.

And to be clear -- thanks so much for your hard work and for releasing this. It has saved me a lot of time! :D

SandboxMonitoredThread's constructor incorrectly uses System.currentTimeMillis(). You should use a monotonically increasing clock (simple use case -- the user moves the system clock ahead an hour b/c they're adjusting their time zone or it's daylight savings time). System.nanoTime() is preferable. In order to keep millisecond precision, I changed:

Hi David, just a quick update. I haven't yet had the chance to look at your suggestions. I am currently on a deadline and don't know when I'll find the time. But I hope I manage to squeeze the sandbox in ...

The java-sandbox would probably not be the way to go forward with such a project. Here you would ideally sandbox the java process using means provided by the operating system in conjunction with a standard java security manager. In any case, this is a very high-risk and highly non-trivial problem and I would strongly recommend not to do such a thing as this will most likely compromise the server your website is running on.

There are more dependencies missing besides the 5 ones you mentioned in the documentation chapter (commons-collections-3.2.1.jar, commons-io-2.4.jar, commons-lang-2.6.jar, objenesis-2.1.jar and javassist.jar). Is this project not made to run standalone? I noticed some references to groovy etc. I still have 100+ errors in it.