Tomcat AJP协议漏洞CVE-2020-1938:原理、复现与安全加固

📅 2026/7/5 1:02:13 👁️ 阅读次数 📝 编程学习
Tomcat AJP协议漏洞CVE-2020-1938:原理、复现与安全加固

1. 项目概述:从一次内部渗透测试说起

前段时间,公司内部做了一次红蓝对抗演练,目标是测试我们自研的几个Web应用的安全性。在信息搜集阶段,我习惯性地用Nmap扫了一下目标服务器的端口,发现除了常见的80、443,还有一个8009端口处于开放状态,后面跟着一个ajp的服务标识。这个发现让我心里“咯噔”了一下,因为ajp(Apache JServ Protocol)协议通常与Tomcat的AJP连接器相关,而它历史上出过一些严重的问题。我立刻想到了那个著名的CVE-2020-1938,也就是CNVD-2020-10487,一个影响范围极广的Tomcat AJP协议文件包含/读取漏洞。为了更深入地理解这个漏洞的成因、利用条件以及修复方案,我决定在本地环境完整地复现一遍这个漏洞。这不仅是为了验证其危害性,更是为了在未来的安全评估和代码审计中,能更精准地识别和防御此类风险。如果你负责的Java Web应用部署在Tomcat上,并且配置了AJP连接器,那么这篇文章将带你从攻击者的视角,一步步拆解这个漏洞,理解其背后的协议逻辑和配置缺陷,最终掌握加固的方法。

2. 漏洞核心原理与AJP协议浅析

2.1 AJP协议:Tomcat的“后门”通道

在深入漏洞之前,我们必须先理解AJP是什么。很多人把Tomcat和HTTP直接划等号,但实际上,Tomcat作为一个Servlet容器,支持多种协议与外部通信。HTTP/1.1 Connector是面向最终用户的,而AJP(Apache JServ Protocol)Connector更像是一个“内部通道”。它通常用于Tomcat与前端的Web服务器(如Apache HTTPD、Nginx通过模块)进行集成,将动态请求通过AJP协议转发给后端的Tomcat处理,静态资源则由前端服务器直接响应。这种架构能提升整体性能。

AJP是一个二进制协议,相比文本协议的HTTP,它更紧凑、高效。通信默认在8009端口。关键点在于:AJP协议设计之初就假设连接的两端(前端服务器和Tomcat)是可信的,通常处于同一个受保护的内部网络。因此,AJP协议本身没有像HTTP那样内置丰富的安全控制机制,它信任前端服务器发送过来的请求信息。

2.2 CVE-2020-1938漏洞根源:信任的滥用

漏洞的根源就来自于这种“信任”。在AJP协议的数据包中,有一系列属性(request_attributes),用于传递额外的请求信息。其中有两个至关重要的属性:

  • javax.servlet.include.request_uri
  • javax.servlet.include.path_info
  • javax.servlet.include.servlet_path

这些属性的本意是用于请求派发(Request Dispatch),比如在Servlet中使用request.getRequestDispatcher(“/some/path”).include(request, response)时,Tomcat内部会设置这些属性,以便包含(include)另一个资源的内容。这个过程是服务器内部行为,路径应该是受控的。

然而,通过构造恶意的AJP请求,攻击者可以直接从客户端(伪装成可信的前端服务器)向Tomcat的AJP端口发送数据包,并手动设置这些属性。当Tomcat接收到这些属性时,由于缺乏严格的校验,它会错误地认为这是一个合法的内部请求派发,从而根据攻击者指定的路径去读取文件。

更致命的是,通过精心构造javax.servlet.include.path_info属性,攻击者可以利用目录遍历(../)跳出Web应用根目录(WEB-INF或应用目录),读取服务器上的任意文件,例如WEB-INF/web.xml(泄露数据库密码等配置)、/etc/passwd等系统敏感文件。在某些特定配置下,甚至能配合文件上传等功能,实现远程代码执行(RCE)。

