freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

漏洞分析 | U8 Cloud ServiceDispatcher反序列化漏洞及补丁分析
2023-11-04 10:23:03

0x01 概述

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

0x02 补丁分析

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

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

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

在 NetObjectInputStream和 NCObjectInputStream类中增加了新的构造方法 [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.classESBContextForNC.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方法并传入 request和 response

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)进行反序列化解析数据,存在反序列化漏洞(已报送给国家漏洞监管单位和厂商进行修复)。

# 漏洞 # 网络安全 # web安全 # 漏洞分析 # 网络安全技术
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录