JetBrains TeamCity 鉴权绕过浅析
CVE-2023-42793 影响版本:2023.05.3 and below 补丁分析 主要是针对 RequestInterceptors 这个类做的,在 myPreHandlingDisabled 字段通过反射添加了一个路由 /RPC2 接着在 myPreHandlingDisabled 中的 myMatchingPaths 字段中移除掉所有路径满足 /**/RPC2 。 代码审计 因
CVE-2023-42793
- 影响版本:2023.05.3 and below
- 补丁分析
主要是针对RequestInterceptors这个类做的,在myPreHandlingDisabled字段通过反射添加了一个路由/RPC2接着在myPreHandlingDisabled中的myMatchingPaths字段中移除掉所有路径满足/**/RPC2。

- 代码审计
因此问题肯定是出在了RequestInterceptors这里,根据 What is Spring Boot Request Interceptor and How to Use It? | by Ahmet Emre DEMİRŞEN | Medium 可知这个类用于在请求过程中作鉴权的,其最先被执行的是preHandle方法

分析代码看到这里会先执行一个requestPreHandlingAllowed如果返回 false 的话直接放行。

跟进会先判断访问的是否为 JSP 页面,之后这里会调用myPreHandlingDisabled字段去做路径匹配,我们注意到了一个/**/RPC2

这个路径是在类初始化的时候创建并添加到myPreHandlingDisabled字段中去的

有了这个路径,那么我们访问以前以/RPC作为后缀的 url 均不会被鉴权。
而恰好 jetbrains.buildServer.server.rest.request.UserRequest#createToken(java.lang.String, java.lang.String, java.lang.String) 可以通过 path 变量来创建 token (这里的作用也就是可以创建指定用户的 token,用户指定通过 userLocator ,对于 admin 默认为id:1),我们直接 name 位置设置为RPC2便可以绕过鉴权。

- 复现


JetBrains TeamCity RCE - CVE-2023-42793 (projectdiscovery.io) 提供了一种 RCE 的方式,也就是利用获取到的 admin token 开启 debug API,触发配置重载过程中 RCE

之后去掉 Content-Type 字段之后就可以 RCE 了

CVE-2024-27198
- 影响版本:before 2023.11.4
- 补丁 https://download-cdn.jetbrains.com/teamcity/plugins/internal/security_patch_2024_02.zip
用了三种反编译工具结果都不太清楚,可能是 JDK 版本过高的缘故,师傅们可以研究研究 - 代码分析
分析版本: 2023.11.3
以绕过授权端点/app/rest/server为例子

teamcity 业务请求的分发处位于jetbrains.buildServer.controllers.BaseController首先看如果发送一个需要授权的请求curl --noproxy '*' -v "http://127.0.0.1:8111/profile.html"
jetbrains.buildServer.controllers.BaseController#handleRequestInternal

可以看到在这里会跳转至/unauthorized.html未授权页面,同时无法获取到模型视图参数。这是因为 web.xml 当作配置了 error-page ,当 401 时就会跳转到/unauthorized.html

但如果我们修改为不存在的 uri:curl --noproxy '*' -v "http://127.0.0.1:8111/xxx"
jetbrains.buildServer.controllers.BaseController#handleRequestInternal可以看到这里面是可以获取到模型视图参数的

原因在于不存在的路由导致 error-code 404 会分发至 PageNotFoundController 处理,并为模型视图设置视图名为404.jsp

跟入updateViewIfRequestHasJspParameter,里面有个获取外部参数的操作getJspFromRequest

跟进jetbrains.buildServer.controllers.BaseController#getJspFromRequest这里会取出jsp参数,如果.jsp结尾或者内容不包含admin/则不会返回 null,同时如果和默认视图名404.jsp不同的话则会将其设置为视图作为最终返回的内容。

payload 修改为:随后视图渲染curl --noproxy '*' -ik "http://127.0.0.1:8111/xxxx?jsp=/app/rest/server;.jsp"
org.springframework.web.servlet.DispatcherServlet#resolveViewName得到JstlView类型的view

对于 jsp 文件的视图渲染,根据官方文档可知会作 dispatch 处理,也就是会将当前的viewName当作 url 请求进行处理

随后在执行获取视图路径的RequestDispatcher过程中,会对 path 作如下处理(位于org.apache.catalina.core.ApplicationContext#getRequestDispatcher)其中stripPathParams方法会将 uri 中出现;及其之后的部分去除,因此最后请求 path 变为了/app/rest/server,进而绕过鉴权

补充:为什么后面的 dispatch 请求就可以绕过鉴权?
我们之前知道鉴权处是在jetbrains.buildServer.controllers.interceptors.RequestInterceptors#preHandle方法当中,可以看到这里会根据当前应用请求获取一个 Stack 类,根据size以及请求 uri 的变化次数可以大概猜到这里应该表示的是请求处理栈。

拦截器只会在栈空间为 1 的时候起作用,但我们当时请求的是个不存在的 uri,所以自然始终是返回 true 的

- 利用
和之前的漏洞一样,我们也可以通过/app/rest/users/id:1/tokens/{name}获取管理员 tokencurl --noproxy '*' -ik "http://127.0.0.1:8111/xxxx?jsp=/app/rest/users/id:1/tokens/racerz;.jsp" -X POST

总结
可能是 Java 版本过高的缘故,分析代码过程中部分核心位置反编译失败导致无法理清逻辑(后续值得研究一下反编译的问题)。不过目前针对 CVE-2024-27198 漏洞利用思路的猜想是:spring 支持当出现 error code 时通过配置 location (渲染视图位置)来响应错误页面,这实际上是做了二次 dispatch 请求. 而 teamcity 的鉴权操作并未对这种类似嵌套的请求作处理,同时视图名又可控,可覆盖默认的配置,导致可以以未授权的方式请求授权页面。(CVE-2024-27199 应该就是另外通过目录穿越的方式来控制第二次 dispatch 的位置)

参考
[1] CVE-2023-42793 JetBrains TeamCity 权限绕过 - Y4er的博客
[2] vulhub/teamcity/CVE-2023-42793/README.zh-cn.md at master · vulhub/vulhub (github.com)
[3] Source Code at Risk: Critical Code Vulnerability in CI/CD Platform TeamCity | Sonar (sonarsource.com)
[4] JetBrains TeamCity RCE - CVE-2023-42793 (projectdiscovery.io)
[5] CVE-2024-27198 and CVE-2024-27199: JetBrains TeamCity Multiple Authentication Bypass Vulnerabilities (FIXED) | Rapid7 Blog
[6] https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/exceptionhandlers.html
能否移动至技术文章板块?