Goby社区第 35 篇技术分享文章
全文共:6284 字 预计阅读时间:16 分钟
近期,爆出了 U8cloud ServiceDispatcherServlet 接口的反序列化漏洞。在对该漏洞进行分析时,我们发现 NC 也曾出现过 ServiceDispatcherServlet 接口的反序列化漏洞。经过分析后发现,这两个漏洞的功能代码实现方式并不相同。但二者都实现了自定义的序列化过程(本文简称 NetObjectStream),并且对传输的序列化流进行了合法性检测。因此,该漏洞的利用条件需要自定义序列化类生成序列化数据进行攻击。
在漏洞分析过程中,我们发现并验证了其他接口也使用了自定义的序列化类(NetObjectStream)进行反序列化解析数据,存在反序列化漏洞(已报送给国家漏洞监管单位和厂商进行修复)。
02 补丁分析
官方的修复方式是通过增加序列化对象的限定类来防止反序列化漏洞。具体是在 NetObjectInputStream 和 NCObjectInputStream 类中增加了新的构造方法,在构造方法中增加了 InvocationInfo.class、ESBContextForNC.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.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) {
...
}
}
03 路由分析
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>
04 漏洞分析
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]
}
}
05 漏洞利用
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);
}
06 总结
本文对 U8Cloud ServiceDispatcher 接口反序列化漏洞原理进行跟踪分析,讲述了该漏洞是如何被利用以及官方的修复方式。同时,在漏洞分析过程中,我们发现并验证了其他接口也使用了自定义的序列化类(NetObjectStream)进行反序列化解析数据,存在反序列化漏洞(已报送给国家漏洞监管单位和厂商进行修复)。
• 14m3ta7k | 死磕Jenkins漏洞回显与利用效果
• M1sery | Adobe ColdFusion 序列化漏洞(CVE-2023-29300)
• M1sery | Adobe ColdFusion WDDX 序列化漏洞利用
• TonyD0g | 跨越语言的艺术:Flask Session 伪造
• kv2 | 针对蜜罐反制Goby背后的故事
• Gryffinbit | 某下一代防火墙远程命令执行漏洞分析及防护绕过
Goby 欢迎表哥/表姐们加入我们的社区大家庭,一起交流技术、生活趣事、奇闻八卦,结交无数白帽好友。
也欢迎投稿到 Goby(Goby 介绍/扫描/口令爆破/漏洞利用/插件开发/ PoC 编写/ IP 库使用场景/ Webshell /漏洞分析 等文章均可),审核通过后可奖励 Goby 红队版,快来加入微信群体验吧~~~
微信群:公众号发暗号“加群”,参与积分商城、抽奖等众多有趣的活动
获取版本:https://gobysec.net/sale