用友nc远程命令执行漏洞分析
NC用友远程命令执行漏洞分析
0x00前言
这个漏洞是 国家电网公司信息与网络安全重点实验室 发现的,微信公众号做了简要分析,但是没有给出具体的poc。现在微信公众号已经将文章删除,但其他的安全公众号仍留有记录,看了一下分析过程,故想要跟着分析漏洞成因,编写poc,因此才有了这一篇分析文章。虽然此次漏洞公告上写的版本为NC6.5,但是实际上像较新版的1909 NCCloud(用友2018年11月发布的最新技术架构软件)等都是存在该漏洞的。
0x01分析过程
1.客户端分析
访问http://ip:port/index.jsp
会提示有两种方法登录系统,一种是通过下载客户端、一种是使用浏览器访问。因为,浏览器访问的方式需要依赖不同用户设备上的java版本,IE浏览器,系统配置等环境因素,使用起来不是很方便,所以为了解决这些问题,用友提供了系统专用的UClient浏览器,可直接通过该浏览器访问nc而无需安装配置任意东西。
下载NClient并安装后,进入启动页面,可以选择添加应用。添加完后,在安装目录中可以看到所安装的应用。
点击app.esc发现直接启动了nc客户端,查看文件内容,发现执行了NClogin65.jar文件中的nc.starter.test.JStarter:
反编译NClogin65.jar,查看nc.starter.test.JStarter,调用nc.starter.ui.NCLauncher#main主要是与远程服务端通信,生成uI之类的操作。主要的通信代码并不在该jar包,继续在目录中寻找。
在nc_client_home\NCCACHE\CODE目录的子目录中有很多的jar包,其中external目录上的jar包是客户端通信的逻辑代码。
随便点了一两个包,发现类还不少,如果逐个看的话很耗费时间,效率还不高,看了分析的文章发现用javaagent把调用的类都打印出来的方法可以解决这个问题,具体原理感兴趣的可以去网上搜索相关的文章,具体可以代码可以参考javaagent项目中使用。
之前在app.esc文件中可以看到启动jar的jvm配置信息,加上我们的javaagent的jar包,这样才能正常加载自己的javaagent。
启动后可以看到把所有调用的类都输出出来了。
这里可以配合idea的远程调试,添加参数jdwp:
1 | -agentlib:jdwp=transport=dt_socket,server=y,address=8000 |
打开客户端,随便输入账号密码点击登录后,查看log可以看到有login字眼的类:
找到对应的nc.login.ui.LoginUISupport类,这个类方法很多,我一开始想的是通过一般登录都是带有request,response的,所以我就搜了request的关键字,在一个看起来比较像处理登录请求的方法下了断点,点击登录后果然是在这个地方断下来了。该方法主要是将输入的用户名、密码等值,在requestd类的变量赋值。
执行getInstance(),获取NCLocator的实例,并执行实例的lookup方法。
跟进nc.bs.framework.common.NCLocator#getInstance(java.util.Properties)
刚启动的时候locatorMap为空,则会在下面的判断分支中,创建RmiNCLocator实例并将该实例存放到locatorMap中。
1 | locator = (NCLocator)locatorMap.get(key); |
获取到RmiNCLocator实例后,跟进到lookup方法:
1 | //nc.bs.framework.rmi.RmiNCLocator#lookup |
调用了this.remoteContext.lookup(name);
,继续跟进:
1 | //nc.bs.framework.rmi.RemoteContextStub#lookup |
可以看到先从proxyMap中查看是否存在参数name的方法,如果存在则直接返回,不存在则进入另外的分支。因为这里我是刚启动的,所以该方法是不存在的,也可以直接将so赋值为null,进去到下面的分支去看看具体的执行流程。
跟进nc.bs.framework.rmi.RemoteContextStub#getMetaOnDemand
,又可以看到调用了this.remoteMetaContext.lookup(name);
方法,这个变量remoteMetaContext是nc.bs.framework.server.RemoteMetaContext
类,那么这个类是怎么来的呢?
因为当前的类为nc.bs.framework.rmi.RemoteContextStub#RemoteContextStub
,这里可以回到类构造函数,第65行创建了一个代理,并赋值到this.remoteMetaContext
:
了解过java代理的应该知道,不管用户调用代理对象的任何方法,该方法都会调用处理器的invoke方法,这里即是nc.bs.framework.rmi.RemoteInvocationHandler#invoke
。不懂的可以先看这里。
那么现在回到上面,跟进this.remoteMetaContext.lookup(name);
,果然进入到了invoke方法。经过一番判断
执行this.sendRequest(method, args)方法。
1 | //nc.bs.framework.rmi.RemoteInvocationHandler#sendRequest(java.lang.reflect.Method, java.lang.Object[]) |
继续跟进this.sendRequest(target, ii, method, args);
,在第182行将ii序列化输出,发送到http://server:port/ServiceDispatcherServlet
,并获取服务端返回的结果反序列化,回显到客户端。
到此客户端的处理流程大致分析完成,看到这里大家可能有会对上面客户端将类序列化发往服务端,那服务端肯定要反序列化呀,会不会有问题?别急,继续往下看。
2.服务端分析
先来分析jndi注入的形成
在nc.bs.framework.comn.serv.CommonServletDispatcher#doPost
第38行下断点。
跟进this.rmiHandler.handle(new HttpRMIContext(request, response));
,跟进后在第85行继续跟进this.doHandle(rmiCtx);
。
在第153行出现处理客户端提交内容的,继续跟进:
在第282行可以看到直接将输入流的内容反序列化了,代码执行过程中完全没有任何的过滤,确实存在触发反序列化漏洞,这里先不管,继续往下。
这里注意的是第286行将反序列化后的类赋予到抽象类nc.bs.framework.rmi.server.AbstractRMIContext#invInfo
的invInfo变量里,这个变量在下面用到。
回到刚才的第二个断点,跟进result.result = this.invokeBeanMethod(rmiCtx);
,这里第333行就是上面说到的invinfo变量,实际就是反序列化后的类。
这里有两个分支,不管是哪个都存在jndi注入,因为这里lookup的参数service是可控的,所以必然存在漏洞。
3.效果演示
这里就不给poc了,如果看懂了上面的过程其实也不难,实际就是构造一个InvocationInfo类,并将servicename的值设置为远程恶意类,序列化后发送到服务端触发jndi注入即可。
4.前面的反序列化
上面发现的反序列化根本都没有过滤的,为啥还要这么麻烦要jndi注入呢,直接反序列化不香嘛?看了web的依赖环境,commons-collections3.2,那不是现成的利用嘛。
直接ysoserial生成恶意类发送,弹计算器。
整个调用栈如下:
注:在NCCloud 1909的版本中该依赖包为较新版,此利用链不可用。
0X02 最后说几句
漏洞过程并不是太复杂,应该不难理解的。实际上,用友NC系统多处存在未过滤的反序列化漏洞,不过由于新版NC将依赖版本更新了,yso的大多数利用链都不能使用了,因此需要重新寻找新的利用链。