漏洞分析 | U8 Cloud ServiceDispatcher反序列化漏洞及补丁分析

0x01 概述

近期,爆出了 U8cloud ServiceDispatcherServlet 接口的反序列化漏洞。在对该漏洞进行分析时,我们发现 NC 也曾出现过 ServiceDispatcherServlet 接口的反序列化漏洞。经过分析后发现,这两个漏洞的功能代码实现方式并不相同。但二者都实现了自定义的序列化过程(本文简称 NetObjectStream),并且对传输的序列化流进行了合法性检测。因此,该漏洞的利用条件需要的自定义序列化类生成序列化数据进行攻击。
在漏洞分析过程中,我们发现并验证了其他接口也使用了自定义的序列化类(NetObjectStream)进行反序列化解析数据,存在反序列化漏洞(已报送给国家漏洞监管单位和厂商进行修复)。
在这里插入图片描述

0x02 补丁分析

官方的修复方式是通过增加序列化对象的限定类来防止反序列化漏洞。具体是在 NetObjectInputStreamNCObjectInputStream 类中增加了新的构造方法,在构造方法中增加了 InvocationInfo.classESBContextForNC.class 两个白名单限制类。

未修复
NetObjectInputStream objIn = new NetObjectInputStream(new ByteArrayInputStream(bytes));

修复后
NetObjectInputStream objIn = new NetObjectInputStream(new ByteArrayInputStream(bytes), new Class[]{InvocationInfo.class, ESBContextForNC.class});

NetObjectInputStreamNCObjectInputStream 类中增加了新的构造方法 [1],将上述的两个限定类传入到私有变量 classes 中用于检测处理。

public NetObjectInputStream(InputStream in, Class[] classes) throws IOException {
    this.bufferSize = 1024;
    ObjectResolver resolver = new NCObjectResolver();
    this.objIn = new NCObjectInputStream(new NCInputStream(in), resolver, classes); // [1]
}

NCObjectInputStream 方法中将 classes 传入了 this.classes 变量中,即将 InvocationInfo.class, ESBContextForNC.class 两个限定类传入到 this.classes [2] 中。

由于传入的 resolver 是个对象,所以会调用 this.enableResolveObject(true); 该方法的作用是当调用 enableResolveObject(true) [3] 时,在反序列化过程中通过调用 resolveObject() 方法来解析特定类型的对象。

public NCObjectInputStream(InputStream in, ObjectResolver resolver, Class[] classes) throws IOException {
    super(in);
    this.check = false;
    this.resolver = resolver;
    this.classes = classes; // [2]
    if (this.resolver != null) {
        this.enableResolveObject(true);  // [3]
    }
}

由于开启了 enableResolveObject(true) ,当序列化对象在调用 readObject() 方法中,默认调用其 resolveObject() 方法。

方法内部通过 isAssignableFrom() 方法 [3] 判断传入的字节流是否为 InvocationInfo.class, ESBContextForNC.class 的实现类或子类,如果不是,则会抛出异常,导致字节流不能够被反序列化,从而避免了反序列化漏洞的产生。

protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
    try {
        Class superImpl = super.resolveClass(desc);
        if(classes!=null&&classes.length>0){
            if(this.check) return superImpl;
            boolean legal = false;
            for(Class c:classes){
                if(c.isAssignableFrom(superImpl)){  // [3]
                    legal = true;  // [4]
                    break;
                }
            }
            if(legal){
                this.check = true;
                return superImpl;
            }else{
                throw new IllegalArgumentException("####### class::" + superImpl);
            }
        }
        return superImpl;
    } catch (ClassNotFoundException nfe) {
      ...
    }
}

0x03 路由分析

U8cloud 在 web.xml 文件中配置 ServiceDispatcher 的 servlet 映射,URL路由为 /ServiceDispatcherServlet,servlet 命名为 CommonServletDispatcher

<servlet-mapping>
  <servlet-name>CommonServletDispatcher</servlet-name>
  <url-pattern>/ServiceDispatcherServlet</url-pattern>
</servlet-mapping>