注意:此漏洞的利用前提是Tomcat开启了AJP Connector(默认配置下是开启的),并且AJP服务端口(默认8009)暴露给了不可信的网络。在云原生和容器化环境下,如果错误地将AJP端口映射到公网,风险极高。

3. 漏洞复现环境搭建与配置

3.1 靶机环境准备

为了安全地复现,我们需要在隔离的环境(如虚拟机或Docker)中搭建一个存在漏洞的Tomcat。

1. 选择有漏洞的Tomcat版本:该漏洞影响以下版本:

  • 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

这里我选择Apache Tomcat 8.5.32作为复现目标,因为它是一个广泛使用的、且受漏洞影响的版本。

2. 使用Docker快速搭建(推荐):这是最干净、便捷的方式。我们可以从Docker Hub拉取特定版本的Tomcat镜像,并确保其AJP端口暴露。

# 拉取Tomcat 8.5.32 官方镜像 docker pull tomcat:8.5.32-jre8 # 运行容器,将Tomcat的HTTP端口8080和AJP端口8009都映射到宿主机 docker run -d -p 8080:8080 -p 8009:8009 --name vulnerable-tomcat tomcat:8.5.32-jre8

执行后,访问http://你的宿主机IP:8080,应该能看到Tomcat的默认主页。

3. 验证AJP服务:使用netstatnmap在宿主机上检查端口。

# 在宿主机上执行 nmap -sV -p 8009 127.0.0.1

如果看到8009端口开放,且服务识别为ajp,说明环境准备就绪。

3.2 攻击机环境与工具准备

我们需要一个能发送自定义AJP协议包的工具。最常用的是python-ajpy库,但这里我推荐一个更易用、功能更集中的开源工具:tomcat-ajp-poc。这个工具专为利用CVE-2020-1938而编写。

1. 下载利用工具:

git clone https://github.com/YDHCUI/tomcat-ajp-poc.git cd tomcat-ajp-poc

2. 安装Python依赖:该工具需要Python3。

pip3 install -r requirements.txt

主要依赖是用于处理AJP协议数据包序列化的库。

3. 工具结构预览:

  • ajp_shooter.py: 核心利用脚本,可以发起文件读取请求。
  • ajp_listener.py: 一个用于测试的AJP监听器(本次复现不主要使用)。

4. 漏洞利用实操:步步为营读取敏感文件

现在,我们进入最关键的实操环节。假设我们的靶机IP是192.168.1.100

4.1 基础利用:读取WEB-INF/web.xml

WEB-INF/web.xml是Java Web应用的核心配置文件,里面常包含数据库连接池配置、Servlet映射、安全约束等敏感信息。它是攻击者首要目标。

使用ajp_shooter.py进行读取:

python3 ajp_shooter.py http://192.168.1.100:8080/ 8009 /WEB-INF/web.xml

命令参数拆解:

  • http://192.168.1.100:8080/: 这是Tomcat的HTTP地址。工具需要这个地址来获取一些上下文信息,如服务器名称、端口(虽然AJP是独立端口)。
  • 8009: AJP服务端口。
  • /WEB-INF/web.xml: 指定要读取的文件路径。注意,这里直接使用了Web应用根目录下的路径。

执行结果分析:如果漏洞存在且路径正确,终端会直接打印出web.xml文件的内容。你可能会看到<resource-ref>标签内的数据库JDBC URL、用户名和密码(可能是明文或加密过的)。这已经构成了一次严重的信息泄露事件。

4.2 目录遍历:读取服务器任意文件

基础利用只能读取Web应用目录内的文件。真正的威力在于目录遍历。AJP属性对路径的校验不严,允许使用../

尝试读取系统文件:

python3 ajp_shooter.py http://192.168.1.100:8080/ 8009 /WEB-INF/../../../../etc/passwd

路径构造解析:我们从/WEB-INF(Web应用内的受保护目录)出发,通过多次../向上回退目录,最终定位到Linux系统的/etc/passwd文件。Tomcat在处理javax.servlet.include.path_info时,没有有效地规范化(normalize)和限制这个路径,导致了穿越。

