深入浅出内存马

本文的技术是21年前的,我自己学习的输出笔记。如果你没学过,可以跟着我的思路来,但我更想表达的是我学习这个东西的思路,就是我是怎么学习的,这个比内容更重要。
谈不上精通,还有好多没写,但是已经不是当下的重点研究了,可以以Tomcat的学习研究为魔板套用到任何一个组件的漏洞挖掘上。
全文写作没有跟着任何一个其他别人文章的思路来的,我是从头到尾自己分析的,图也是自己画的,我只是拿着以前的代码,然后去调试,因为可以看懂源码和Tomcat内的运行逻辑,然后以拿到的代码为基础,延伸出一些自己的思考。我学习Tomcat大概用了一周,学习内存马并写文章用了一周。
如果想全面的去学习内存马可以去看https://xz.aliyun.com/t/11003#toc-10

要想真正明白内存马,必须要先熟悉Tomcat,为此我特意从头到尾刷了一遍《Tomcat深入分析》对Tomcat结构有了一个比较详细的了解。
我用大白话和图片给你讲解一下学习研究内存马或者看源码需要知道Tomcat中的哪些东西。
几个重要的组件
connector,container,service,server
还有几个有助于理解源码的组件
pipeline,valve

1.container和pipeline/valve

首先我们要理解Tomcat是由容器(Container)组成,从外到里分别逐层嵌套,Engine容器,Host容器,Context容器,Wrapper容器。一个Engine容器可以有多个虚拟主机容器(Host容器),一个主机(Host容器)可以有多个应用(Context容器),一个应用要有多个连接请求(Wrapper容器)。上面四个种容器都是Container的实现。
通常情况下上层容器与下层容器之间关系为父子容器,Host没有父容器,Wrapper没有子容器。
Tomcat提供给我们默认的类分别叫做StandardEngine,StandardHost, StandardContext, StandardWrapper。
pipeline是什么呢,中文名字是流水线,每个容器都有自己的pipeline,代表一个完成任务的管道。流水线上面有很多任务,具体任务是什么呢?就是Valve,中文名字是阀。其中Pipeline和Valve都是接口,对于Pipeline有一个标准实现StandardPipeline。对于Valve不同的容器有它自己的实现,比如StandardWrapper容器实现的StandardWrapperValve

20230712145709-51ecee82-2081-1.png
我们通过StandardWrapper举个例子,见下文

StandardWrapper

StandardWrapper 对象的主要职责是:加载它表示的 servlet 并分配它的一个实例。该 Standardwrapper 不会调用 servlet 的 service 方法。这个任务留给StandardWrapperValve 对象,在 StandardWrapper 实例的基本阀门管道。StandardVrapperValve 对象通过调用 StandardWrapper的 allocate 方法获得Servlet 实例。在获得 Servlet 实例之后的 StandardwrapperValve 调用 servlet的 service 方法。

下面代码中关注:构造函数,类成员,allocate方法,loadService方法

