package org.codehaus.groovy.jsr223; import groovy.lang.GroovyClassLoader; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.CodeVisitorSupport; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.stmt.ExpressionStatement; import org.codehaus.groovy.classgen.GeneratorContext; import org.codehaus.groovy.control.CompilationUnit; import org.codehaus.groovy.control.CompilationUnit.PrimaryClassNodeOperation; import org.codehaus.groovy.control.CompilerConfiguration; import org.codehaus.groovy.control.Phases; import org.codehaus.groovy.control.SourceUnit; import org.junit.Test; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import java.lang.reflect.Field; import java.security.CodeSource; import java.util.HashSet; import java.util.Set; /** * Test contributed by Tiago Fernandez, for GROOVY-3946 */ public class JSR223SecurityTest { @Test(expected = ScriptException.class) public void should_forbid_an_instruction_when_overriding_GroovyClassLoader_using_reflection() throws Exception { secureEval("System.exit 2", "java.lang.System", true); } @Test(expected = ScriptException.class) public void should_forbid_an_instruction_when_overriding_GroovyClassLoader_using_injection() throws Exception { secureEval("System.exit 2", "java.lang.System", false); } private void secureEval(final String script, final String forbiddenInstruction, final boolean useReflection) throws Exception { final ScriptEngine groovyEngine = new ScriptEngineManager().getEngineByName("groovy"); final GroovySecurityManager groovySecurityManager = GroovySecurityManager.instance(); groovySecurityManager.overrideGroovyClassLoader(groovyEngine, useReflection); groovySecurityManager.forbid(forbiddenInstruction); groovyEngine.eval(script); } } class GroovySecurityManager { private final static GroovySecurityManager instance = new GroovySecurityManager(); private final Set<String> blacklist = new HashSet<String>(); private GroovySecurityManager() { } public synchronized static GroovySecurityManager instance() { return instance; } public void overrideGroovyClassLoader(final ScriptEngine engine, final boolean useReflection) { try { if (useReflection) overrideDefaultGroovyClassLoaderUsingReflection(engine); else overrideDefaultGroovyClassLoaderUsingInjection(engine); } catch (Throwable ex) { throw new RuntimeException("Could not initialize the security manager", ex); } } public void forbid(final String instruction) { blacklist.add(instruction); } public boolean isForbidden(final String instruction) { for (String forbidden : blacklist) if (instruction.startsWith(forbidden)) return true; return false; } private void overrideDefaultGroovyClassLoaderUsingReflection(final ScriptEngine engine) throws Exception { final Field classLoader = engine.getClass().getDeclaredField("loader"); classLoader.setAccessible(true); classLoader.set(engine, new CustomGroovyClassLoader()); } private void overrideDefaultGroovyClassLoaderUsingInjection(final ScriptEngine engine) throws Exception { GroovyScriptEngineImpl concreteEngine = (GroovyScriptEngineImpl) engine; concreteEngine.setClassLoader(new CustomGroovyClassLoader()); } } class GroovySecurityException extends RuntimeException { public GroovySecurityException(final String message) { super(message); } } class CustomGroovyClassLoader extends GroovyClassLoader { @Override protected CompilationUnit createCompilationUnit(final CompilerConfiguration config, final CodeSource source) { final CompilationUnit unit = super.createCompilationUnit(config, source); unit.addPhaseOperation(new CustomPrimaryClassNodeOperation(), Phases.SEMANTIC_ANALYSIS); return unit; } } class CustomPrimaryClassNodeOperation extends PrimaryClassNodeOperation { @Override public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) { for (Object statement : source.getAST().getStatementBlock().getStatements()) ((ExpressionStatement) statement).visit(new CustomCodeVisitorSupport()); } } class CustomCodeVisitorSupport extends CodeVisitorSupport { private final GroovySecurityManager groovySecurityManager = GroovySecurityManager.instance(); @Override public void visitMethodCallExpression(final MethodCallExpression call) { if (groovySecurityManager.isForbidden(call.getText())) throw new GroovySecurityException("The following code is forbidden in the script: " + call.getText()); } }