java反序列化之Groovy1链条分析

前言

之前分析了cc链条,这次分析一下Groovy1链条

简介

Groovy 是 Apache 旗下的一门基于 JVM 平台的动态/敏捷编程语言,在语言的设计上它吸纳了 Python、Ruby 和 Smalltalk 语言的优秀特性,语法非常简练和优美,开发效率也非常高(编程语言的开发效率和性能是相互矛盾的,越高级的编程语言性能越差,因为意味着更多底层的封装,不过开发效率会更高,需结合使用场景做取舍)。并且,Groovy 可以与 Java 语言无缝对接,在写 Groovy 的时候如果忘记了语法可以直接按Java的语法继续写,也可以在 Java 中调用 Groovy 脚本,都可以很好的工作,这有效的降低了 Java 开发者学习 Groovy 的成

环境

<dependency>
  <groupId>org.codehaus.groovy</groupId>
  <artifactId>groovy</artifactId>
  <version>2.4.16</version>
</dependency>

jdk版本在1.8.0_121(我的本机)环境中可以触发,推测我这个版本之前的都能触发,换到jdk1.7u80也可以触发的。

DemoPoc

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

import java.lang.reflect.Array;
import java.lang.reflect.Proxy;
import java.util.Map;

public class GroovyTest {
    static String command = "calc";

    public static void main(String[] args) {
        MethodClosure methodClosure = new MethodClosure(command,"execute");
        final ConvertedClosure closure = new ConvertedClosure(methodClosure,"entrySet");

        Class<?>[] allinterfaces = (Class<?>[]) Array.newInstance(Class.class,1);

        allinterfaces[0] = Map.class;

        Object o = Proxy.newProxyInstance(GroovyTest.class.getClassLoader(),allinterfaces,closure);

        //类名.class.cast() 强制转换
        final Map map = Map.class.cast(o);

        map.entrySet();

    }
}

我们来一一分析本地poc的代码。代码中我们看到并没有调用exec方法,只是Map集合获取了entrySet对象就触发了命令执行。
这里想要说一下entry对象,entry对象是Map中的一个接口,返回 有映射关系的set集合。
在这里插入图片描述
在这里插入图片描述

分析

接下来我们一步步分析。

MethodClosure methodClosure = new MethodClosure(command,"execute");

首先我们实例一个MethodClosure对象,然后将我们执行的命令和execute字符串传入。

在这里插入图片描述
在这里插入图片描述
跟进源码可以看到:传入的command交给父类处理,被赋成Closure.owner,Closure.delegate。而execute字符串被赋为MethodClosure.method。

final ConvertedClosure closure = new ConvertedClosure(methodClosure,"entrySet");

接下来又实例化了另一个对象,并将刚才的实例化对象,和entrySet字符串传入,为什么要传entrySet字符串呢,而不是其他的字符串?这里我们稍后再说,刚开始分析的时候我也在可以有些磕绊,好在最后找到原因,明白了为啥要传入这个字符串。
跟进实例化类的构造方法,我们可以看到字符串已经被赋为ConvertedClosure.methodName,methodClosure对象被赋到父类的delegate对象,也就是ConversionHandler.delegate。
在这里插入图片描述

Class<?>[] allinterfaces = (Class<?>[]) Array.newInstance(Class.class,1);
allinterfaces[0] = Map.class;

接下来就是生产一个class类的Array数组,因为Proxy.newProxyInstance方法第二个参数是动态代理类要实现的接口要以数组的形式传入。所以我们生成了一个Class数组并在其中存入我要实现的接口也就是Map.calss

Object o = Proxy.newProxyInstance(GroovyTest.class.getClassLoader(),allinterfaces,closure);

接下来就是生成动态代理对象的过程,第一个参数是类加载器,第二个参数是代理类,第三个参数是实现代理接口具体的行为(实现了InvocationHandler接口的类的对象)。
里面只有一个Map.class所以生成的代理对象是实现了Map接口里所有方法的,然后强制转换成map数组,掉用entrySet方法即可触发。
动态代理的一大特点:不论你调用代理对象的哪一个方法,其实执行的都是我们创建代理对象时所传入的InvocationHandler对象中我们所重写的Invoke方法。
而在这里我们从传入的是ConvertedClosure对象,所以动态代理时,会去调用ConvertedClosure的invoke方法,我们 跟进一下ConvertedClosure的invoke方法。
在这里插入图片描述
我们并没有看到ConvertedClosure的invoke方法,但是我们发现ConvertedClosure的父类ConversionHandler中存在invoke方法,跟进:
在这里插入图片描述
到68行时,invokeCustom方法是个抽象方法是个抽象方法,所以要找实现这个抽象方法的子类。
在这里插入图片描述
ConvertedClosure类中的截图我们可以看到实现了invokeCustom类。
在这里插入图片描述
这里我们要分析一下,并且解决上面留下的问题。
首先看一下this.methodNam就是ConvertedClosure.methodName,也就是我们传入的字符串entrySet,entryset和ethod.getName()不相同就返回null,否则就执行后面((Closure)this.getDelegate()).call(args)这一串。
我们调试一下看看变量。
在这里插入图片描述
method传入的内容就是我们map.entrySet()的时候传入的。所以会执行后面的。

