复现环境
nccloud 2105
补丁
https://security.yonyou.com/#/noticeInfo?id=280
![]() |
|---|
漏洞分析
漏洞接口为/uapjs/jsinvoke,全局搜索jsinvoke找到hotwebs/uapjs/WEB-INF/web.xml文件中,/jsinvoke/*路由对应的servlet为nc.bs.framework.js.servlet.JsInvokeServlet。
![]() |
|---|
nc.bs.framework.js.servlet.JsInvokeServlet这个类所在的jar包并不在对应路径的lib下,可以随便找个路由远程调试,使用idea的执行表达式功能,反射加载该类并输出该类在那个路径下,再将其添加到库中。
1 | Class clazz = Class.forName("nc.bs.framework.js.servlet.JsInvokeServlet"); |
![]() |
|---|
跟入到JsInvokeServlet中,调用了this.internal的service方法,this.internal为nc.bs.framework.js.servlet.InternalJsInvokeServlet类的实例对象,调用的也就是InternalJsInvokeServlet的service方法。
![]() |
|---|
跟入InternalJsInvokeServlet类中,service调用了invoke方法,在invoke方法中首先调用了CommandFactory.getCommand(request)去拿到一个ICommand对象。
![]() |
|---|
在CommandFactory.getCommand方法中,从request中获取action参数,来创建不同的对象,并返回,在创建对象的时候会从request中读取数据作为参数进行创建。漏洞利用的点是在InvokeCommand对象中,所以需要传入invoke。
![]() |
|---|
回到nc.bs.framework.js.servlet.InternalJsInvokeServlet的invoke方法中,接下来会调用InvokeCommand的execute方法。
![]() |
|---|
在execute方法中,会将this.data反序列化成MethodInvocation对象,这里的this.data是在CommandFactory.getCommand方法中执行readJSONString(request);被设置的,是请求时body中传递的json字符串,会将json的值赋值到MethodInvocation对象的属性中。
![]() |
|---|
主要的反射调用逻辑在handler.handleMethodInvocation(invocation),创建了MethodInvocationHandler对象,以MethodInvocation对象作为参数调用了handleMethodInvocation方法。
![]() |
|---|
跟入到MethodInvocationHandler的handleMethodInvocation方法中,通过ServiceName作为参数调用NCLocator.getInstance().lookup(invocation.getServiceName())方法,获得一个Object对象,在将这个对象作为参数调用invocation.invoke(stub)。
![]() |
|---|
首先是调用了NCLocator.getInstance()拿到一个NCLocator对象,拿到的是ServerNCLocator对象。
![]() |
|---|
随后会调用ServerNCLocator的lookup方法,紧接着会执行BusinessAppServer.getInstance().getServerContext().lookup(name);,最终会调用到nc.bs.framework.server.AbstractContext的lookup方法。
![]() |
|---|
在nc.bs.framework.server.AbstractContext的lookup方法中,过程看起来比较复杂。
![]() |
|---|
首先会判断name的前缀是否以->开头,是的话会截取->后的字符串,调用this.findMeta(name)获取ComponentMeta对象,然后以ComponentMeta对象作为参数调用this.findComponent(meta)获取Object对象进行返回。
![]() |
|---|
不是的话,会调用this.getServiceCache().get(name)从缓存中拿到Object对象,没在缓存中,会判断前缀是否以java:comp/env/开头,那么会调用jndiCtx.lookup(name)进行加载,拿到retObject对象。
![]() |
|---|
name既不是以->开头,也不是java:comp/env/开头,会调用this.findMeta(name)去获取ComponentMeta对象。
![]() |
|---|
跟入到this.findMeta(name)方法中
![]() |
|---|
最终会调用到nc.bs.framework.server.AbstractContainer的getMeta方法,this.publicRepo是一个PublicMetaRepo对象,metas和nameIndices为该类的属性。
![]() |
|---|
getComponentMeta方法则是通过name在nameIndices中查找对应的ComponentMeta对象并返回。
![]() |
|---|
最后会根据ComponentMeta对象调用this.findComponent(meta)。
![]() |
|---|
获取实例并返回。
![]() |
|---|
最后回到nc.bs.framework.js.rmi.MethodInvocationHandler的handleMethodInvocation方法中
![]() |
|---|
跟入invocation.invoke(stub),对指定的serviceName和methodName进行反射调用。
![]() |
|---|
通过如上分析,可以知道最终是可以反射调用一些特定的类的方法,并不是所有的类的方法都能够调用,只能是this.publicRepo中nameIndices这个属性中的,数量还比较多,大概有1w多个,那么只需要在这里面筛选出一些有危害的可利用的类进行反射调用来利用。
利用类
nc.bs.iufo.base.BaseSPService就是保存在nameIndices这个属性当中,该类的saveXStreamConfig方法可传入url和config两个参数,url可控制写入路径,config可控制写入的文件内容,但是写入的文件内容是在xml中,这里选择用EL表达式来进行利用。
1 | public void saveXStreamConfig(Object config, String url) { |
漏洞利用
首先需要写入动态执行js表达式payload
1 | POST /uapjs/jsinvoke?action=invoke HTTP/1.1 |
![]() |
|---|
命令执行回显
1 | POST /403.jsp HTTP/1.1 |
![]() |
|---|
写入webshell
1 | POST /403.jsp HTTP/1.1 |
![]() |
|---|
冰蝎连接 /501.jsp mzr@123
![]() |
|---|
打入内存马
1 | POST /uapjs/jsinvoke?action=invoke HTTP/1.1 |
![]() |
|---|
先访问 http://172.20.10.190:8080/403.jsp,再连接
1 | 密码: mzr@123 |
![]() |
|---|




