执行成功后,你将看到系统的/etc/passwd文件内容被打印出来。这证明了攻击者可以读取服务器操作系统上的任意文件,只要Tomcat进程有权限读取(通常Tomcat以非root用户运行,但仍可读取大量敏感文件)。

其他敏感文件示例:

  • /proc/self/environ: 获取Tomcat进程的环境变量,可能包含密钥。
  • ../../myapp/WEB-INF/classes/config.properties: 读取同一服务器上其他Web应用的配置文件。
  • Windows系统下的C:\windows\system32\drivers\etc\hosts

实操心得:路径的“起点”玄机在实际测试中,path_info的解析“起点”有时会因Tomcat版本和具体请求上下文略有差异。除了从/WEB-INF开始遍历,也可以尝试从//static/等已知或可能存在的目录开始构造。多尝试几种../的组合是必要的。工具可能内置了一些常见Payload,但手动理解和构造能帮你应对更复杂的环境。

4.3 利用场景扩展:从文件包含到代码执行

单纯的文件读取危害巨大,但攻击者往往追求远程代码执行(RCE)。这需要结合其他条件:

  1. 配合文件上传功能:如果目标应用存在任意文件上传漏洞,且上传路径可知。攻击者可以先上传一个包含恶意JSP代码的文件(如shell.jsp)到某个可访问目录(如/upload/)。然后,利用AJP文件包含漏洞,通过/WEB-INF/../../upload/shell.jsp这样的路径去“包含”这个JSP文件。Tomcat在处理.jsp文件时,会将其编译并执行其中的Java代码,从而实现RCE。

  2. 写入特定目录:在某些老旧或不安全配置下,如果攻击者能通过其他方式(如SSRF、框架漏洞)向Tomcat的webapps目录或work/Catalina临时目录写入JSP文件,同样可以借助此漏洞触发执行。

模拟利用流程(概念性):

1. 发现目标存在AJP漏洞(端口开放,可读取文件)。 2. 寻找目标应用的上传点,上传 `shell.jsp` (内容为 `<% Runtime.getRuntime().exec(request.getParameter("cmd")); %>`)。 3. 确认上传后的文件路径,例如 `/uploads/shell.jsp`。 4. 使用AJP漏洞包含该JSP文件:`python3 ajp_shooter.py ... /WEB-INF/../../../uploads/shell.jsp?cmd=id` 5. 如果包含成功,Tomcat会执行`id`命令,攻击者可通过其他方式(如反弹shell)获取输出。

重要警告:此部分仅为原理说明,旨在强调漏洞组合利用的严重性。在实际安全测试中,必须获得明确的书面授权方可进行任何可能影响系统可用性或数据完整性的操作。

5. 漏洞修复与安全加固方案

复现漏洞是为了更好地防御。针对CVE-2020-1938,有以下几种根本性的解决方案。

5.1 官方补丁升级(首选)

最直接的方法是升级Tomcat到已修复的版本:

  • Tomcat 9.x 升级至 9.0.31 或更高
  • Tomcat 8.5.x 升级至 8.5.51 或更高
  • Tomcat 7.x 升级至 7.0.100 或更高

升级后,Tomcat会对AJP请求中的javax.servlet.include.*等属性进行严格的校验,禁止从客户端设置这些用于请求派发的属性。

5.2 禁用或保护AJP Connector(治本)

如果业务上不需要使用AJP协议(例如,Tomcat独立运行,前面没有Apache/Nginx做反向代理),最彻底的安全措施是直接禁用AJP Connector

修改conf/server.xml找到如下配置段:

<!-- 默认的AJP 1.3 Connector配置 --> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

有两种处理方式:

  1. 直接删除或注释掉这整行<Connector ... />配置。
  2. 修改其监听地址,仅允许本地或可信网络访问:
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" address="127.0.0.1" />
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" address="192.168.1.0" />
    修改后重启Tomcat生效。