跟进名为 CommonServletDispatcher 的 servlet ,发现该 servlet 映射到了 nc.bs.framework.comn.serv.CommonServletDispatcher 类中,并在初始化中定义了一个 service 参数 [5] ,该参数映射到了 nc.bs.framework.comn.serv.ServiceDispatcher 类[6]中。

<servlet>
  <servlet-name>CommonServletDispatcher</servlet-name>		
  <servlet-class>nc.bs.framework.comn.serv.CommonServletDispatcher</servlet-class>
  <init-param>
    <param-name>service</param-name>  // [5]
    <param-value>nc.bs.framework.comn.serv.ServiceDispatcher</param-value>  // [6]
  </init-param>
  <load-on-startup>10</load-on-startup>
</servlet>

0x04 漏洞分析

CommonServletDispatcher 类继承了 HttpServlet 接口并实现了 init()destroy()doGet()doPost() 方法。在 init() 方法中通过 web.xml文件定义的 service 参数获取对应的 nc.bs.framework.comn.serv.ServiceDispatcher 类 [7] 并进行实例化 [8]。

public class CommonServletDispatcher extends HttpServlet {
  private ServiceHandler serviceHandler = null;
     public void init() throws ServletException {
        String targetname = null;
        Throwable cause = null;
        this.log.debug("ServletDispatcher.initing......");
        if ((targetname = this.getInitParameter("service")) != null) {  // [7]
            try {
                Class handlerClass = Class.forName(targetname);
                this.serviceHandler = (ServiceHandler)handlerClass.newInstance();// [8]
            } 
          ...
        }
        ...
    }
}

doGet 方法中定义了 label118 的循环体,循环中通过 this.serviceHandler.execCall(request, response); 调用了上文中已经实例化后的 nc.bs.framework.comn.serv.ServiceDispatcher 类的 execCall 方法并传入 requestresponse

this.serviceHandler.execCall(request, response);

跟进 ServiceDispatcher 类的 execCall() 方法,通过调用本类中的静态方法 readObject() [9] 对传入的 request.getInputStream() 序列化流数据进行反序列化操作。

int[] lsizes = new int[1];
long wBeginTime;
try {
    ThreadTracer.getInstance().beginReadFromClient();
    wBeginTime = System.currentTimeMillis();
    invInfo = (InvocationInfo)readObject(request.getInputStream(), streamRet, lsizes);  // [9]
}

跟进 ServiceDispatcher 类的 readObject() 方法,该方法首先使用 BufferedInputStream 读取流数据,通过 NetObjectInputStream 类的 readInt 方法对流数据进行移位运算解析得到流的字节长度,对比 BufferedInputStream 类中读取的长度来判断是否为一个正常的可以被 NetObjectInputStream 解析的反序列化流数据。

如果判断是一个可以被 NetObjectInputStream [10] 读取的流,则读取该流并进行 readObject() [11] 反序列化操作,这时就可以通过构造利用链进行反序列化攻击。

public static Object readObject(InputStream in, boolean[] retValue, int[] lsizes) throws IOException, ClassNotFoundException {
  BufferedInputStream bin = new BufferedInputStream(in);
  int len = NetObjectInputStream.readInt(bin);
  byte[] bytes = new byte[len];
  int readLen = bin.read(bytes);
  int tmpLen;
  for(lsizes[0] = readLen; readLen < len; readLen += tmpLen) {
    tmpLen = bin.read(bytes, readLen, len - readLen);
    if (tmpLen < 0) {
      break;
    }
  }
  if (readLen < len) { // [10]
    throw new EOFException("ReadObject EOF error readLen: " + readLen + " expected: " + len);
  } else {
    NetObjectInputStream objIn = new NetObjectInputStream(new ByteArrayInputStream(bytes));  
    if (retValue != null) {
      retValue[0] = objIn.isCompressed();
      retValue[1] = objIn.isEncrypted();
    }
    return objIn.readObject();   // [11]
  }
}

0x05 漏洞利用

U8 Cloud 中依赖了 commons-collections-3.2.1 ,则可以通过cc链达到反序列化利用的目的。

