执行外部 groovy 脚本,System.exit()...敏感方法、死循环、超时循环...解决方案

import groovy.lang.GroovyClassLoader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GroovyClassLocaderConfig {

	@Bean
	public GroovyClassLoader groovyClassLoader( ) {
		GroovyClassLoader classLoader = new GroovyClassLoader();
		return classLoader;
	}
}
import java.security.Permission;

public class GroovySecurityManager extends SecurityManager{
    @Override
    public void checkExit(int status) {
        throw new SecurityException();
    }

    @Override
    public void checkPermission(Permission permission) {
        // 允许其他行为
    }
}

import GroovyClassCacheUtils;
import groovy.lang.GroovyObject;

public class GroovyExector{

    private static final String EXPRESS_SAFE_CHECK_PREVFIX = "import xxx.GroovySecurityManager;\n" +
                                                             "import groovy.transform.TimedInterrupt;\n" +
                                                             "import java.util.concurrent.TimeUnit;\n" +
                                                             "@TimedInterrupt(unit = TimeUnit.MILLISECONDS, value = 10000L)\n" +
                                                             "@groovy.transform.ThreadInterrupt\n" +
                                                             "def safeMethod( serviceBeans ){\n" +
                                                                 // 运行其他『危险』指令之前执行
                                                             "    GroovySecurityManager secManager = new GroovySecurityManager();\n" +
                                                             "    System.setSecurityManager(secManager);\n" +
                                                             "    bootMethod(serviceBeans );\n" +
                                                             "}\n";
    public static Object execute( String express ) {
        try {
            String express_safe = EXPRESS_SAFE_CHECK_PREVFIX + express;
            Class scriptClass = GroovyClassCacheUtils.getClassByGroovyScript( express_safe );
            GroovyObject groovyInstance = (GroovyObject) scriptClass.newInstance();
            try {
                Object testResult = groovyInstance.invokeMethod("safeMethod","xxx");
                if( testResult == null ){
                    throw new BusinessException( "groovy 脚本 bootMethod 方法缺少返回值" );
                }
                return testResult;
            }catch ( SecurityException e ){
                throw new BusinessException( "检测到恶意用户代码..." );
            }
        }catch ( Exception e ){
            throw new BusinessException( e.getMessage() );
        }
    }
}
public class Test(){
     public static void main(String[] args) {
        String express = "def bootMethod(  ){\n" +
                        "   int i = 0;\n" +
                        "   while( true ){\n" +
                        "       System.out.println( i++ );\n" +
                        "       Thread.sleep( 100L );\n" +
                        "   }\n" +
                        "   System.exit( 0 );\n" +
                        "}";

        GroovyExector.execute( express );
    }
}
import groovy.lang.GroovyClassLoader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class GroovyClassCacheUtils {

    private static GroovyClassLoader groovyClassLoader;

    @Autowired
    public void setGroovyClassLoader( GroovyClassLoader groovyClassLoader ) {
        GroovyClassCacheUtils.groovyClassLoader = groovyClassLoader;
    }

    /**
     * 缓存 md5 和 groovy 脚本解析生成的 java class 的 map
     */
    private static final Map<String,Class> GROOVY_CLASS_MAP = new HashMap<>();

    /**
     * 该方法可能在多线程环境下使用,所以要加锁
     * @param groovyScript
     */
    public static Class getClassByGroovyScript( String groovyScript ){
        String md5 = MD5Utils.generateStringMD5(groovyScript);
        Class scriptClass = GroovyClassCacheUtils.GROOVY_CLASS_MAP.get( md5 );
        if( scriptClass == null ){
            synchronized(GroovyClassCacheUtils.class){
                if( scriptClass == null ){
                    scriptClass = GroovyClassCacheUtils.groovyClassLoader.parseClass( groovyScript );
                    GroovyClassCacheUtils.GROOVY_CLASS_MAP.put( md5,scriptClass );
                }
            }
        }
        return scriptClass;
    }
}

ps:这里有一个坑,就是当 class GroovySecurityManager 定义在 groovy 脚本里面的话,使用 @TimedInterrupt(unit = TimeUnit.MILLISECONDS, value = 10000L) 和 @groovy.transform.ThreadInterrupt 注解,会发生 Stack Overflow

猜你喜欢

转载自blog.csdn.net/heshiyuan1406146854/article/details/128938867