首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Mozilla Rhino 反序列化漏洞 POC 分析

Mozilla Rhino是一个使用 Java 语言编写的开源 JavaScript 引擎。ysoserial 中收录了 Rhino 的反序列化 Gadget,我们来一起分析下这个 Gadget。

零、NativeError 的继承关系

首先来看类的继承关系。它继承自,后者继承自。而实现了接口和接口。因此,可以进行序列化和反序列化操作。

一、分析

1、首先,反序列化攻击的入口在的函数。

中调用了函数,传入参数为的 this 对象。看下。

调用了两次函数,传入的参数是对象和字符串 name/message,继续跟进。

中调用的是父类的函数,入参没有变化。跟进去看看。

其中调用的是接口的函数。这个的实现在类。

最后调用的是父类的函数,再次回到类。

继续跟进函数。

其中的关键在于 2007 行到 2026 行的这部分。先看 2009 行到 2020 行的第一个分支。

这个分支中有的调用,看上去有戏。但有一个问题在于,是变量,在反序列化过程中无法赋值。

这会导致 2013 行的判断恒真,就被赋值为最初的对象。这就导致 2020 行的无法调用我们期望的目标对象的函数,只能调用静态函数或者类的内置函数。这当然不是我们期望的结果。

再来看 2021 行到 2026 行的分支。

这个分支中需要将设置为对象,并最终调用的函数。先看看如何赋值。

通过的。是的内部类,支持序列化。

可以通过来进行赋值。

那么要赋值成的哪个对象呢?是个接口,看下它的实现类。

我们选择类。这个类继承自,后者同样继承自,因此同样可以进行序列化和反序列化处理。

函数挺长,翻一翻会发现在 247 行调用了。

这个的调用,其实是函数,其中直接调用了我们熟悉的函数。

看起来很有希望。为了能成功调用到我们期望的目标函数,我们需要关注中里的三个变量:、和。

一个一个来,先看。

2、的值来自类的成员变量,通过查找到索引。

成员变量是类的对象数组,本身可以通过反序列化赋值。

至于的内容要设置成什么样,来看下函数。其中来自函数,而后者是直接返回了变量。

是个 transient 变量,要怎么赋值呢?

答案就在中。这里先通过得到了 member 对象,再通过函数将赋值给。

继续跟进函数,就是一个反序列化的实现。因此,通过反序列化给的赋值,不存在问题。

也就是说,我们可以通过反序列化给赋值为期望的目标函数。

结论

设置中的需要:

构造对象 m

设置 m 的成员变量为目标函数

构造对象 n

设置 n 的成员变量的 0 号元素为 m

3、javaObject 涉及的代码,都在的 222~247 行。

关键的部分就是 225~242 行的分支里。

如果要把赋值为我们期望的对象,就是要在 235 行完成这个赋值。但是这里有一个问题:我们知道就是对象,同理也是。但没有实现接口,这样一来 234 行的判断条件就不能满足了。

转机在于,这个判断身处循环之中,240 行的给了我们希望。查看一下的实现类。

看下的函数,直接返回了成员变量。

而成员变量可以通过反序列化的函数直接赋值。

也就是说,如果我们让对象的返回特定的对象,就可以完成的赋值。看看的实现,在类中。

这个来自 ScriptableObject 的成员变量,可以通过反序列化赋值。

结论

设置中的需要:

构造对象 o

设置 o 的成员变量为目标对象

构造对象 e

设置 e 的成员变量为 o

4、最后看一下。来自入参,其实就是调用者传入的。

这就决定我们要寻找的目标函数,必须是一个无参函数

5、再回到开头,通常反序列化的入口都是函数,而文章开头说的反序列化入口在函数。怎么才能从入口转到呢?

答案就在 JDK 中的类的函数。

也就是说,只要将的设置为对象,就可以在反序列化的过程中调用了。

6、结论

如果要完成反序列化POC,需要:

构造对象 m

设置 m 的成员变量为目标函数

构造对象 n

设置 n 的成员变量的 0 号元素为 m

构造对象 o

设置 o 的成员变量为目标对象

构造对象 a

设置 a 的成员变量为 o

通过 a 的函数,设置 a 的属性为对象 n

构造对象 b

设置 b 的成员变量为对象 a

前面说过,需要寻找的目标函数,应当是一个无参函数。同时,这个无参函数所属的目标类,还得是实现了接口、支持序列化和反序列化的类。

因此,首先想到的就是,使用类作为目标类,使用它的作为目标函数。

二、填坑

完成了上述分析,我们开始写POC。途中暗坑无数,逐一填之。

1、无法实例化

声明对象,直接报错:。

报错原因:

类不是,不能直接引用。

解决方案:

通过反射,实例化对象。

2、反射实例化的运行失败

运行这段代码:

报错原因:

没有提供默认的public 无参构造函数,无法直接调用 newInstance()。

解决方案:

通过反射设置构造函数为 public,再进行调用。

反射在 ysoserial 中被大量的使用,原因也就在此。

3、执行POC失败:No Context

按照“分析”部分的结论,结合大量的反射调用,完成POC如下。

编译运行。呃,序列化成功,可是反序列化的时候却没看到计算器,只看到了报错:“No Context associated with current Thread”。

报错原因:

问题在哪里呢?就在的 else 分支中。

我们期望进入 2024 行的,结果在 2023 行抛出了异常,因为 Context 对象为空。

构造需要调用函数。

怎样在反序列化的时候插入的调用呢?

重新看下调用栈,发现调用了两次函数,分别传入字符串 “name”和“message”。

因此,我们可以把作为 “message”的属性,把作为 “name” 的属性,这样就可以先执行,再执行进行 Payload 执行。

解决方案:

按照 POC 中设置的方法,设置为 “name” 属性,将设置为 “message” 属性。

4、执行POC仍然失败:No Context

增加的调用之后,重新运行POC,呃,问题依旧……

报错原因:

为什么新增的调用无效呢?因为设置函数的方法错了。

无论是还是,我们都是通过函数进行设置。而这个函数设置的属性,是类型的。

回到报错的地方看。 2009 行 if 分支的判断条件是,属性的值必须是类型,而并没有实现接口,所以无论进来的是还是,代码流程都会走到 2021 行的 else 分支中。

我们期望流程走到 2024 行的,遇到的问题是在 2023 行就报错了。我们增加的调用,期望他能解决无法通过来调用的问题。

但是对的调用遇到了一样的问题,在 2023 行就抛出了异常,无法走到 2024 行去执行我们期望的函数。

所以,对的设置,就不能像一样,去通过函数进行设置,只能让他通过 2009 行的 if 分支去调用。但是要怎么去设置呢?ysoserial 通过反射进行强制设置属性来解决这个问题。

解决方案:

参考 ysoserial 中的方法,通过反射进行强制设置属性为 MemberBox 对象的方法:

现在再执行 POC,终于可以看到计算器了。

三、POC

完整POC参见Github (https://github.com/yaojieno1/rhinoPoc)。

主要函数:

四、心得

1、有作为反序列化的入口,也成为了之外的另一个反序列化攻击触发点。

2、反射功能,很好很强大。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180224G02J0D00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券