在利用过 CC 链利用过程中,我们尝试直接通过 CC 链攻击时发现并不能有效地成功利用。经过调试后发现,原因出在该接口的序列化数据中,正如上文提到需要经过 NetObjectInputStream 进行序列化数据检测,而我们则使用的是 ObjectOutputStream 进行生成的序列化数据,导致了序列化数据不合法。

于是我们尝试通过 NetObjectOutputStream 生成序列化序列化数据,这样就通过了 NetObjectInputStream 对序列化流的合法性检测,成功达到了反序列化攻击的目的。

// 序列化对象
public static void serialize(Object obj) throws IOException {
  ByteArrayOutputStream bao = new ByteArrayOutputStream();
  NetObjectOutputStream noo = new NetObjectOutputStream(bao);
  noo.writeObject(obj);
  noo.flush();
  noo.close();
  FileOutputStream fos = new FileOutputStream("./ServiceDispatcherServlet.ser");
  NetObjectOutputStream.writeObject(fos, obj);
}

0x06 总结

本文对 U8Cloud ServiceDispatcher 接口反序列化漏洞原理进行跟踪分析,讲述了该漏洞是如何被利用以及官方的修复方式。同时,在漏洞分析过程中,我们发现并验证了其他接口也使用了自定义的序列化类(NetObjectStream)进行反序列化解析数据,存在反序列化漏洞(已报送给国家漏洞监管单位和厂商进行修复)。

Goby 欢迎表哥/表姐们加入我们的社区大家庭,一起交流技术、生活趣事、奇闻八卦,结交无数白帽好友。

也欢迎投稿到 Goby(Goby 介绍/扫描/口令爆破/漏洞利用/插件开发/ PoC 编写/ IP 库使用场景/ Webshell /漏洞分析 等文章均可),审核通过后可奖励 Goby 红队版,快来加入微信群体验吧~~~

文章来自Goby社区成员:Gryffinbit@白帽汇安全研究院,转载请注明出处。
微信群:公众号发暗号“加群”,参与积分商城、抽奖等众多有趣的活动
获取版本:https://gobysec.net/sale

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WebLogic和Fastjson反序列化漏洞是两个独立的漏洞,但它们都涉及到Java反序列化安全问题。我会分别给你介绍这两个漏洞的原理和复现方法。 1. WebLogic反序列化漏洞原理分析及复现: WebLogic反序列化漏洞是指通过利用WebLogic Server中T3协议的漏洞攻击者可以在目标服务器上执行任意代码。这个漏洞的根本原因是WebLogic Server在处理T3协议时未正确过滤用户提供的数据,导致攻击者可以构造恶意的序列化数据,在服务器端触发反序列化漏洞。 复现该漏洞的步骤如下: 1) 下载并配置WebLogic Server环境。 2) 使用ysoserial工具生成payload,例如利用CommonsCollections的payload。 3) 构造T3协议请求,将生成的payload嵌入到请求中。 4) 启动WebLogic Server,并发送恶意请求。 5) 成功触发反序列化漏洞后,攻击者可以执行任意代码。 2. Fastjson反序列化漏洞原理分析及复现: Fastjson是一个常用的Java JSON库,该漏洞存在于Fastjson的版本1.2.24及之前的版本中。攻击者可以通过构造恶意的JSON数据,触发Fastjson的反序列化漏洞,从而执行任意代码。 漏洞的原因是Fastjson在反序列化JSON数据时,对默认类型进行了自动化检测和加载,并且允许调用类的默认构造函数,从而导致了代码执行漏洞攻击者可以通过构造恶意的JSON数据,在目标服务器上执行任意代码。 复现该漏洞的步骤如下: 1) 下载并配置Fastjson版本1.2.24或之前的环境。 2) 使用ysoserial工具生成payload,例如利用CommonsCollections的payload。 3) 构造恶意的JSON数据,将生成的payload嵌入到JSON中。 4) 编写测试代码,使用Fastjson进行反序列化操作。 5) 执行测试代码,成功触发反序列化漏洞后,攻击者可以执行任意代码。 请注意,漏洞利用是非法行为,仅在授权范围内进行安全测试和研究。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值