7

I'm pretty new to groovy, and scripting in java generally, and I really hope there is a simple solution for my problem. In our application, the users can execute groovy scripts which they write themselves, and we need to control what those scripts can and can not do. I read a lot of stuff about sandboxing groovy, but either I am looking at wrong places or I am overlooking the obvious. To make it simple, I have a small example which demonstrates the problem. This is my class loader which should prevent java.lang.System from being loaded and available to scripts:

public class MyClassLoader extends ClassLoader {

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (name.startsWith("java.lang.System")) {
            throw new ClassNotFoundException("Class not found: " + name);
        }
        return super.loadClass(name);
    }
}

And this is a simple program that tries to call System.currentTimeMillis():

public static void main(String[] args) {
    String code = "java.lang.System.currentTimeMillis();";
    ClassLoader classLoader = new MyClassLoader();
    Thread.currentThread().setContextClassLoader(classLoader);

    GroovyShell shell = new GroovyShell();
    Script script = shell.parse(code);
    Object result = script.run();
    log.debug(result);
}

MyClassLoader throws exceptions for java.lang.SystemBeanInfo and java.lang.SystemCustomizer, but the code executes. Same thing happens if I use javax.script classes:

ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("Groovy");
Object o = engine.eval(code);
log.debug(o);

And if I try it with JavaScript engine, it works as expected (just replace "Groovy" with "JavaScript" in the above example).

Can anyone help me with this? BTW, I'm using groovy-all-1.8.8.jar, with jdk1.7.0_55.

Thanks

2
  • It works same with groovy v.2.3.6 and java 1.8.0_05. java.lang.System isn't even loaded with this classloader. Commented Sep 24, 2014 at 14:45
  • @Opal This classloader tries to load java.lang.SystemBeanInfo and java.lang.SystemCustomizer, which, I assume, groovy uses for wrapping java.lang.System. But it makes sense that another classloader is in use, regardless of MyClassLoadernot having a parent classloader, I just don't know how it works and how to customize that behaviour. Commented Sep 24, 2014 at 15:37

3 Answers 3

3

I can recommend Groovy Sandbox for this purpose. In contrast to SecureASTCustomizer it will check if an execution is allowed dynamically at runtime. It intercepts every method call, object allocations, property/attribute access, array access, and so on - and you thus have a very fine grained control on what you allow (white-listing).

Naturally the configuration on what is allowed is very important. For example you may want to allow using Strings and use methods like substring, but probably not the execute method on String, which could be exploited with something like 'rm -R ~/*'.execute(). Creating a configuration that is really safe is a challenge, and it is more difficult the more you allow.

Downside of the Groovy Sandbox is that the code must run with the interceptor registered and you will have a performance penalty during execution.

This image [1] shows an example from a project where we used Groovy Sandbox for Groovy code entered by the user. The code is run to valide the script - so if the statement there would actually be executed as part of it, the application would have exited before I could do the screenshot ;)

Example from a project where we used Groovy Sandbox

Sign up to request clarification or add additional context in comments.

5 Comments

I read about Groovy Sandbox but never tried it, I'll check it out. However, as I said in a different comment, we already have a custom classloader which works for other script engines we support, so preferred solution would be if we can reuse it also for groovy.
Maybe the configuration of your custom class loader, which classes it allows / forbids, could be reused within a Groovy Sandbox configuration.Groovy may need special treatment anyway as it extends the JDK classes with additional functionality that may pose security issues on classes that are safe to be allowed in Java (String.execute is just one example)
I agree, I was thinking along the same lines, already modifying the code. I have one more experiment with class loader, and if it fails I'll accept this answer.
I am trying to configure Groovy Sandbox per our needs, it's going ok but I have a question: how to prevent certain methods from being called (like String.execute in your example)? So far, couldn't find a way to do it, only to block the whole class.
Use a custom GroovyInterceptor to have detailed control on method calls, object instantiations, etc. See here for an example (how we configured it).
2

Perhaps you'd be interested in using a SecureASTCustomizer in conjunction with a CompilerConfiguration. If you are concerned with security, an explicit white list might be better than a black list.

