Tomcat 学习

渗透技巧 2年前 (2022) admin
580 0 0

前言

在学习内存马之前,了解tomcat的运行原理是十分有必要的。

Tomcat 整体架构

Tomcat 结构比较复杂,我们先从重点的结构上分别学习各个模块。

Tomcat 学习

Tomcat 中最顶层的容器是Server,一个Server包含多个Service,一个Service只能有一个Container ,但可以有多个Connector。

Service主要用来提供对外服务,包含两个部分:Connector 连接器和Container 容器。

Connector 连接器用于处理连接相关的事情,并提供Socket与请求、响应之间的转换;Container 容器用于封装和管理Servlet,以及具体处理Request请求;

Coyote 连接器

Coyote 是Tomcat 连接器Connector框架的名称,封装了底层的网络通信(Socket 请求及响应处理),为Servlet容器Catalina提供了统一的接口。Coyote 中支持多种应用层协议和I/O模型。

IO 模型 描述
NIO 非阻塞同步I/O,采用Java NIO类库实现。
NIO2 非阻塞异步I/O,采用JDK 7最新的NIO2类库实现。
APR 采用Apache可移植运行库实现,是C/C++编写的本地库。需要单独安装APR库。
应用层协议 描述
HTTP/1.1 常见的Web访问协议
AJP 用于和Web服务器集成(如Apache),以实现对静态资源的优化以及集群部署,当前支持AJP/1.3。
HTTP/2 HTTP 2.0 下一代HTTP协议

连接器组件如下:

Tomcat 学习

1、EndPoint 监听点 Coyote 通信端点,即通信监听的接口,是具体Socket接收和发送处理器,用来实现TCP/IP协议。


2、Processor 处理接口 Processor接收来自EndPoint的Socket,读取字节流解析为Tomcat Request和Response对象,实现HTTP协议。

3、ProtocolHandler 协议处理器 通过Endpoint 和 Processor,实现针对具体协议的处理能力。

4、Adapter 适配器 将ProtocolHandler 协议处理器解析出的Tomcat Request对象,适配成ServletRequest,再调用ServletRequest的Service方法。

Container 容器

Container 用于封装和管理Servlet,以及具体处理Request请求,在Container内部包含了4个子容器。如下图:

Tomcat 学习

1、Engine Catalina的Servlet引擎,用来管理多个虚拟的主机,每个Service一个Engine,但是可以包含多个虚拟主机Host。

2、Host 代表一个虚拟主机,可以给Tomcat配置多个虚拟主机地址,而一个虚拟主机下可包含多个Context。

3、Context 表示一个Web应用程序,一个Web应用可包含多个Wrapper。

4、Wrapper Servlet的别名,是容器中的最底层。

server.xml 配置文件可以充分说明这个容器结构。

<Server>  <Service>    <Engine>      <Host>        <Context></Context>      </Host>    </Engine>  </Service></Server>

Catalina 容器

在前文中多次提到Catalina容器,那么它又是做什么的呢?Catalina负责管理Server,Server表示着整个服务器。也可以理解为一个Server就是一个Catalina。如下图:

Tomcat 学习

组件 职责
Catalina 负责解析Tomcat的配置文件,以此来创建服务器Server组件
Server 表示整个Catalina Servlet容器以及其它组件,负责组装并启动Servlet引擎,Tomcat连接器。
Service 一个Server包含多个Service。将若干个Connector 绑定到一个Container 容器(Engine)上。
Connector 连接器,用于处理连接相关的事情,并提供Socket与请求、响应之间的转换。
Container 容器,负责处理用户的servlet请求,并返回对象给Web用户。

Tomcat 源码安装