5.3 配置AJP认证与秘密(缓解措施)

如果必须使用AJP(例如与Apache HTTPD集成),务必启用AJP连接器的secret认证。

conf/server.xml中配置:

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" address="127.0.0.1" secret="YourStrongAJPSecret123!" />

同时,在前端服务器(如Apache)的AJP代理配置中(例如mod_proxy_ajp),也需要设置相同的秘密:

ProxyPass / ajp://localhost:8009/ secret=YourStrongAJPSecret123!

这样,只有知道秘密的前端服务器才能与Tomcat建立AJP连接,可以有效防止来自网络其他位置的直接AJP攻击。

5.4 网络层访问控制

在防火墙或安全组策略中,严格限制对Tomcat服务器8009端口的访问。只允许前端代理服务器(如Apache/Nginx所在主机)的IP地址访问该端口,禁止任何其他来源的访问。这是纵深防御中关键的一环。

6. 排查技巧与深度防御建议

在实际运维中,如何发现和防御此类风险?

6.1 自查清单

  1. 端口扫描自查:定期从外部网络和内部不同网段扫描你的服务器,检查8009端口是否意外暴露。命令如nmap -p 8009 <你的服务器IP>
  2. 配置审计:检查Tomcat的server.xml,确认AJP Connector的配置。关注portaddresssecret属性。
  3. 版本确认:运行{TOMCAT_HOME}/bin/version.sh(Linux) 或version.bat(Windows),确认Tomcat版本是否在受影响范围。

6.2 深度防御建议

  • 最小权限原则:运行Tomcat的进程用户(如tomcat用户)应仅拥有必要的文件系统读/写权限,避免使用root用户。这可以限制即使文件被读取,危害范围也有限。
  • 独立部署:为每个Web应用使用独立的Tomcat实例或至少是独立的环境变量、工作目录,实现应用间隔离。
  • 安全基线:遵循Apache Tomcat官方安全文档和业界安全基线(如CIS Benchmarks for Tomcat)进行配置。
  • WAF/IPS防护:在网络边界部署Web应用防火墙(WAF)或入侵防御系统(IPS),可以配置规则拦截对AJP端口(8009)的异常请求或包含特定攻击特征的流量。

6.3 常见问题排查实录

Q1: 我用了工具,但返回“500 Internal Server Error”或读取不到文件?A1: 可能有几种原因: *版本不对:目标Tomcat版本可能已升级修复。 *路径不对:Web应用的上下文路径(Context Path)可能不是根路径(/)。例如,如果应用部署在http://host:8080/myapp,你需要尝试在路径中包含/myapp,如/myapp/WEB-INF/web.xml。或者,应用本身没有WEB-INF目录。 *权限不足:Tomcat进程用户对目标文件没有读取权限。 *网络问题:防火墙阻止了AJP端口通信。

Q2: 我的服务器在云上,只开了80/443端口,还有风险吗?A2: 如果AJP端口(8009)没有在安全组或云防火墙中开放给公网,那么来自互联网的直接攻击风险较低。但需警惕内部横向移动。如果攻击者通过其他漏洞(如Web应用漏洞)获取了服务器内网访问权限,他们依然可以尝试攻击本地的8009端口。

Q3: 除了文件读取,这个漏洞还能用来做什么?A3: 核心是文件包含。在特定条件下,如果服务器配置了autoDeploy且允许部署WAR包,理论上可能通过构造极其复杂的请求进行应用部署,但难度极大,非主流利用方式。主要危害还是敏感信息泄露,为后续攻击提供“弹药”。

这次完整的复现和分析让我对AJP协议的安全模型有了刻骨铭心的认识。安全往往崩溃在信任边界最模糊的地方。对于运维和开发同学,定期审查网络暴露面、关闭不必要的服务、及时更新组件,永远是成本最低且最有效的安全措施。在微服务和云原生架构下,每个容器或Pod的端口暴露都需要格外审慎,像AJP这种“内部协议”的端口,绝不应该出现在服务对外暴露的清单里。