在看AJP协议数据包处理之前,先来了解一下Tomcat处理一个请求的过程。大致如下流程:
一次请求的处理可以划分为Connector及Container进行处理,经历的过程大致如下:
- 一个TCP/IP数据包发送到目标服务器,被监听此端口的Tomcat获取到。
- 处理这个Socket网络连接,使用Processor解析及包装成request和response对象,并传递给下一步处理。
- Engine来处理接下来的动作,匹配虚拟主机Host、上下文Context、Mapping Table中的servlet。
- Servlet调用相应的方法(service/doGet/doPost…)进行处理,并将结果逐级返回。
而对于使用HTTP协议或AJP协议进行访问的请求来讲,在解析包装成为request和response对象之后的流程都是一样的,主要的区别就是对socket流量的处理以及使用Processor进行解析的过程的不同。
提供这部分功能的接口为 org.apache.coyote.Processor<S>
,主要负责请求的预处理。并通过它将请求转发给Adapter,针对不用的协议则具有不同的实现类。
这个接口里定义了一些重要的方法:
这里主要还是针对于HTTP协议和AJP协议,抽象类AbstractProcessorLight
及其子类AbstractProcessor
还是对共有特性的封装。AbstractProcessor
具有三个子类,AjpProcessor
用来处理AJP协议,Http11Processor
用来处理HTTP/1.1,StreamProcessor
用来处理HTTP/2,我们先来看看针对平常使用的HTTP协议的处理。Http11Processor
重点的process()
方法,使用service()
方法来处理标准HTTP请求,这里我们重点看一下:
解析请求行和请求头部分:
在Tomcat 8.5 之后,加入了判断是否需要HTTP协议升级:
调用prepareRequest()
,将相关信息放入Http11InputBuffer
对象中
然后调用Adapter将请求交给Container处理:
然后接下来是一些收尾工作。在了解了这个过程后,我们再来看一下 AjpProcessor
中service()
方法,大体上是一致的流程,只是具体的细节不同,首先是一些解析数据包读取字节的操作,这里不是重点,暂且不提,然后也是调用 prepareRequest()
方法进行预处理:
处理之后同样的调用Adapter将请求交给Container处理
而AJP协议的任意文件读取/任意文件包含漏洞,则出现在上面提到的 prepareRequest()
方法中。