下载源码解压后,创建home文件夹,并将conf、wappers目录移到该文件夹中。创建pom.xml文件。

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>org.apache.tomcat</groupId> <artifactId>apache-tomcat-8.5.73-src</artifactId> <name>Tomcat8.5</name> <version>8.5</version>
<build> <finalName>Tomcat8.5</finalName> <sourceDirectory>java</sourceDirectory> <!-- <testSourceDirectory>test</testSourceDirectory> --> <resources> <resource> <directory>java</directory> </resource> </resources> <!-- <testResources> <testResource> <directory>test</directory> </testResource> </testResources>--> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3</version> <configuration> <encoding>UTF-8</encoding> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.easymock</groupId> <artifactId>easymock</artifactId> <version>3.4</version> </dependency> <dependency> <groupId>ant</groupId> <artifactId>ant</artifactId> <version>1.7.0</version> </dependency> <dependency> <groupId>wsdl4j</groupId> <artifactId>wsdl4j</artifactId> <version>1.6.2</version> </dependency> <dependency> <groupId>javax.xml</groupId> <artifactId>jaxrpc</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>org.eclipse.jdt.core.compiler</groupId> <artifactId>ecj</artifactId> <version>4.5.1</version> </dependency> <dependency> <groupId>org.apache.ant</groupId> <artifactId>ant</artifactId> <version>1.7.0</version> </dependency>
</dependencies></project>

再以maven源码导入。

Tomcat 学习

配置启动类。

-Dcatalina.home=/Users/cseroad/IdeaProjects/Tomcat_Project/apache-tomcat-8.5.73-src/home-Dcatalina.base=/Users/cseroad/IdeaProjects/Tomcat_Project/apache-tomcat-8.5.73-src/home-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager-Djava.util.logging.config.file=/Users/cseroad/IdeaProjects/Tomcat_Project/apache-tomcat-8.5.73-src/home/conf/logging.properties

org.apache.catalina.startup下,找到Bootstrap类的main方法开始启动。访问tomcat如果出现500错误,需要在org.apache.catalina.startup包下的ContextConfig类中 configureStart方法添加

// 初始化jsp解析器context.addServletContainerInitializer(new JasperInitializer(), null);

再次重启即可。

Tomcat 学习

请求处理

先创建一个Servlet并配置web.xml,然后编译请求地址,再整个servlet_war_exploded拷贝出来。部署在Tomcat上并以源码的形式进行追踪分析请求过程。


web.xml

    <servlet>        <servlet-name>BbsServlet</servlet-name>        <servlet-class>com.itcast.web.BbsServlet</servlet-class>    </servlet>
<servlet-mapping> <servlet-name>BbsServlet</servlet-name> <url-pattern>/bbs/findAll</url-pattern> </servlet-mapping>

BbsServlet

import java.io.IOException;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;
public class BbsServlet extends HttpServlet { public BbsServlet() { }
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("GET请求"); }
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("post请求"); }}

Tomcat 学习

在启动Tomcat时,Tomcat会先初始化。然后endpoint准备完毕,等待外部的请求连接。从org.apache.tomcat.util.net.NioEndpoint类内部的Acceptor类的run方法开始

Tomcat 学习

处理接收到的socket对象,获取输入输出流,然后注册到Poller当中,添加到PollerEvent队列当中。

Tomcat 学习

org.apache.tomcat.util.net的register方法。

Tomcat 学习

而后Poller进行处理socket,调用org.apache.tomcat.util.net.NioEndpoint的processKey方法处理读写。

Tomcat 学习

Tomcat 学习

里面就到org.apache.tomcat.util.net.AbstractEndpoint.processSocket的Executor实现的线程池进行处理。

Tomcat 学习

提交到线程池,放入线程池的 workQueue 中。再调用org.apache.tomcat.util.net.NioEndpoint.SocketProcessor的doRun()方法。

Tomcat 学习

org.apache.coyote.AbstractProtoco.ConnectionHandler的process方法处理socket请求。

Tomcat 学习

该方法里面创建一个Http11Processor,解析socket请求。这样Socket中的内容就会封装到Request 中。

