写在前面
前面已经从代码层面讲解了Tomcat的架构,这是内存马系列文章的第五篇,带来的是Tomcat
Executor类型的内存马实现。有了前面第四篇中的了解,才能更好的看懂内存马的构造。
前置
什么是Executor
Executor是一种可以在Tomcat组件之间进行共享的连接池。
我们可以从代码中观察到对应的描述:
The Executor implementations provided in this package implement
ExecutorService, which is a more extensive interface. The ThreadPoolExecutor
class provides an extensible thread pool implementation. The Executors class
provides convenient factory methods for these Executors.
Memory consistency effects: Actions in a thread prior to submitting a
Runnable object to an Executor happen-before its execution begins, perhaps
in another thread.

Executes the given command at some time in the future. The command may
execute in a new thread, in a pooled thread, or in the calling thread, at
the discretion of the Executor implementation.
Params: command – the runnable task
Throws: RejectedExecutionException – if this task cannot be accepted for
execution
NullPointerException – if command is null
对于他的作用,允许为一个Service的所有Connector配置一个共享线程池。
在运行多个Connector的状况下,这样处理非常有用,而且每个Connector必须设置一个maxThread值,但不希望Tomcat实例并发使用的线程最大数永远与所有连接器maxThread数量的总和一样高。
这是因为如果这样处理,则需要占用太多的硬件资源。相反,您可以使用Executor元素配置一个共享线程池,而且所有的Connector都能共享这个线程池。
分析流程
通过上篇文章的分析我们知道,
在启动Tomcat的时候首先会。
调用启动类,并传入参数start预示着Tomcat启动:

这里调用start方法进行相关配置的初始化操作,
一直走到了org.apache.catalina.startup.Catalina类中load方法中调用了。this.getServer().init()方法进行Server的初始化操作,
即调用了LifecycleBase#init方法,进而调用了initInternal方法,即来到了他的实现类StandardServer#initInternal中来了。

上篇中也提到过,将会循环的调用所有service的init方法,进而调用了StandardService#initInternal方法进行初始化,调用了Engine#init方法,因为没有配置Executor,所以在初始化的时候不会调用他的init方法,之后再调用mapperListener.init()进行Listener的初始化操作,在获取了所有的connector之后将会循环调用其init方法进行初始化。

在初始化结束之后将会调用start方法

即调用了Bootstrap#start方法,进而调用了Server.start方法
来到了StandardService#startInternal方法,紧跟着调用了上面调用了Init方法的start方法,成功启动Tomcat。
正文
接下来我们来分析一下为什么选用Executor来构造内存马,和如构造内存的流程。
分析注入方式
在成功开启了Tomcat之后,我们可以在Executor中的execute方法中打下断点,

之后运行访问8080端口
在前面那一篇文章中我们知道Acceptor是生产者,而Poller是消费者,
在执行Endpoint.start()会开启Acceptor线程来处理请求。
在其run方法中存在
-
运行过程中,如果
Endpoint暂停了,则Acceptor进行自旋(间隔50毫秒); -
如果
Endpoint终止运行了,则Acceptor也会终止; -
如果请求达到了最大连接数,则wait直到连接数降下来;
-
接受下一次连接的socket。
这一步己经在运行Tomcat容器的时候已经进行了,
在我们访问Tomcat的页面之后将会创建一个线程,并调用target属性的run方法,这里的target就是Poller对象(消费者)。

即调用了NioEndpoint$Poller#run方法,跟进
public void run() {
while(true) {
boolean hasEvents = false;
label58: {
try {
if (!this.close) {
hasEvents = this.events();
if (this.wakeupCounter.getAndSet(-1L) > 0L) {
this.keyCount = this.selector.selectNow();
} else {
this.keyCount = this.selector.select(NioEndpoint.this.selectorTimeout);
}
this.wakeupCounter.set(0L);
}
if (!this.close) {

本文是内存马系列的第五篇,详细介绍了如何利用Tomcat的Executor构造内存马。通过分析Executor的工作原理,展示了从启动Tomcat到Executor执行请求的流程,探讨了如何在Executor的execute方法中插入恶意代码,实现命令执行并回显结果,提供了构造内存马的步骤和简单示例。
最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



