java - 如何在 Groovy 脚本中通过 @Grab 禁止依赖关系

标签 java security groovy scripting

我希望允许用户在我的 Java 服务器应用程序中运行他们的 Groovy 脚本,但我也希望禁止他们使用 @Grab 添加任何随机依赖项。

是的,我可以通过在源代码中搜索和替换来简单地切断所有@Grab注释,但最好以更优雅的方式做到这一点,例如仅允许批准的依赖项。

是的,我知道这个问题的最佳解决方案是 JVM 的 SecurityManager

最佳答案

有多种方法,例如 Groovy Sandbox ,这可能比您将要看到的效果更好。

import groovy.grape.Grape

Grape.metaClass.static.grab = {String endorsed ->
    throw new SecurityException("Oh no you didn't! Grabbing is forbidden.")
}

Grape.metaClass.static.grab = {Map dependency ->
    throw new SecurityException("Oh no you didn't! Grabbing is forbidden.")
}

Grape.metaClass.static.grab = {Map args, Map dependency ->
    throw new SecurityException("Oh no you didn't! Grabbing is forbidden.")
}

def source1 = '''
println('This is a nice safe Groovy script.')
'''

def source2 = '''
@Grab('commons-validator:commons-validator:1.4.1')

import org.apache.commons.validator.routines.EmailValidator

def emailValidator = EmailValidator.getInstance();

assert emailValidator.isValid('<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="66110e0712480748150e070b0326131548030a0305120f09081548010910" rel="noreferrer noopener nofollow">[email protected]</a>')
assert !emailValidator.isValid('an_invalid_emai_address')

println 'You should not see this message!'
'''

def script
def shell = new GroovyShell()

try {
    script = shell.parse(source1)
    script.run()
} catch (Exception e) { 
    assert false, "Oh, oh. That wasn't supposed to happen :("
}    

try {
    script = shell.parse(source2)
    assert false, "Oh, oh. That wasn't supposed to happen :("
} catch (ExceptionInInitializerError e) { 
    println 'Naughty script was blocked when parsed.'
}  

上面的示例演示了如何阻止@Grab。它不是通过阻止注释来实现此目的,而是通过覆盖注释添加的方法调用:groovy.grape.Grape.grab()。

Grape.metaClass.static.grab = {String endorsed ->
    throw new SecurityException("Oh no you didn't! Grabbing is forbidden.")
}

Grape.metaClass.static.grab = {Map dependency ->
    throw new SecurityException("Oh no you didn't! Grabbing is forbidden.")
}

Grape.metaClass.static.grab = {Map args, Map dependency ->
    throw new SecurityException("Oh no you didn't! Grabbing is forbidden.")
}

这是由 Groovy Console AST 查看器剖析的顽皮脚本:

@groovy.lang.Grab(module = 'commons-validator', group = 'commons-validator', version = '1.4.1')
import org.apache.commons.validator.routines.EmailValidator as EmailValidator

public class script1440223706571 extends groovy.lang.Script { 

    private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo 
    public static transient boolean __$stMC 

    public script1440223706571() {
    }

    public script1440223706571(groovy.lang.Binding context) {
        super(context)
    }

    public static void main(java.lang.String[] args) {
        org.codehaus.groovy.runtime.InvokerHelper.runScript(script1440223706571, args)
    }

    public java.lang.Object run() {
        java.lang.Object emailValidator = org.apache.commons.validator.routines.EmailValidator.getInstance()
        assert emailValidator.isValid('<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f6819e9782d897d8859e979b93b68385d8939a9395829f999885d8919980" rel="noreferrer noopener nofollow">[email protected]</a>') : null
        assert !(emailValidator.isValid('an_invalid_emai_address')) : null
        return null
    }

    static { 
        groovy.grape.Grape.grab([:], ['group': 'commons-validator', 'module': 'commons-validator', 'version': '1.4.1'])
    }

    protected groovy.lang.MetaClass $getStaticMetaClass() {
    }

}

在这里您可以看到静态初始值设定项中对 Grape.grab() 的调用。要添加对依赖项的细粒度过滤,您可以内省(introspection)依赖项和认可的参数。

依赖

['group': 'commons-validator', 'module': 'commons-validator', 'version': '1.4.1']

认可

commons-validator:commons-validator:1.4.1

修改实现

这个新的实现使用拦截器来阻止/允许 Grape 抓取。

import groovy.grape.GrapeIvy

def source1 = '''
println('This is a nice safe Groovy script.')
'''

def source2 = '''
@Grab('commons-validator:commons-validator:1.4.1')

import org.apache.commons.validator.routines.EmailValidator

def emailValidator = EmailValidator.getInstance();

assert emailValidator.isValid('<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="55223d34217b347b263d3438301520267b30393036213c3a3b267b323a23" rel="noreferrer noopener nofollow">[email protected]</a>')
assert !emailValidator.isValid('an_invalid_emai_address')

println 'You should not see this message!'
'''

def script
def shell = new GroovyShell()
def proxy = ProxyMetaClass.getInstance(GrapeIvy)

proxy.interceptor = new GrapeInterceptor({group, module, version ->
    if(group == 'commons-validator' && module == 'commons-validator') false
    else true
})

proxy.use {
    shell.parse(source1).run()

    try {
        shell.parse(source2).run()
    } catch (org.codehaus.groovy.control.MultipleCompilationErrorsException e) {
        assert e.message.contains('unable to resolve class')
    }
}

@groovy.transform.TupleConstructor
class GrapeInterceptor implements Interceptor {
    private boolean invokeMethod = true
    Closure authorizer

    def afterInvoke(Object object, String methodName, Object[] arguments, Object result) {
        invokeMethod = true

        return result
    }

    def beforeInvoke(Object object, String methodName, Object[] arguments) {
        if(methodName == 'createGrabRecord') {
            def dependencies = arguments[0]
            invokeMethod = authorizer(dependencies.group, dependencies.module, dependencies.version)
        } else {
            invokeMethod = true
        }

        return null
    }

    boolean doInvoke() { invokeMethod }
}

GrapeInterceptor 构造函数将 Closure 作为其唯一参数。通过这个闭包,您可以轻松决定是否允许 Grab 发生:)

例如,如果 Grab 如下所示:@Grab('commons-validator:commons-validator:1.4.1')

闭包的参数将按如下方式分配:

  • 组:commons-validator
  • 模块:commons-validator
  • 版本:1.4.1

要允许抓取,闭包必须返回 true。返回 false 以阻止它。

关于java - 如何在 Groovy 脚本中通过 @Grab 禁止依赖关系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32149515/

相关文章:

java - supercsv 与 jdk 1.7 的兼容性

php - 为什么md5仍然被广泛使用

java - 使用内置的 Android/Java 加密 API 安全吗?

java - 在 groovy 中仅使用另一个类的私有(private)构造函数实例化一个类

Scientific Linux 上的 Java 安装程序显示了两个不同的版本

java - 在 java 中运行 mp3 和 .aac vlc 音频

java - Groovy 生成的类名

spring-boot - 将@Timed用于继承函数

java - java中位移位时溢出

python - 安全删除内存中的密码 (Python)