通过getAdapter获取合适的处理器,调用service方法进行处理。执行的是org.apache.catalina.connector.CoyoteAdapter类的 service 方法。

Tomcat 学习

创建了最终我们使用的ServletRequest和ServletResponse。来到org.apache.catalina.connector.CoyoteAdapter类的service方法。然后调用容器,getContainer方法会拿到service关联的Engine。

connector.getService().getContainer().getPipeline().getFirst().invoke(                        request, response);

先将请求传递到 Engine 管道中,传递到Engine Valve 这个阀中。调用org.apache.catalina.core.StandardEngineValve下的invoke方法。

Tomcat 学习

获取host,接着invoke方法里面为

host.getPipeline().getFirst().invoke(request, response);

表示请求从 Engine Valve 传递到一个 Host 的管道中,在该管道中最后传递到 Host Valve 这个阀里。调用org.apache.catalina.core.StandardHostValve下的invoke方法。

Tomcat 学习

获取context,接着invoke方法里面为

context.getPipeline().getFirst().invoke(request, response);

表示从 Host Valve 传递到 Context 的管道中,在该管道中最后传递到 Context Valve 中。调用org.apache.catalina.core.StandardContextValve下的invoke方法。

Tomcat 学习

获取wrapper,接着invoke方法里面为

wrapper.getPipeline().getFirst().invoke(request, response);

表示请求传递到 Wrapper Valve 中,在这里会经过一个过滤器链 Filter Chain ,最终到熟悉的Servlet中。调用org.apache.catalina.core.StandardWrapperValve下的invoke方法。

Tomcat 学习

创建Servlet,再往下创建filterChain过滤器链。org.apache.catalina.core.StandardWrapperValve下的invoke方法。

Tomcat 学习

而后执行过滤器链,org.apache.catalina.core.StandardWrapperValve下的invoke方法。

Tomcat 学习

执行org.apache.catalina.core.ApplicationFilterChain 下的 doFilter 方法。

Tomcat 学习

往下调用internalDoFilter方法,实际调用的org.apache.catalina.core.ApplicationFilterChain下的 internalDoFilter 方法。

Tomcat 学习

到HttpServlet类的service方法,再调用我们编写的doGet()方法。

Tomcat 学习

整个过程极为复杂,画张图来梳理一下。

Tomcat 学习

总结: endpoint收到socket请求,调用handler找到Processor,接着调用CoyoteAdapter,将socket请求处理为Request请求。这就是tomcat连接器的工作。接着CoyoteAdapter获取到Engine,Engine获取到host,host获取到Context,Context获取到Wrapper,Wrapper在反射调用就获取到了Servlet,然后创建过滤器、执行过滤器链,再执行Servlet。

其他配置

创建host

在server.xml中给Host添加别名,实现同一个Host拥有多个网络名称。

      <Host name="www.cseroad.tomcat"  appBase="webapps1"            unpackWARs="true" autoDeploy="true">            <Context docBase="E:app" path="/app"  reloadable="true" ></Context>      </Host>

docBase:Web应用目录或者War包的部署路径。可以是绝对路径,也可以是相对于Host appBase的相对路径。path:Web应用的Context 路径。

配置错误页面

在web.xml中配置

<error-page>    <error-code>404</error-code>    <location>/404.html</location></error-page><error-page>    <error-code>500</error-code>    <location>/500.html</location></error-page>

当出现404错误代码,就跳转至404.html自定义页面。

总结

针对Tomcat的源码学习有些吃力,整个过程需要结合代码、资料不断调试加深理解。

参考资料

https://blog.csdn.net/ly823260355/article/details/104181278 https://www.bilibili.com/video/BV1dJ411N7Um?p=5


原文始发于微信公众号(Wings安全团队):Tomcat 学习

版权声明:admin 发表于 2022年10月13日 下午5:01。
转载请注明:Tomcat 学习 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...