((Closure)this.getDelegate()).call(args)

这里我们来分析一下。
getdelegate(),就是前面我们传入的methodClosure对象,然后强制转换为Closure类型,调用Closure.call方法。
跟进Closure.call方法:
在这里插入图片描述
我们可以看到this.getMetaClass()调用了父类的getMetaClass()方法,也就是调用了GroovyObjectSupport.getMetaClass()方法,返回的是一个MetaClassImpl对象
在这里插入图片描述

既然返回了MetaClassImpl对象,也就是调用了MetaClassImpl.ivoke()方法。
在这里插入图片描述

这是MetaClassImpl.ivoke()方法的前半部分,由于代码比较长,我们先分析前半段。这里传入的是object为Closure对象中的属性。
912行判断是否存在对象,如果存在进入循环。
931行判断是否是否为isClosure对象,如果是,进入循环,显然进入循环。
932行强制转换成closure对象。
933行获取Owner属性,这个属性是我们前面实例化对象的时候传入如的参数,并且赋给根深的Owner属性,这里获取到了他,值为calc。
935行或运算,因为doCall是代码中硬编码传入的,所以回走到循环里面。
936行获取class类。
937行判断class类是否为MethodClosure类,如果之前一步一步跟进的化,显然这里为true,回进入循环。
939行获取getMethod,这里Method,也就是我们实例化对象时,传入的execute。
最终回执行到 return ownerMetaClass.invokeMethod()方法,我们可以从上图变量值中看到,ownerMetaClass其实还是一个MetaClassImpl对象也就是说这里其实是一个递归调用,然后再去判断912行的是否为对象。
然后再到912行,判断object这个参数时,就我们刚才在943行传入的owner的值,为clac,所以就不会走入循环,就会走到1098行。
在这里插入图片描述
method显然不为空,然后我们要执行的命令被传入了ProcessGroovyMethods.execute((String)var1);

在这里插入图片描述
在这里就执行了我们传入的command命令,到这里就弹出了计算机。
在这里插入图片描述
好了,这是我们本地的demo(ysoserial的payload中的Groovy的gadget)。还有要分析的就是:结合反序列化漏洞中的反序列化配和Groovy1的gadget来远程代码执行的。
代码我已经分析完了,也是找到一个类来触发我们构造好的map数组,本地代码需要再稍微调试一下,这个坑,过两天再补上。

参考:
https://xz.aliyun.com/t/10703
https://paper.seebug.org/1171
https://su18.org/post/ysoserial-su18-3/
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Groovy 中,你可以使用 `JsonSlurper` 类来反序列化 JSON 数据,包括数组。下面是一个示例,用于从 JSON 数据中反序列化一个字符串数组: ``` import groovy.json.JsonSlurper def json = '["apple", "banana", "cherry"]' def slurper = new JsonSlurper() def myArray = slurper.parseText(json) assert myArray instanceof List assert myArray.size() == 3 assert myArray[0] == "apple" assert myArray[1] == "banana" assert myArray[2] == "cherry" ``` 在上面的示例中,我们首先定义了一个包含字符串数组的 JSON 字符串。然后,我们创建了一个 `JsonSlurper` 对象,并使用其 `parseText` 方法将 JSON 字符串解析为 Groovy 中的对象。最后,我们检查了 `myArray` 对象是否是一个 `List` 类型的对象,并检查了数组中的元素是否正确。 如果 JSON 数据中包含的是对象数组,那么你可以将其反序列化为一个包含多个对象的列表,如下所示: ``` import groovy.json.JsonSlurper def json = '[{"name": "apple", "price": 1.0}, {"name": "banana", "price": 2.0}, {"name": "cherry", "price": 3.0}]' def slurper = new JsonSlurper() def myList = slurper.parseText(json) assert myList instanceof List assert myList.size() == 3 assert myList[0].name == "apple" assert myList[0].price == 1.0 assert myList[1].name == "banana" assert myList[1].price == 2.0 assert myList[2].name == "cherry" assert myList[2].price == 3.0 ``` 在上面的示例中,我们定义了一个包含对象数组的 JSON 字符串。我们使用 `JsonSlurper` 类的 `parseText` 方法将其反序列化为包含三个对象的列表。我们检查了 `myList` 对象是否是一个 `List` 类型的对象,并检查了每个对象的属性是否正确。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值