def s = new SecureASTCustomizer()
s.importsWhiteList = [ 'a.legal.Klass', 'other.legal.Klass' ]

def c = new CompilerConfiguration()   
c.addCompilationCustomizers(s)

def sh = new GroovyShell(c)

Take a look at that class, it contains a lot of options that are ready to use.

3 Comments

This definitely helps, but it doesn't cover all my needs. For example, I can prevent java.lang.System from being used like in the above example by using importsBlacklist and c.setIndirectImportCheckEnabled(true), but this would still work: Class.forName("java.lang.System").getMethod("currentTimeMillis", null).invoke(null). Another thing, we already have our custom classloader which is used (and works) for other script engines we support (javascript and python), so I would prefer if it would be possible to achieve this by using custom classloader.
Here is a clue, if I execute this: def cl = this.class.classLoader; def threadCl = Thread.currentThread().contextClassLoader; I will get groovy.lang.GroovyClassLoader and my.package.MyClassLoader, respectively. So I guess GroovyClassLoader is loading the System class, I just don't know if and how I can influence that.
@PredragMaric Hey, I see your point. Unfortunately I don't know much about Groovy's Classloaders (yet)..
0
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyShell;
import groovy.lang.Script;

public class SandboxGroovyClassLoader extends ClassLoader {

public SandboxGroovyClassLoader(ClassLoader parent) {
    super(parent);
}

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException    {
    if (name.startsWith("java.lang.System"))
        return null;
    return super.loadClass(name);
}

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    if (name.startsWith("java.lang.System"))
        return null;
    return super.loadClass(name, resolve);
}

static void runWithGroovyClassLoader() throws Exception {
    System.out.println("Begin runWithGroovyClassLoader");

    String code = "def hello_world() { java.lang.System.currentTimeMillis(); };";

    GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
    Class<?> scriptClass = groovyClassLoader.parseClass(code);
    Object scriptInstance = scriptClass.newInstance();
    Object result = scriptClass.getDeclaredMethod("hello_world", new Class[] {}).invoke(scriptInstance, new Object[] {});
    System.out.println(result);
    groovyClassLoader.close();
    System.out.println("End runWithGroovyClassLoader");
}

static void runWithSandboxGroovyClassLoader() throws Exception {
    System.out.println("Begin runWithSandboxGroovyClassLoader");
    ClassLoader parentClassLoader = SandboxGroovyClassLoader.class.getClassLoader();
    SandboxGroovyClassLoader classLoader = new SandboxGroovyClassLoader(parentClassLoader);

    String code = "def hello_world() { java.lang.System.currentTimeMillis(); };";

    GroovyClassLoader groovyClassLoader = new GroovyClassLoader(classLoader);
    Class<?> scriptClass = groovyClassLoader.parseClass(code);
    Object scriptInstance = scriptClass.newInstance();
    Object result = scriptClass.getDeclaredMethod("hello_world", new Class[] {}).invoke(scriptInstance, new Object[] {});
    System.out.println(result);
    groovyClassLoader.close();
    System.out.println("End runWithSandboxGroovyClassLoader");
}

static void runWithSandboxGroovyShellClassLoader() throws Exception {
    System.out.println("Begin runWithSandboxGroovyShellClassLoader");

    String code = "java.lang.System.currentTimeMillis();";

    ClassLoader parentClassLoader = SandboxGroovyClassLoader.class.getClassLoader();
    SandboxGroovyClassLoader classLoader = new SandboxGroovyClassLoader(parentClassLoader);
    Thread.currentThread().setContextClassLoader(classLoader);

    GroovyShell shell = new GroovyShell();
    Script script = shell.parse(code);
    Object result = script.run();
    System.out.println(result);
    System.out.println("End runWithSandboxGroovyShellClassLoader");
}

public static void main(String[] args) throws Exception {

    runWithGroovyClassLoader();
    runWithSandboxGroovyClassLoader();
    runWithSandboxGroovyShellClassLoader();

}
}

Is it what you want ?

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.