public class StandardWrapper extends ContainerBase
    implements ServletConfig, Wrapper, NotificationEmitter {
   
   
    // 构造函数,我们的pipeline中加入基本的阀,每个pipeline中必须有一个基本阀
    public StandardWrapper() {
   
   
        super();
        swValve=new StandardWrapperValve();
        pipeline.setBasic(swValve);
        broadcaster = new NotificationBroadcasterSupport();
    }
    // servlet类
    protected volatile Servlet instance = null;

    // 映射
    protected final ArrayList<String> mappings = new ArrayList<>();

    // 阀
    protected StandardWrapperValve swValve;

    @Override
    public Servlet allocate() throws ServletException {
   
   
		// ...
        boolean newInstance = false;

        // If not SingleThreadedModel, return the same instance every time
		// 这里涉及多线程访问servlet的问题,我们不关心
        if (!singleThreadModel) {
   
   
            // Load and initialize our instance if necessary
            if (instance == null || !instanceInitialized) {
   
   
                synchronized (this) {
   
   
                    if (instance == null) {
   
   
                        try {
   
   
					// 获取servlet
                            instance = loadServlet();
                            newInstance = true;
                        } catch (ServletException e) {
   
   
                            throw e;
                        }
                    }
                    if (!instanceInitialized) {
   
   
				// 初始化servlet
                        initServlet(instance);
                    }
                }
            }
//  省略有关instancePool的代码,类似于线程池,复用servlet
            return instancePool.pop();
        }
    }


    public synchronized Servlet loadServlet() throws ServletException {
   
   
        Servlet servlet;
        try {
   
   
            InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
            try {
   
   
                servlet = (Servlet) instanceManager.newInstance(servletClass);
            } catch (ClassCastException e) {
   
   
				//...
			}
			initServlet(servlet);
			// 事件通知机制,背后有listener监听
            fireContainerEvent("load", this);
			return servlet;
    }
}

我们再看看StandardWrapperValve,主要关注invoke方法中的servlet获取,过滤链的创建和调用

final class StandardWrapperValve extends ValveBase {
   
   
    @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {
   
   

        boolean unavailable = false;
        requestCount.incrementAndGet(); //请求数量
        StandardWrapper wrapper = (StandardWrapper) getContainer(); // 获取wrapper
        Servlet servlet = null; // servlet
        Context context = (Context) wrapper.getParent(); // wrapper父容器,代表一个应用

		// 检查application是否可用
		// 检查servlet是否可用
		//...

		// 分配一个servlet实例来处理request请求
        try {
   
   
            if (!unavailable) {
   
   
                servlet = wrapper.allocate(); // 上文我们分析的allocate方法
            }
        } catch (Exception e) {
   
   
			// ...
		}

		// 设置一些属性
        // ...
        // 为请求创建一个过滤链
        ApplicationFilterChain filterChain =
                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

        Container container = this.container;
        try {
   
   
            if ((servlet != null) && (filterChain != null)) {
   
   
                if (context.getSwallowOutput()) {
   
   
					// 不重要
                } else {
   
   
                    if (request.isAsyncDispatching()) {
   
   
                        request.getAsyncContextInternal().doInternalDispatch();
                    } else {
   
   
						// 执行过滤链逻辑
						// 执行过程中也会嗲用servlet的service方法
                        filterChain.doFilter
                            (request.getRequest(), response.getResponse());
                    }
                }

            }
        } catch (ClientAbortException | CloseNowException e) {
   
   
			// ... 一些异常
        } finally {
   
   
		    // ... 释放资源,重置时间
        }
    }
}

StandardWRapper的主要作用就是加载创建Servlet实例,以及一些关于Servlet的setter,getter方法
20230712155437-59780d46-2089-1.png

到目前为止我们捋顺一下逻辑
20230712154404-e0217d48-2087-1.png
注意一下,最开始的StandardWrapper.getPipeline().getFirst().invoke(request, response);是上层Context任务的调用,毕竟要完成一个任务需要调用子任务。

如果不是很清晰的话,就看下代码,注意最下面的代码部分

final class StandardContextValve extends ValveBase {
   
   
	    @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {
   
   
		// .....
		// 注意这里
        wrapper.getPipeline().getFirst().invoke(request, response);
    }
}

重复这种模式,container->pipeline->valve->container->pipeline->valve,看一下实际的调用栈
20230712154831-7f30e158-2088-1.png
ok,我们再最后具体了解一下剩下的几个容器的作用,然后回到最上层的Connector

Context/Host/Engine

Context

一个应用程序有自己的资源,类加载器,管理器。所以StandardContext也要有这些内容

public class StandardContext extends ContainerBase
        implements Context, NotificationEmitter {
   
   
    public StandardContext() {
   
   

        super();
        pipeline.setBasic(new StandardContextValve());
        broadcaster = new NotificationBroadcasterSupport();
        // Set defaults
        if (!Globals.STRICT_SERVLET_COMPLIANCE) {
   
   
            // Strict servlet compliance requires all extension mapped servlets
            // to be checked against welcome files
            resourceOnlyServlets.add("jsp");
        }
		// 监听器
		private String applicationListeners[] = new String[0];
		private List<Object> applicationEventListenersList = new CopyOnWriteArrayList<>();
		// 初始化器
		private Map<ServletContainerInitializer,Set<Class<?>>> initializers = new LinkedHashMap<>();
		// ServletContext的标准实现,表示web应用程序的执行环境。这个类的一个实例与StandardContext的每个实例相关联。
		protected ApplicationContext context = null;
		// 过滤器配置
		private Map<String, ApplicationFilterConfig> filterConfigs = new HashMap<>();
		// 过滤器的定义信息
		private Map<String, FilterDef> filterDefs = new HashMap<>();
		// 类加载器
		private Loader loader = null;
		// 管理器
		protected Manager manager = null;
		// 资源
		private NamingResourcesImpl namingResources = null;
		// 访问参数
		private final Map<String, String> parameters = new ConcurrentHashMap<>();
		// ....等
    }
    @Override
    protected synchronized void startInternal() throws LifecycleException {
   
   
		// 该方法启动所有的线程
		namingResources.start();
		resourcesStart();
		((Lifecycle) loader).start();
		((Lifecycle) realm).start();
		// 启动子容器
		 for (Container child : findChildren()) {
   
   
			if (!child.getState().isAvailable()) {
   
   
				child.start();
			}
		}
		((Lifecycle) pipeline).start();
		((Lifecycle) manager).start();
	}
}

然后它还有一些后台线程热部署资源的东西,可以不用停机重新打包,自动更新资源

Host

主机就是一些路径处理,部署处理等等

Engine

引擎代表整个catalina的引擎

2.Server/Service

org.apache.catalina.Server代表catalina服务,其中有端口地址,开关服务,还可以添加很多service,有global资源,类加载器等等

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值