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.

Download

Sourcecode and binaries are available from Sourceforge. The only dependencies besides transloader are Apache's commons IO, commons lang (version 2.x) and commons collections (version 3.x) as well as Objenesis, which you can obtain from google code and javassist. 

Version: 0.3
Released on 2013-06-19.
Download binary and sources from Sourceforge.
Download reflective-sandcastle 0.1, hardened-reflective-sandcastle 0.1
Go to the documentation, API documentation, sandcastle docuementation

Version: 0.2
Released on 2013-06-12.
Download binary and sources from Sourceforge.
Go to the documentation

Version: 0.1
Released on 2013-05-31.
Download binary and sources from Sourceforge.
Go to the documentation

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

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:

${''.getClass().forName('java.lang.System').getMethod('exit', ''.getClass().forName('java.lang.Integer').getField('TYPE').get(null)).invoke(null, ''.hashCode())}

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 Birt

Eclipse 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

String pw = service.restrict(context);
try{
    /* put untrusted code here */
} finally {
    service.releaseRestriction(pw);
}

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.

SandboxedEnvironment<Object> c = new SandboxedEnvironment<Object>() {
   @Override
   public Object execute() {
      /* untrusted code */
      return null;
   }
};

service.runSandboxed(c.getClass(), context);

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.

33 comments:

  1. Could you please release this to maven central? Also, there are lots and lots of compile warnings.
    Would also be nice if you could remove the dependency on groovy for those of us not using it. :D

    You can always release a separate jar with groovy support in it.

    ReplyDelete
  2. SandboxContext#addClasspath() incorrectly uses ":" for the path separator. On Windows this is a semicolon. You should use File.pathSeparator instead which is platform independent.

    ReplyDelete
  3. 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).

    My fixed version looks like:

    private final Path prefix;
    private boolean negate = false;

    public FilePrefixPermission(String prefix) {
    this(prefix, false);
    }

    public FilePrefixPermission(String prefix, boolean negate) {
    this(Paths.get(prefix), negate);
    }

    public FilePrefixPermission(Path prefix, boolean negate) {
    this.prefix = prefix.toAbsolutePath();
    this.negate = negate;
    }

    @Override
    public boolean testPermission(String file) {
    final String sanitized_file;
    if (file.endsWith("-") || file.endsWith("*"))
    sanitized_file = file.substring(0, file.length() - 1);
    else
    sanitized_file = file;

    final Path p = new File(sanitized_file).getAbsoluteFile().toPath();
    return p.startsWith(prefix) ^ negate;
    }

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Should probably be the following instead since it's possible to have a hyphen at the end of a folder name:

      if (file.endsWith("/-") || file.endsWith("\\-") || file.endsWith("/*") || file.endsWith("\\*"))

      I'm not sure what to do in the case where a real folder name is "-"...

      Delete
  4. FileEqualsPermission suffers from the same problem. Fixed version:

    private final Path mask;
    private boolean negate = false;

    public FileEqualsPermission(String mask) {
    this(mask,false);
    }

    public FileEqualsPermission(String mask, boolean negate) {
    this(Paths.get(mask), negate);
    }

    public FileEqualsPermission(Path mask, boolean negate) {
    this.mask = mask.toAbsolutePath();
    this.negate = negate;
    }

    @Override
    public boolean testPermission(String file) {
    final String sanitized_file;
    if (file.endsWith("-") || file.endsWith("*"))
    sanitized_file = file.substring(0, file.length() - 1);
    else
    sanitized_file = file;

    final Path p = new File(sanitized_file).getAbsoluteFile().toPath();
    return mask.equals(p) ^ negate;
    }

    ReplyDelete
  5. Hi David,

    thanks for the input. I am not in this week but i will have a look at the issues next week.

    Arno

    ReplyDelete
  6. Happy to help. :D

    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

    ReplyDelete
  7. For the record -- here's the list of dependencies that I needed in SBT format (easy enough to translate to a pom for maven):

    libraryDependencies += "commons-io" % "commons-io" % "2.4"

    libraryDependencies += "commons-lang" % "commons-lang" % "2.6"

    libraryDependencies += "commons-collections" % "commons-collections" % "3.2.1"

    libraryDependencies += "commons-configuration" % "commons-configuration" % "1.9"

    libraryDependencies += "org.objenesis" % "objenesis" % "2.0"

    libraryDependencies += "org.javassist" % "javassist" % "3.18.0-GA"

    libraryDependencies += "com.google.inject" % "guice" % "3.0"

    libraryDependencies += "com.google.code.findbugs" % "jsr305" % "1.3.+"

    ReplyDelete
  8. Sorry for all the comments/questions...

    Any chance you could move this over to github? I could fork and then issue pull requests making it easier for you to review and merge my changes.

    ReplyDelete
  9. 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:

    startTime = System.currentTimeMillis();

    to:

    startTime = TimeUnit.MILLISECONDS.convert(System.nanoTime(), TimeUnit.NANOSECONDS);

    Subsequently, SandboxMonitorDaemon#testRuntime() should be changed from:

    if(System.currentTimeMillis() - startTime > TimeUnit.MILLISECONDS.convert(context.getMaximumRunTime(), context.getMaximumRunTimeUnit()))

    to:

    if(TimeUnit.MILLISECONDS.convert(System.nanoTime(), TimeUnit.NANOSECONDS) - monitor.getStartTime() > TimeUnit.MILLISECONDS.convert(context.getMaximumRunTime(), context.getMaximumRunTimeUnit()))

    ReplyDelete
  10. Added an option to handle uncaught exceptions for threads created when running in a sandbox. Please see https://github.com/scommon/scommon/commit/b359c286534622e89416033d99727b2e1d4b2467 for details.

    ReplyDelete
  11. Added basic socket permissions:

    https://github.com/scommon/scommon/commit/7f6f1f804f6d6ebe4e35d86740c4f658c7c1b6ae

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

    ReplyDelete
  13. is it working to run java programs??in php

    ReplyDelete
    Replies
    1. i mean in php this sandbox work or not??

      Delete
    2. I am sorry. I don't quite get the question. Could you elaborate a bit?

      Delete
    3. how can i use this sandbox in php??

      Delete
    4. you cannot. it has nothing to do with php.

      Delete
    5. i want to develope a "try it" editor for java in my php web site so can i use this sandbox for it or not


      my question is explained at below link
      http://stackoverflow.com/questions/19175255/how-to-develope-try-it-out-for-java-program-in-wordpress-site

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

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

    ReplyDelete
    Replies
    1. There is a difference between compiling it and using it. For compiling additional resources are needed. If you only want to use the sandbox, then the five dependencies mentioned are sufficient.

      Delete
    2. Thank you for your fast reply.
      What do you mean by "use it" then?

      Delete
    3. By use it, I mean putting the precompiled jar into your classpath and start making sandboxes as described in the documentation. what are you trying to do?

      Delete
  15. I wanted to play around with this, see the strengths and flaws of a project like this.

    ReplyDelete
  16. net.datenwerke.sandbox.permissions.FilePermission cannot be instantiated - clone() method is the cause.

    ReplyDelete
    Replies
    1. FilePermission is an Interface. Implementations are, for example, FileSuffix-, FilePrefix-, and FileRegexPermission.

      Delete
    2. Yes, I was using an anonymous inner class. Found the problem doe so never mind. Thx.

      Delete
  17. Project looks very interesting. Did the fixes suggested David Hoyt already make it into the code? Does anyone already use this in a production system?

    ReplyDelete
    Replies
    1. Hi Rolf, regrettably I have not yet found the time to integrate the suggestions.

      Delete
    2. Hi Arno - Are you still considering improving this project?

      Delete
    3. I don't think I will find the time to work on this any time soon .. sorry

      Delete